Beispiel #1
0
class StimuliSurface(SpatioTemporalPattern):
    """
    A spatio-temporal pattern defined in a Surface DataType.
    It includes the list of focal points.
    """

    surface = surfaces.CorticalSurface(label="Surface", order=1)

    focal_points_surface = basic.List(label="Focal points",
                                      locked=True,
                                      order=4)

    focal_points_triangles = basic.List(label="Focal points triangles",
                                        locked=True,
                                        order=4)

    def configure_space(self, region_mapping=None):
        """
        Do necessary preparations in order to use this stimulus.
        NOTE: this was previously done in simulator configure_stimuli() method.
        It no needs to be used in stimulus viewer also.
        """
        dis_shp = (self.surface.number_of_vertices,
                   numpy.size(self.focal_points_surface))
        # TODO: When this was in Simulator it was number of nodes, using surface vertices
        # breaks surface simulations which include non-cortical regions.

        distance = numpy.zeros(dis_shp)
        k = -1
        for focal_point in self.focal_points_surface:
            k += 1
            foci = numpy.array([focal_point], dtype=numpy.int32)
            distance[:, k] = self.surface.geodesic_distance(foci)
        super(StimuliSurface, self).configure_space(distance)
Beispiel #2
0
 def test_cortical_surface(self):
     dt = surfaces.CorticalSurface().from_file()
     dt.__setattr__('valid_for_simulations', True)
     assert isinstance(dt, surfaces.CorticalSurface)
     dt.configure()
     summary_info = dt.summary_info()
     assert summary_info['Number of edges'] == 49140
     assert summary_info['Number of triangles'] == 32760
     assert summary_info['Number of vertices'] == 16384
     assert dt.surface_type == surfaces.CORTICAL
     assert len(dt.vertex_neighbours) == 16384
     assert isinstance(dt.vertex_neighbours[0], frozenset)
     assert len(dt.vertex_triangles) == 16384
     assert isinstance(dt.vertex_triangles[0], frozenset)
     assert len(dt.nth_ring(0)) == 17
     assert dt.triangle_areas.shape == (32760, 1)
     assert dt.triangle_angles.shape == (32760, 3)
     assert len(dt.edges) == 49140
     assert abs(dt.edge_mean_length - 3.97605292887) < 0.00000001
     assert abs(dt.edge_min_length - 0.6638) < 0.0001
     assert abs(dt.edge_max_length - 7.7567) < 0.0001
     assert len(dt.edge_triangles) == 49140
     assert [] == dt.validate_topology_for_simulations().warnings
     assert dt.vertices.shape == (16384, 3)
     assert dt.vertex_normals.shape == (16384, 3)
     assert dt.triangles.shape == (32760, 3)
     topologicals = dt.compute_topological_constants()
     assert 4 == topologicals[0]
     assert all([a.size == 0 for a in topologicals[1:]])
Beispiel #3
0
 def test_cortical_surface(self):
     dt = surfaces.CorticalSurface(load_default=True)
     self.assertTrue(isinstance(dt, surfaces.CorticalSurface))
     dt.configure()
     summary_info = dt.summary_info
     self.assertEqual(summary_info['Number of edges'], 49140)
     self.assertEqual(summary_info['Number of triangles'], 32760)
     self.assertEqual(summary_info['Number of vertices'], 16384)
     self.assertEqual(dt.surface_type, surfaces.CORTICAL)
     self.assertEqual(len(dt.vertex_neighbours), 16384)
     self.assertTrue(isinstance(dt.vertex_neighbours[0], frozenset))
     self.assertEqual(len(dt.vertex_triangles), 16384)
     self.assertTrue(isinstance(dt.vertex_triangles[0], frozenset))
     self.assertEqual(len(dt.nth_ring(0)), 17)
     self.assertEqual(dt.triangle_areas.shape, (32760, 1))
     self.assertEqual(dt.triangle_angles.shape, (32760, 3))
     self.assertEqual(len(dt.edges), 49140)
     self.assertTrue(abs(dt.edge_length_mean - 3.97605292887) < 0.00000001)
     self.assertTrue(abs(dt.edge_length_min - 0.6638) < 0.0001)
     self.assertTrue(abs(dt.edge_length_max - 7.7567) < 0.0001)
     self.assertEqual(len(dt.edge_triangles), 49140)
     self.assertEqual([], dt.validate_topology_for_simulations().warnings)
     self.assertEqual(dt.get_data_shape('vertices'), (16384, 3))
     self.assertEqual(dt.get_data_shape('vertex_normals'), (16384, 3))
     self.assertEqual(dt.get_data_shape('triangles'), (32760, 3))
     topologicals = dt.compute_topological_constants()
     self.assertEqual(4, topologicals[0])
     self.assertTrue(all([a.size == 0 for a in topologicals[1:]]))
 def test_cortical_surface(self):
     dt = surfaces.CorticalSurface(load_default=True)
     self.assertTrue(isinstance(dt, surfaces.CorticalSurface))
     dt.configure()
     summary_info = dt.summary_info
     self.assertEqual(summary_info['Number of edges'], 49140)
     self.assertEqual(summary_info['Number of triangles'], 32760)
     self.assertEqual(summary_info['Number of vertices'], 16384)
     self.assertEqual(dt.surface_type, surfaces_data.CORTICAL)
     self.assertEqual(len(dt.vertex_neighbours), 16384)
     self.assertTrue(isinstance(dt.vertex_neighbours[0], frozenset))
     self.assertEqual(len(dt.vertex_triangles), 16384)
     self.assertTrue(isinstance(dt.vertex_triangles[0], frozenset))
     self.assertEqual(len(dt.nth_ring(0)), 17)
     self.assertEqual(dt.triangle_areas.shape, (32760, 1))
     self.assertEqual(dt.triangle_angles.shape, (32760, 3))
     self.assertEqual(len(dt.edges), 49140)
     self.assertTrue(abs(dt.edge_length_mean - 3.97605292887) < 0.00000001)
     self.assertTrue(abs(dt.edge_length_min - 0.663807567201) < 0.00000001)
     self.assertTrue(abs(dt.edge_length_max - 7.75671853782) < 0.00000001)
     self.assertEqual(len(dt.edge_triangles), 49140)
     self.assertEqual(dt.check(), (True, 4, [], [], [], ""))
     self.assertEqual(dt.get_data_shape('vertices'), (16384, 3))
     self.assertEqual(dt.get_data_shape('vertex_normals'), (16384, 3))
     self.assertEqual(dt.get_data_shape('triangles'), (32760, 3))
Beispiel #5
0
class ProjectionData(MappedType):
    """
    Base DataType for representing a ProjectionMatrix.
    The projection is between a source of type CorticalSurface and a set of Sensors.
    """

    projection_type = basic.String

    __mapper_args__ = {'polymorphic_on': 'projection_type'}

    brain_skull = surfaces.BrainSkull(
        label="Brain Skull",
        default=None,
        required=False,
        doc="""Boundary between skull and cortex domains.""")

    skull_skin = surfaces.SkullSkin(
        label="Skull Skin",
        default=None,
        required=False,
        doc="""Boundary between skull and skin domains.""")

    skin_air = surfaces.SkinAir(
        label="Skin Air",
        default=None,
        required=False,
        doc="""Boundary between skin and air domains.""")

    conductances = basic.Dict(
        label="Domain conductances",
        required=False,
        default={
            'air': 0.0,
            'skin': 1.0,
            'skull': 0.01,
            'brain': 1.0
        },
        doc=""" A dictionary representing the conductances of ... """)

    sources = surfaces.CorticalSurface(label="surface or region",
                                       default=None,
                                       required=True)

    sensors = sensors.Sensors(
        label="Sensors",
        default=None,
        required=False,
        doc=""" A set of sensors to compute projection matrix for them. """)

    projection_data = arrays.FloatArray(label="Projection Matrix Data",
                                        default=None,
                                        required=True)
class StimuliSurfaceData(SpatioTemporalPatternData):
    """
    A spatio-temporal pattern defined in a Surface DataType.
    It includes the list of focal points.
    """

    surface = surfaces.CorticalSurface(label="Surface", order=1)

    focal_points_surface = basic.List(label="Focal points",
                                      locked=True,
                                      order=4)

    focal_points_triangles = basic.List(label="Focal points triangles",
                                        locked=True,
                                        order=4)
Beispiel #7
0
    def __init__(self,
                 surface=None,
                 focal_points_surface=None,
                 focal_points_triangles=None,
                 *args,
                 **kwargs):
        if surface is None:
            surface = surfaces.CorticalSurface()
        self.surface = surface

        if focal_points_surface is None:
            focal_points_surface = []
        self.focal_points_surface = focal_points_surface

        if focal_points_triangles is None:
            focal_points_triangles = []
        self.focal_points_triangles = focal_points_triangles

        super(StimuliSurface, self).__init__(*args, **kwargs)
Beispiel #8
0
 def test_stimulisurface(self):
     srf = surfaces.CorticalSurface(load_default=True)
     srf.configure()
     dt = patterns.StimuliSurface()
     dt.surface = srf
     dt.spatial = equations.DiscreteEquation()
     dt.temporal = equations.Gaussian()
     dt.focal_points_surface = [0, 1, 2]
     dt.focal_points_triangles = [0, 1, 2]
     dt.configure()
     dt.configure_space()
     summary = dt.summary_info
     assert summary['Type'] == "StimuliSurface"
     assert dt.space.shape == (16384, 3)
     assert isinstance(dt.spatial, equations.DiscreteEquation)
     assert dt.spatial_pattern.shape == (16384, 1)
     assert dt.surface is not None
     assert isinstance(dt.temporal, equations.Gaussian)
     assert dt.temporal_pattern is None
     assert dt.time is None
Beispiel #9
0
 def test_stimulisurface(self):
     srf = surfaces.CorticalSurface()
     srf.configure()
     dt = patterns.StimuliSurface()
     dt.surface = srf
     dt.spatial = equations.DiscreteEquation()
     dt.temporal = equations.Gaussian()
     dt.focal_points_surface = [0, 1, 2]
     dt.focal_points_triangles = [0, 1, 2]
     dt.configure()
     dt.configure_space()
     summary = dt.summary_info
     self.assertEqual(summary['Type'], "StimuliSurface")
     self.assertEqual(dt.space.shape, (81924, 3))
     self.assertTrue(isinstance(dt.spatial, equations.DiscreteEquation))
     self.assertEqual(dt.spatial_pattern.shape, (81924, 1))
     self.assertTrue(dt.surface is not None)
     self.assertTrue(isinstance(dt.temporal, equations.Gaussian))
     self.assertTrue(dt.temporal_pattern is None)
     self.assertTrue(dt.time is None)
 def test_default_attributes(self):
     """
     Test that default_console attributes are populated.
     """
     cortex = surfaces.CorticalSurface()
     cortex.configure()
     self.assertTrue(cortex.vertices is not None)
     self.assertEqual(81924, cortex.number_of_vertices)
     self.assertEqual((81924, 3), cortex.vertices.shape)
     self.assertEqual((81924, 3), cortex.vertex_normals.shape)
     self.assertEqual(163840, cortex.number_of_triangles)
     self.assertEqual((163840, 3), cortex.triangles.shape)
     
     conn = connectivity.Connectivity()
     conn.configure()
     self.assertTrue(conn.centres is not None)
     self.assertEqual((74,), conn.region_labels.shape)
     self.assertEqual('lA1', conn.region_labels[0])
     self.assertEquals((74, 3), conn.centres.shape)
     self.assertEquals((74, 74), conn.weights.shape)
     self.assertEquals((74, 74), conn.tract_lengths.shape)
     self.assertEquals(conn.delays.shape, conn.tract_lengths.shape)
     self.assertEqual(74, conn.number_of_regions)
class LocalConnectivity(types_mapped.MappedType):
    """
    A sparse matrix for representing the local connectivity within the Cortex.
    """
    _ui_name = "Local connectivity"

    surface = surfaces.CorticalSurface(label="Surface", order=1)

    matrix = types_mapped.SparseMatrix(order=-1)

    equation = equations.FiniteSupportEquation(label="Spatial",
                                               required=False,
                                               default=equations.Gaussian,
                                               order=2)

    cutoff = basic.Float(
        label="Cutoff distance (mm)",
        default=40.0,
        doc="Distance at which to truncate the evaluation in mm.",
        order=3)

    def compute(self):
        """
        Compute current Matrix.
        """
        LOG.info("Mapping geodesic distance through the LocalConnectivity.")

        #Start with data being geodesic_distance_matrix, then map it through equation
        self.equation.pattern = self.matrix_gdist.data

        #Then replace original data with result...
        self.matrix_gdist.data = self.equation.pattern

        #Homogenise spatial discretisation effects across the surface
        nv = self.matrix_gdist.shape[0]
        ind = numpy.arange(nv, dtype=int)
        pos_mask = self.matrix_gdist.data > 0.0
        neg_mask = self.matrix_gdist.data < 0.0
        pos_con = self.matrix_gdist.copy()
        neg_con = self.matrix_gdist.copy()
        pos_con.data[neg_mask] = 0.0
        neg_con.data[pos_mask] = 0.0
        pos_contrib = pos_con.sum(axis=1)
        pos_contrib = numpy.array(pos_contrib).squeeze()
        neg_contrib = neg_con.sum(axis=1)
        neg_contrib = numpy.array(neg_contrib).squeeze()
        pos_mean = pos_contrib.mean()
        neg_mean = neg_contrib.mean()
        if ((pos_mean != 0.0 and any(pos_contrib == 0.0))
                or (neg_mean != 0.0 and any(neg_contrib == 0.0))):
            msg = "Cortical mesh is too coarse for requested LocalConnectivity."
            LOG.warning(msg)
            bad_verts = ()
            if pos_mean != 0.0:
                bad_verts = bad_verts + numpy.nonzero(pos_contrib == 0.0)
            if neg_mean != 0.0:
                bad_verts = bad_verts + numpy.nonzero(neg_contrib == 0.0)
            LOG.debug("Problem vertices are: %s" % str(bad_verts))
        pos_hf = numpy.zeros(shape=pos_contrib.shape)
        pos_hf[pos_contrib != 0] = pos_mean / pos_contrib[pos_contrib != 0]
        neg_hf = numpy.zeros(shape=neg_contrib.shape)
        neg_hf[neg_contrib != 0] = neg_mean / neg_contrib[neg_contrib != 0]
        pos_hf_diag = scipy.sparse.csc_matrix((pos_hf, (ind, ind)),
                                              shape=(nv, nv))
        neg_hf_diag = scipy.sparse.csc_matrix((neg_hf, (ind, ind)),
                                              shape=(nv, nv))
        homogenious_conn = (pos_hf_diag * pos_con) + (neg_hf_diag * neg_con)

        #Then replace unhomogenised result with the spatially homogeneous one...
        if not homogenious_conn.has_sorted_indices:
            homogenious_conn.sort_indices()

        self.matrix = homogenious_conn

    def _validate_before_store(self):
        """
        Overrides MappedType._validate_before_store to use a custom error for missing matrix.
        """
        # Sparse Matrix is required so we should check if there is any data stored for it
        if self.matrix is None:
            msg = ("LocalConnectivity can not be stored because it "
                   "has no SparseMatrix attached.")
            raise exceptions.ValidationException(msg)

        super(LocalConnectivity, self)._validate_before_store()

    @staticmethod
    def from_file(source_file="local_connectivity_16384.mat", instance=None):

        if instance is None:
            result = LocalConnectivity()
        else:
            result = instance

        source_full_path = try_get_absolute_path("tvb_data.local_connectivity",
                                                 source_file)
        reader = FileReader(source_full_path)

        result.matrix = reader.read_array(matlab_data_name="LocalCoupling")
        return result

    def get_min_max_values(self):
        """
        Retrieve the minimum and maximum values from the metadata.
        :returns: (minimum_value, maximum_value)
        """
        metadata = self.get_metadata('matrix')
        return metadata[self.METADATA_ARRAY_MIN], metadata[
            self.METADATA_ARRAY_MAX]

    def _find_summary_info(self):
        """
        Gather scientifically interesting summary information from an instance
        of this datatype.
        """
        return self.get_info_about_array('matrix', [
            self.METADATA_ARRAY_MAX, self.METADATA_ARRAY_MIN,
            self.METADATA_ARRAY_MEAN, self.METADATA_ARRAY_SHAPE
        ])

    def compute_sparse_matrix(self):
        """
        NOTE: Before calling this method, the surface field
        should already be set on the local connectivity.

        Computes the sparse matrix for this local connectivity.
        """
        if self.surface is None:
            raise AttributeError(
                'Require surface to compute local connectivity.')

        self.matrix_gdist = surfaces.gdist.local_gdist_matrix(
            self.surface.vertices.astype(numpy.float64),
            self.surface.triangles.astype(numpy.int32),
            max_distance=self.cutoff)

        self.compute()
        # Avoid having a large data-set in memory.
        self.matrix_gdist = None
Beispiel #12
0
 def test_corticalsurface(self):
     dt = surfaces.CorticalSurface()
     self.assertEqual(dt.get_data_shape('vertices'), (81924, 3))
     self.assertEqual(dt.get_data_shape('vertex_normals'), (81924, 3))
     self.assertEqual(dt.get_data_shape('triangles'), (163840, 3))
Beispiel #13
0
class ProjectionMatrix(MappedType):
    """
    Base DataType for representing a ProjectionMatrix.
    The projection is between a source of type CorticalSurface and a set of Sensors.
    """

    projection_type = basic.String

    __mapper_args__ = {'polymorphic_on': 'projection_type'}

    brain_skull = surfaces.BrainSkull(
        label="Brain Skull",
        default=None,
        required=False,
        doc="""Boundary between skull and cortex domains.""")

    skull_skin = surfaces.SkullSkin(
        label="Skull Skin",
        default=None,
        required=False,
        doc="""Boundary between skull and skin domains.""")

    skin_air = surfaces.SkinAir(
        label="Skin Air",
        default=None,
        required=False,
        doc="""Boundary between skin and air domains.""")

    conductances = basic.Dict(
        label="Domain conductances",
        required=False,
        default={
            'air': 0.0,
            'skin': 1.0,
            'skull': 0.01,
            'brain': 1.0
        },
        doc=""" A dictionary representing the conductances of ... """)

    sources = surfaces.CorticalSurface(label="surface or region",
                                       default=None,
                                       required=True)

    sensors = sensors.Sensors(
        label="Sensors",
        default=None,
        required=False,
        doc=""" A set of sensors to compute projection matrix for them. """)

    projection_data = arrays.FloatArray(label="Projection Matrix Data",
                                        default=None,
                                        required=True)

    @property
    def shape(self):
        return self.projection_data.shape

    @classmethod
    def from_file(cls,
                  source_file,
                  matlab_data_name=None,
                  is_brainstorm=False,
                  instance=None):

        if instance is None:
            proj = cls()
        else:
            proj = instance

        source_full_path = try_get_absolute_path("tvb_data.projectionMatrix",
                                                 source_file)
        reader = FileReader(source_full_path)
        if is_brainstorm:
            proj.projection_data = reader.read_gain_from_brainstorm()
        else:
            proj.projection_data = reader.read_array(
                matlab_data_name=matlab_data_name)
        return proj
Beispiel #14
0
class ProjectionMatrix(MappedType):
    """
    Base DataType for representing a ProjectionMatrix.
    The projection is between a source of type CorticalSurface and a set of Sensors.
    """

    projection_type = basic.String

    __mapper_args__ = {'polymorphic_on': 'projection_type'}

    brain_skull = surfaces.BrainSkull(
        label="Brain Skull",
        default=None,
        required=False,
        doc="""Boundary between skull and cortex domains.""")

    skull_skin = surfaces.SkullSkin(
        label="Skull Skin",
        default=None,
        required=False,
        doc="""Boundary between skull and skin domains.""")

    skin_air = surfaces.SkinAir(
        label="Skin Air",
        default=None,
        required=False,
        doc="""Boundary between skin and air domains.""")

    conductances = basic.Dict(
        label="Domain conductances",
        required=False,
        default={
            'air': 0.0,
            'skin': 1.0,
            'skull': 0.01,
            'brain': 1.0
        },
        doc=""" A dictionary representing the conductances of ... """)

    sources = surfaces.CorticalSurface(label="surface or region",
                                       default=None,
                                       required=True)

    sensors = sensors.Sensors(
        label="Sensors",
        default=None,
        required=False,
        doc=""" A set of sensors to compute projection matrix for them. """)

    projection_data = arrays.FloatArray(label="Projection Matrix Data",
                                        default=None,
                                        required=True)

    @property
    def shape(self):
        return self.projection_data.shape

    @staticmethod
    def load_surface_projection_matrix(result, source_file):
        source_full_path = try_get_absolute_path("tvb_data.projectionMatrix",
                                                 source_file)
        if source_file.endswith(".mat"):
            # consider we have a brainstorm format
            mat = scipy.io.loadmat(source_full_path)
            gain, loc, ori = (mat[field]
                              for field in 'Gain GridLoc GridOrient'.split())
            result.projection_data = (gain.reshape(
                (gain.shape[0], -1, 3)) * ori).sum(axis=-1)
        elif source_file.endswith(".npy"):
            # numpy array with the projection matrix already computed
            result.projection_data = numpy.load(source_full_path)
        else:
            raise Exception(
                "The projection matrix must be either a numpy array or a brainstorm mat file"
            )
        return result

    @classmethod
    def from_file(cls, source_file):
        proj = cls()
        ProjectionMatrix.load_surface_projection_matrix(proj, source_file)
        return proj
def gifti2surface(gifti_image,
                  vertices_array=None,
                  normals_array=None,
                  triangles_array=None,
                  storage_path=''):
    """
    Convert GiftiImage object into our internal Surface DataType
    """
    meta = gifti_image.meta.get_data_as_dict()
    if vertices_array:
        tmpdir = os.path.join(
            gettempdir(), vertices_array.parent_cfile.get_unique_cff_name())
        LOG.debug("Using temporary folder for surface import: " + tmpdir)
        #### Extract SRC from zipFile to TEMP
        _zipfile = ZipFile(vertices_array.parent_cfile.src, 'r', ZIP_DEFLATED)
        try:
            if vertices_array is not None:
                vertices_array = _zipfile.extract(vertices_array.src, tmpdir)
                vertices_array = numpy.load(vertices_array)
            if normals_array is not None:
                normals_array = _zipfile.extract(normals_array.src, tmpdir)
                normals_array = numpy.load(normals_array)
            if triangles_array is not None:
                triangles_array = _zipfile.extract(triangles_array.src, tmpdir)
                triangles_array = numpy.load(triangles_array)
        except:
            raise RuntimeError('Can not extract %s from Connectome file.' %
                               vertices_array)

    if vertices_array is None:
        for i in xrange(int(gifti_image.numDA)):
            # Intent code is stored in the DataArray structure
            darray = gifti_image.darrays[i]
            if darray.intent == GiftiIntentCode.NIFTI_INTENT_POINTSET:
                vertices_array = darray.data
                continue
            if darray.intent == GiftiIntentCode.NIFTI_INTENT_TRIANGLE:
                triangles_array = darray.data
                continue
            if darray.intent == GiftiIntentCode.NIFTI_INTENT_NORMAL:
                normals_array = darray.data
                continue
    zero_based_val = eval(meta[constants.PARAM_ZERO_BASED])

    if meta[constants.KEY_UID] in [surfaces.CORTICAL, 'srf_reg13', 'srf_80k']:
        surface = surfaces.CorticalSurface()
    elif meta[constants.KEY_UID] in [surfaces.OUTER_SKIN, 'outer_skin']:
        surface = surfaces.SkinAir()
    else:
        raise RuntimeError(
            'Can not determine surface type. Abandon import operation.')

    surface.storage_path = storage_path
    # Remove mappings from MetaData (or too large exception will be thrown)
    del meta[constants.MAPPINGS_DICT]
    surface.set_metadata(meta)
    if os.path.isdir(tmpdir):
        shutil.rmtree(tmpdir)

    surface.zero_based_triangles = zero_based_val
    surface.vertices = vertices_array
    surface.vertex_normals = normals_array
    if zero_based_val:
        surface.triangles = numpy.array(triangles_array, dtype=numpy.int32)
    else:
        surface.triangles = numpy.array(triangles_array, dtype=numpy.int32) - 1
    surface.triangle_normals = None

    # Now check if the triangles of the surface are valid
    triangles_min_vertex = numpy.amin(surface.triangles)
    if triangles_min_vertex < 0:
        if triangles_min_vertex == -1 and not zero_based_val:
            raise RuntimeError(
                "Your triangles contains a negative vertex index. May be you have a ZERO based surface."
            )
        else:
            raise RuntimeError(
                "Your triangles contains a negative vertex index: %d" %
                triangles_min_vertex)

    no_of_vertices = len(surface.vertices)
    triangles_max_vertex = numpy.amax(surface.triangles)
    if triangles_max_vertex >= no_of_vertices:
        if triangles_max_vertex == no_of_vertices and zero_based_val:
            raise RuntimeError(
                "Your triangles contains an invalid vertex index: %d. \
            May be your surface is NOT ZERO based." % triangles_max_vertex)
        else:
            raise RuntimeError(
                "Your triangles contains an invalid vertex index: %d." %
                triangles_max_vertex)

    return surface, meta[constants.KEY_UID]