{"action":"created","comment":{"url":"https://api.github.com/repos/nipy/nibabel/pulls/comments/56096529","id":56096529,"diff_hunk":"@@ -0,0 +1,848 @@\n+# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-\n+# vi: set ft=python sts=4 ts=4 sw=4 et:\n+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n+#\n+#   See COPYING file distributed along with the NiBabel package for the\n+#   copyright and license terms.\n+#\n+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n+''' Read / write access to NIfTI2 image format\n+\n+Format described here:\n+\n+    http://www.nitrc.org/forum/message.php?msg_id=3738\n+\n+Stuff about the CIFTI2 file format here:\n+\n+    http://www.nitrc.org/plugins/mwiki/index.php/cifti2:ConnectivityMatrixFileFormats\n+\n+'''\n+from __future__ import division, print_function, absolute_import\n+\n+import re\n+import numpy as np\n+\n+from .. import xmlutils as xml\n+from ..externals.six import string_types\n+from ..externals.six.moves import reduce\n+from ..filebasedimages import FileBasedHeader, FileBasedImage\n+from ..nifti2 import Nifti2Image\n+\n+\n+CIFTI_MAP_TYPES = ('CIFTI_INDEX_TYPE_BRAIN_MODELS',\n+                   'CIFTI_INDEX_TYPE_PARCELS',\n+                   'CIFTI_INDEX_TYPE_SERIES',\n+                   'CIFTI_INDEX_TYPE_SCALARS',\n+                   'CIFTI_INDEX_TYPE_LABELS')\n+\n+CIFTI_MODEL_TYPES = ('CIFTI_MODEL_TYPE_SURFACE',\n+                     'CIFTI_MODEL_TYPE_VOXELS')\n+\n+CIFTI_SERIESUNIT_TYPES = ('SECOND',\n+                          'HERTZ',\n+                          'METER',\n+                          'RADIAN')\n+\n+CIFTI_BrainStructures = ('CIFTI_STRUCTURE_ACCUMBENS_LEFT',\n+                         'CIFTI_STRUCTURE_ACCUMBENS_RIGHT',\n+                         'CIFTI_STRUCTURE_ALL_WHITE_MATTER',\n+                         'CIFTI_STRUCTURE_ALL_GREY_MATTER',\n+                         'CIFTI_STRUCTURE_AMYGDALA_LEFT',\n+                         'CIFTI_STRUCTURE_AMYGDALA_RIGHT',\n+                         'CIFTI_STRUCTURE_BRAIN_STEM',\n+                         'CIFTI_STRUCTURE_CAUDATE_LEFT',\n+                         'CIFTI_STRUCTURE_CAUDATE_RIGHT',\n+                         'CIFTI_STRUCTURE_CEREBELLAR_WHITE_MATTER_LEFT',\n+                         'CIFTI_STRUCTURE_CEREBELLAR_WHITE_MATTER_RIGHT',\n+                         'CIFTI_STRUCTURE_CEREBELLUM',\n+                         'CIFTI_STRUCTURE_CEREBELLUM_LEFT',\n+                         'CIFTI_STRUCTURE_CEREBELLUM_RIGHT',\n+                         'CIFTI_STRUCTURE_CEREBRAL_WHITE_MATTER_LEFT',\n+                         'CIFTI_STRUCTURE_CEREBRAL_WHITE_MATTER_RIGHT',\n+                         'CIFTI_STRUCTURE_CORTEX',\n+                         'CIFTI_STRUCTURE_CORTEX_LEFT',\n+                         'CIFTI_STRUCTURE_CORTEX_RIGHT',\n+                         'CIFTI_STRUCTURE_DIENCEPHALON_VENTRAL_LEFT',\n+                         'CIFTI_STRUCTURE_DIENCEPHALON_VENTRAL_RIGHT',\n+                         'CIFTI_STRUCTURE_HIPPOCAMPUS_LEFT',\n+                         'CIFTI_STRUCTURE_HIPPOCAMPUS_RIGHT',\n+                         'CIFTI_STRUCTURE_OTHER',\n+                         'CIFTI_STRUCTURE_OTHER_GREY_MATTER',\n+                         'CIFTI_STRUCTURE_OTHER_WHITE_MATTER',\n+                         'CIFTI_STRUCTURE_PALLIDUM_LEFT',\n+                         'CIFTI_STRUCTURE_PALLIDUM_RIGHT',\n+                         'CIFTI_STRUCTURE_PUTAMEN_LEFT',\n+                         'CIFTI_STRUCTURE_PUTAMEN_RIGHT',\n+                         'CIFTI_STRUCTURE_THALAMUS_LEFT',\n+                         'CIFTI_STRUCTURE_THALAMUS_RIGHT')\n+\n+\n+def _value_if_klass(val, klass, none_ok=True):\n+    if none_ok and val is None:\n+        return val\n+    elif isinstance(val, klass):\n+        return val\n+    else:\n+        raise ValueError('Not a valid %s instance.' % klass.__name__)\n+\n+\n+def _value_or_make_klass(val, klass):\n+    if val is None:\n+        return klass()\n+    else:\n+        return _value_if_klass(val, klass)\n+\n+\n+def _underscore(string):\n+    \"\"\" Convert a string from camelcase to underscored \"\"\"\n+    string = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\\1_\\2', string)\n+    return re.sub(r'([a-z0-9])([A-Z])', r'\\1_\\2', string).lower()\n+\n+\n+class Cifti2MetaData(xml.XmlSerializable):\n+    \"\"\" A list of key-value pairs stored in the list self.data \"\"\"\n+\n+    def __init__(self, nvpair=None):\n+        self.data = []\n+        self.add_metadata(nvpair)\n+\n+    def _add_remove_metadata(self, metadata, func):\n+        pairs = []\n+        if isinstance(metadata, dict):\n+            pairs = metadata.items()\n+        elif isinstance(metadata, (list, tuple)):\n+            if not isinstance(metadata[0], string_types):\n+                for item in metadata:\n+                    self._add_remove_metadata(item, func)\n+                return\n+            elif len(metadata) == 2:\n+                pairs = [tuple((metadata[0], metadata[1]))]\n+            else:\n+                raise ValueError('nvpair must be a 2-list or 2-tuple')\n+        else:\n+            raise ValueError('nvpair input must be a list, tuple or dict')\n+\n+        for pair in pairs:\n+            if func == 'add':\n+                if pair not in self.data:\n+                    self.data.append(pair)\n+            elif func == 'remove':\n+                self.data.remove(pair)\n+            else:\n+                raise ValueError('Unknown func %s' % func)\n+\n+    def add_metadata(self, metadata):\n+        \"\"\"Add metadata key-value pairs\n+\n+        This allows storing multiple keys with the same name but different\n+        values.\n+\n+\n+        Parameters\n+        ----------\n+        metadata : 2-List, 2-Tuple, Dictionary, List[2-List or 2-Tuple]\n+                     Tuple[2-List or 2-Tuple]\n+\n+        Returns\n+        -------\n+        None\n+\n+        \"\"\"\n+        if metadata is None:\n+            return\n+        self._add_remove_metadata(metadata, 'add')\n+\n+    def remove_metadata(self, metadata):\n+        if metadata is None:\n+            return\n+        self._add_remove_metadata(metadata, 'remove')\n+\n+    def _to_xml_element(self):\n+        metadata = xml.Element('MetaData')\n+\n+        for name_text, value_text in self.data:\n+            md = xml.SubElement(metadata, 'MD')\n+            name = xml.SubElement(md, 'Name')\n+            name.text = str(name_text)\n+            value = xml.SubElement(md, 'Value')\n+            assert(value_text is not None)\n+            value.text = str(value_text)\n+        return metadata\n+\n+\n+class Cifti2LabelTable(xml.XmlSerializable):\n+\n+    def __init__(self):\n+        self.labels = []\n+\n+    @property\n+    def num_labels(self):\n+        return len(self.labels)\n+\n+    def get_labels_as_dict(self):\n+        self.labels_as_dict = {}\n+        for ele in self.labels:\n+            self.labels_as_dict[ele.key] = ele.label\n+        return self.labels_as_dict\n+\n+    def _to_xml_element(self):\n+        assert len(self.labels) > 0\n+        labeltable = xml.Element('LabelTable')\n+        for ele in self.labels:\n+            labeltable.append(ele._to_xml_element())\n+        return labeltable\n+\n+    def print_summary(self):\n+        print(self.get_labels_as_dict())\n+\n+\n+class Cifti2Label(xml.XmlSerializable):\n+\n+    def __init__(self, key=0, label='', red=None,\n+                 green=None, blue=None, alpha=None):\n+        self.key = key\n+        self.label = label\n+        self.red = red\n+        self.green = green\n+        self.blue = blue\n+        self.alpha = alpha\n+\n+    @property\n+    def rgba(self):\n+        \"\"\" Returns RGBA as tuple \"\"\"\n+        return (self.red, self.green, self.blue, self.alpha)\n+\n+    def _to_xml_element(self):\n+        lab = xml.Element('Label')\n+        lab.attrib['Key'] = str(self.key)\n+        lab.text = str(self.label)\n+        if self.red is not None:\n+            lab.attrib['Red'] = str(self.red)\n+        if self.green is not None:\n+            lab.attrib['Green'] = str(self.green)\n+        if self.blue is not None:\n+            lab.attrib['Blue'] = str(self.blue)\n+        if self.alpha is not None:\n+            lab.attrib['Alpha'] = str(self.alpha)\n+        return lab\n+\n+\n+class Cifti2NamedMap(xml.XmlSerializable):\n+    \"\"\"Class for Named Map\"\"\"\n+    map_name = str\n+\n+    def __init__(self, map_name=None, metadata=None, label_table=None):\n+        self.map_name = map_name\n+        self.metadata = metadata  # _value_or_make_klass(metadata, Cifti2MetaData)\n+        self.label_table = label_table  # _value_or_make_klass(label_table, Cifti2LabelTable)\n+\n+    @property\n+    def metadata(self):\n+        return self._metadata\n+\n+    @metadata.setter\n+    def metadata(self, metadata):\n+        \"\"\" Set the metadata for this NamedMap\n+\n+        Parameters\n+        ----------\n+        meta : Cifti2MetaData\n+\n+        Returns\n+        -------\n+        None\n+        \"\"\"\n+        self._metadata = _value_if_klass(metadata, Cifti2MetaData)\n+\n+    @property\n+    def label_table(self):\n+        return self._label_table\n+\n+    @label_table.setter\n+    def label_table(self, label_table):\n+        \"\"\" Set the label_table for this NamedMap\n+\n+        Parameters\n+        ----------\n+        label_table : Cifti2LabelTable\n+\n+        Returns\n+        -------\n+        None\n+        \"\"\"\n+        self._label_table = _value_if_klass(label_table, Cifti2LabelTable)\n+\n+    def _to_xml_element(self):\n+        named_map = xml.Element('NamedMap')\n+        if self.metadata:\n+            named_map.append(self.metadata._to_xml_element())\n+        if self.label_table:\n+            named_map.append(self.label_table._to_xml_element())\n+        map_name = xml.SubElement(named_map, 'MapName')\n+        map_name.text = self.map_name\n+        return named_map\n+\n+\n+class Cifti2Surface(xml.XmlSerializable):\n+    \"\"\"Class for Surface \"\"\"\n+    # brainStructure = str\n+    # surfaceNumberOfVertices = int\n+\n+    def __init__(self, brain_structure=None, surface_number_of_vertices=None):\n+        self.brain_structure = brain_structure\n+        self.surface_number_of_vertices = surface_number_of_vertices\n+\n+    def _to_xml_element(self):\n+        assert self.brain_structure is not None\n+        surf = xml.Element('Surface')\n+        surf.attrib['BrainStructure'] = str(self.brain_structure)\n+        surf.attrib['SurfaceNumberOfVertices'] = str(self.surface_number_of_vertices)\n+        return surf\n+\n+\n+class Cifti2VoxelIndicesIJK(xml.XmlSerializable):\n+    # indices = np.array\n+\n+    def __init__(self, indices=None):\n+        self.indices = indices\n+\n+    def _to_xml_element(self):\n+        assert self.indices is not None\n+        vox_ind = xml.Element('VoxelIndicesIJK')\n+        if self.indices is not None:\n+            vox_ind.text = ''\n+            for row in self.indices:\n+                vox_ind.text += ' '.join(row.astype(str).tolist()) + '\\n'\n+        return vox_ind\n+\n+\n+class Cifti2Vertices(xml.XmlSerializable):\n+\n+    # brain_structure = str\n+    # vertices = np.array\n+\n+    def __init__(self, brain_structure=None, vertices=None):\n+        self.vertices = vertices\n+        self.brain_structure = brain_structure\n+\n+    def _to_xml_element(self):\n+        assert self.vertices is not None\n+        vertices = xml.Element('Vertices')\n+        vertices.attrib['BrainStructure'] = str(self.brain_structure)\n+\n+        if self.vertices is not None:\n+            vertices.text = ' '.join(self.vertices.astype(str).tolist())\n+        return vertices\n+\n+\n+class Cifti2Parcel(object):\n+    \"\"\"Class for Parcel\"\"\"\n+    # name = str\n+\n+    def __init__(self, name=None, voxel_indices_ijk=None, vertices=None):\n+        self.name = name\n+        self.voxel_indices_ijk = voxel_indices_ijk\n+        self.vertices = vertices if vertices is not None else []\n+\n+    @property\n+    def voxel_indices_ijk(self):\n+        return self._voxel_indices_ijk\n+\n+    @voxel_indices_ijk.setter\n+    def voxel_indices_ijk(self, value):\n+        self._voxel_indices_ijk = _value_if_klass(value, Cifti2VoxelIndicesIJK)\n+\n+    def add_cifti_vertices(self, vertices):\n+        \"\"\" Adds a vertices to the Cifti2Parcel\n+\n+        Parameters\n+        ----------\n+        vertices : Cifti2Vertices\n+        \"\"\"\n+        if not isinstance(vertices, Cifti2Vertices):\n+            raise TypeError(\"Not a valid Cifti2Vertices instance\")\n+        self.vertices.append(vertices)\n+\n+    def remove_cifti2_vertices(self, ith):\n+        \"\"\" Removes the ith vertices element from the Cifti2Parcel \"\"\"\n+        self.vertices.pop(ith)\n+\n+    def _to_xml_element(self):\n+        assert self.name is not None\n+        parcel = xml.Element('Parcel')\n+        parcel.attrib['Name'] = str(self.name)\n+        if self.voxel_indices_ijk:\n+            parcel.append(self.voxel_indices_ijk._to_xml_element())\n+        for vertex in self.vertices:\n+            parcel.append(vertex._to_xml_element())\n+        return parcel\n+\n+\n+class Cifti2TransformationMatrixVoxelIndicesIJKtoXYZ(object):\n+\n+    # meterExponent = int\n+    # matrix = np.array\n+\n+    def __init__(self, meter_exponent=None, matrix=None):\n+        self.meter_exponent = meter_exponent\n+        self.matrix = matrix\n+\n+    def _to_xml_element(self):\n+        assert self.matrix is not None\n+        trans = xml.Element('TransformationMatrixVoxelIndicesIJKtoXYZ')\n+        trans.attrib['MeterExponent'] = str(self.meter_exponent)\n+        if self.matrix is not None:\n+            trans.text = ''\n+            for row in self.matrix:\n+                trans.text += ' '.join(['%.10f' % val for val in row]) + \"\\n\"\n+        return trans\n+\n+\n+class Cifti2Volume(object):\n+\n+    # volumeDimensions = np.array\n+    # transformationMatrixVoxelIndicesIJKtoXYZ = np.array\n+\n+    def __init__(self, volume_dimensions=None, transform_matrix=None):\n+        self.volume_dimensions = volume_dimensions\n+        self.transformation_matrix_voxel_indices_ijk_to_xyz = transform_matrix\n+\n+    def _to_xml_element(self):\n+        assert self.volume_dimensions is not None\n+        volume = xml.Element('Volume')\n+        volume.attrib['VolumeDimensions'] = ','.join(\n+            [str(val) for val in self.volume_dimensions])\n+        volume.append(self.transformation_matrix_voxel_indices_ijk_to_xyz._to_xml_element())\n+        return volume\n+\n+\n+class Cifti2VertexIndices(object):\n+    # indices = np.array\n+\n+    def __init__(self, indices=None):\n+        self.indices = indices\n+\n+    def _to_xml_element(self):\n+        assert self.indices is not None\n+        vert_indices = xml.Element('VertexIndices')\n+        vert_indices.text = ' '.join(self.indices.astype(str).tolist())\n+        return vert_indices\n+\n+\n+class Cifti2BrainModel(object):\n+\n+    # index_offset = int\n+    # index_count = int\n+    # model_type = str\n+    # brain_structure = str\n+    # surface_number_of_vertices = int\n+    # voxel_indices_ijk = np.array\n+    # vertex_indices = np.array\n+\n+    def __init__(self, index_offset=None, index_count=None, model_type=None,\n+                 brain_structure=None, n_surface_vertices=None,\n+                 voxel_indices_ijk=None, vertex_indices=None):\n+        self.index_offset = index_offset\n+        self.index_count = index_count\n+        self.model_type = model_type\n+        self.brain_structure = brain_structure\n+        self.surface_number_of_vertices = n_surface_vertices\n+\n+        self.voxel_indices_ijk = voxel_indices_ijk\n+        self.vertex_indices = vertex_indices\n+\n+    @property\n+    def voxel_indices_ijk(self):\n+        return self._voxel_indices_ijk\n+\n+    @voxel_indices_ijk.setter\n+    def voxel_indices_ijk(self, value):\n+        self._voxel_indices_ijk = _value_if_klass(value, Cifti2VoxelIndicesIJK)\n+\n+    @property\n+    def vertex_indices(self):\n+        return self._vertex_indices\n+\n+    @vertex_indices.setter\n+    def vertex_indices(self, value):\n+        self._vertex_indices = _value_if_klass(value, Cifti2VertexIndices)\n+\n+    def _to_xml_element(self):\n+        brain_model = xml.Element('BrainModel')\n+\n+        for key in ['IndexOffset', 'IndexCount', 'ModelType', 'BrainStructure',\n+                    'SurfaceNumberOfVertices']:\n+            attr = _underscore(key)\n+            value = getattr(self, attr)\n+            if value is not None:\n+                brain_model.attrib[key] = str(value)\n+        if self.voxel_indices_ijk:\n+            brain_model.append(self.voxel_indices_ijk._to_xml_element())\n+        if self.vertex_indices:\n+            brain_model.append(self.vertex_indices._to_xml_element())\n+        return brain_model\n+\n+\n+class Cifti2MatrixIndicesMap(object):\n+    \"\"\"Class for Matrix Indices Map\n+\n+    Provides a mapping between matrix indices and their interpretation.\n+    \"\"\"\n+    # applies_to_matrix_dimension = list\n+    # indices_map_to_data_type = str\n+    # number_of_series_points = int\n+    # series_exponent = int\n+    # series_start = float\n+    # series_step = float\n+    # series_unit = str\n+\n+    def __init__(self, applies_to_matrix_dimension,\n+                 indices_map_to_data_type,\n+                 number_of_series_points=None,\n+                 series_exponent=None,\n+                 series_start=None,\n+                 series_step=None,\n+                 series_unit=None,\n+                 brain_models=None,\n+                 named_maps=None,\n+                 parcels=None,\n+                 surfaces=None,\n+                 volume=None):\n+        self.applies_to_matrix_dimension = applies_to_matrix_dimension\n+        self.indices_map_to_data_type = indices_map_to_data_type\n+        self.number_of_series_points = number_of_series_points\n+        self.series_exponent = series_exponent\n+        self.series_start = series_start\n+        self.series_step = series_step\n+        self.series_unit = series_unit\n+        self.brain_models = brain_models if brain_models is not None else []\n+        self.named_maps = named_maps if named_maps is not None else []\n+        self.parcels = parcels if parcels is not None else []\n+        self.surfaces = surfaces if surfaces is not None else []\n+        self.volume = volume  # _value_or_make_klass(volume, Cifti2Volume)\n+\n+    def add_cifti_brain_model(self, brain_model):\n+        \"\"\" Adds a brain model to the Cifti2MatrixIndicesMap\n+\n+        Parameters\n+        ----------\n+        brain_model : Cifti2BrainModel\n+        \"\"\"\n+        if not isinstance(brain_model, Cifti2BrainModel):\n+            raise TypeError(\"Not a valid Cifti2BrainModel instance\")\n+        self.brain_models.append(brain_model)\n+\n+    def remove_cifti_brain_model(self, ith):\n+        \"\"\" Removes the ith brain model element from the Cifti2MatrixIndicesMap \"\"\"\n+        self.brain_models.pop(ith)\n+\n+    def add_cifti_named_map(self, named_map):\n+        \"\"\" Adds a named map to the Cifti2MatrixIndicesMap\n+\n+        Parameters\n+        ----------\n+        named_map : Cifti2MatrixIndicesMap\n+        \"\"\"\n+        if isinstance(named_map, Cifti2MatrixIndicesMap):\n+            raise TypeError(\"Not a valid Cifti2MatrixIndicesMap instance\")\n+        self.named_maps.append(named_map)\n+\n+    def remove_cifti_named_map(self, ith):\n+        \"\"\" Removes the ith named_map element from the Cifti2MatrixIndicesMap \"\"\"\n+        self.named_maps.pop(ith)\n+\n+    def add_cifti_parcel(self, parcel):\n+        \"\"\" Adds a parcel to the Cifti2MatrixIndicesMap\n+\n+        Parameters\n+        ----------\n+        parcel : Cifti2Parcel\n+        \"\"\"\n+        if not isinstance(parcel, Cifti2Parcel):\n+            raise TypeError(\"Not a valid Cifti2Parcel instance\")\n+        self.parcels.append(parcel)\n+\n+    def remove_cifti2_parcel(self, ith):\n+        \"\"\" Removes the ith parcel element from the Cifti2MatrixIndicesMap \"\"\"\n+        self.parcels.pop(ith)\n+\n+    def add_cifti_surface(self, surface):\n+        \"\"\" Adds a surface to the Cifti2MatrixIndicesMap\n+\n+        Parameters\n+        ----------\n+        surface : Cifti2Surface\n+        \"\"\"\n+        if not isinstance(surface, Cifti2Surface):\n+            raise TypeError(\"Not a valid Cifti2Surface instance\")\n+        self.surfaces.append(surface)\n+\n+    def remove_cifti2_surface(self, ith):\n+        \"\"\" Removes the ith surface element from the Cifti2MatrixIndicesMap \"\"\"\n+        self.surfaces.pop(ith)\n+\n+    def set_cifti2_volume(self, volume):\n+        \"\"\" Adds a volume to the Cifti2MatrixIndicesMap\n+\n+        Parameters\n+        ----------\n+        volume : Cifti2Volume\n+        \"\"\"\n+        if not isinstance(volume, Cifti2Volume):\n+            raise TypeError(\"Not a valid Cifti2Volume instance\")\n+        self.volume = volume\n+\n+    def remove_cifti2_volume(self):\n+        \"\"\" Removes the volume element from the Cifti2MatrixIndicesMap \"\"\"\n+        self.volume = None\n+\n+    def _to_xml_element(self):\n+        assert self.applies_to_matrix_dimension is not None\n+\n+        mat_ind_map = xml.Element('MatrixIndicesMap')\n+        dims_as_strings = [str(dim) for dim in self.applies_to_matrix_dimension]\n+        mat_ind_map.attrib['AppliesToMatrixDimension'] = ','.join(dims_as_strings)\n+        for key in ['IndicesMapToDataType', 'NumberOfSeriesPoints', 'SeriesExponent',\n+                    'SeriesStart', 'SeriesStep', 'SeriesUnit']:\n+            attr = _underscore(key)\n+            value = getattr(self, attr)\n+            if value is not None:\n+                mat_ind_map.attrib[key] = str(value)\n+        for named_map in self.named_maps:\n+            assert named_map._to_xml_element() is not None\n+            mat_ind_map.append(named_map._to_xml_element())\n+        for surface in self.surfaces:\n+            assert surface._to_xml_element() is not None\n+            mat_ind_map.append(surface._to_xml_element())\n+        for parcel in self.parcels:\n+            assert parcel._to_xml_element() is not None\n+            mat_ind_map.append(parcel._to_xml_element())\n+        if self.volume:\n+            assert self.volume._to_xml_element() is not None\n+            mat_ind_map.append(self.volume._to_xml_element())\n+        for model in self.brain_models:\n+            assert model._to_xml_element() is not None\n+            mat_ind_map.append(model._to_xml_element())\n+        return mat_ind_map\n+\n+\n+class Cifti2Matrix(xml.XmlSerializable):\n+    def __init__(self, meta=None, mims=None):\n+        self.mims = mims if mims is not None else []\n+        self.metadata = meta\n+\n+    @property\n+    def metadata(self):\n+        return self._meta\n+\n+    @metadata.setter\n+    def metadata(self, meta):\n+        \"\"\" Set the metadata for this Cifti2Header\n+\n+        Parameters\n+        ----------\n+        meta : Cifti2MetaData\n+\n+        Returns\n+        -------\n+        None\n+        \"\"\"\n+        if meta is not None and not isinstance(meta, Cifti2MetaData):\n+            raise TypeError(\"Not a valid Cifti2MetaData instance\")\n+        self._meta = meta\n+\n+    def add_cifti_matrix_indices_map(self, mim):\n+        \"\"\" Adds a matrix indices map to the Cifti2Matrix\n+\n+        Parameters\n+        ----------\n+        mim : Cifti2MatrixIndicesMap\n+        \"\"\"\n+        if isinstance(mim, Cifti2MatrixIndicesMap):\n+            self.mims.append(mim)\n+        else:\n+            raise TypeError(\"Not a valid Cifti2MatrixIndicesMap instance\")\n+\n+    def remove_cifti2_matrix_indices_map(self, ith):\n+        \"\"\" Removes the ith matrix indices map element from the Cifti2Matrix \"\"\"\n+        self.mims.pop(ith)\n+\n+    def _to_xml_element(self):\n+        assert len(self.mims) != 0 or self.metadata is not None\n+        mat = xml.Element('Matrix')\n+        if self.metadata:\n+            mat.append(self.metadata._to_xml_element())\n+        for mim in self.mims:\n+            mat.append(mim._to_xml_element())\n+        return mat\n+\n+\n+class Cifti2Header(FileBasedHeader, xml.XmlSerializable):\n+    ''' Class for Cifti2 header extension '''\n+\n+    # version = str\n+\n+    def __init__(self, matrix=None, version=\"2.0\"):\n+        FileBasedHeader.__init__(self)\n+        xml.XmlSerializable.__init__(self)\n+        if matrix is None:\n+            self.matrix = Cifti2Matrix()\n+        else:\n+            self.matrix = matrix\n+        self.version = version\n+\n+    def _to_xml_element(self):\n+        cifti = xml.Element('CIFTI')\n+        cifti.attrib['Version'] = str(self.version)\n+        mat_xml = self.matrix._to_xml_element()\n+        if mat_xml is not None:\n+            cifti.append(mat_xml)\n+        return cifti\n+\n+    @classmethod\n+    def from_header(klass, header=None):\n+        if header is None:\n+            return klass()\n+        if type(header) == klass:\n+            return header.copy()\n+        raise ValueError('header is not a Cifti2Header')\n+\n+    @classmethod\n+    def may_contain_header(klass, binaryblock):\n+        from .parse_cifti2_fast import _Cifti2AsNiftiHeader\n+        return _Cifti2AsNiftiHeader.may_contain_header(binaryblock)\n+\n+\n+class Cifti2Image(FileBasedImage):\n+    # It is a Nifti2Image, but because Nifti2Image object\n+    # contains both the *format* and the assumption that it's\n+    # a spatial image, we can't inherit directly.\n+    header_class = Cifti2Header\n+    valid_exts = Nifti2Image.valid_exts\n+    files_types = Nifti2Image.files_types\n+    makeable = False\n+    rw = True\n+\n+    def __init__(self, data=None, header=None, nifti_header=None):\n+        self._header = header or Cifti2Header()\n+        self.data = data\n+        self.extra = nifti_header\n+\n+    def get_data(self):\n+        return self.data\n+\n+    @classmethod\n+    def from_file_map(klass, file_map):\n+        \"\"\" Load a Cifti2 image from a file_map\n+\n+        Parameters\n+        file_map : string\n+\n+        Returns\n+        -------\n+        img : Cifti2Image\n+            Returns a Cifti2Image\n+         \"\"\"\n+        from .parse_cifti2_fast import _Cifti2AsNiftiImage, Cifti2Extension\n+        nifti_img = _Cifti2AsNiftiImage.from_file_map(file_map)\n+\n+        # Get cifti2 header\n+        cifti_header = reduce(lambda accum, item:\n+                              item.get_content()\n+                              if isinstance(item, Cifti2Extension)\n+                              else accum,\n+                              nifti_img.get_header().extensions or [],\n+                              None)\n+        if cifti_header is None:\n+            raise ValueError('Nifti2 header does not contain a CIFTI2 '\n+                             'extension')\n+\n+        # Construct cifti image\n+        cifti_img = Cifti2Image(data=np.squeeze(nifti_img.get_data()),","path":"nibabel/cifti2/cifti2.py","position":761,"original_position":761,"commit_id":"ca37d4c6e87aca47d4f113d13dd88af6c5df1c43","original_commit_id":"ca37d4c6e87aca47d4f113d13dd88af6c5df1c43","user":{"login":"matthew-brett","id":67612,"avatar_url":"https://avatars.githubusercontent.com/u/67612?v=3","gravatar_id":"","url":"https://api.github.com/users/matthew-brett","html_url":"https://github.com/matthew-brett","followers_url":"https://api.github.com/users/matthew-brett/followers","following_url":"https://api.github.com/users/matthew-brett/following{/other_user}","gists_url":"https://api.github.com/users/matthew-brett/gists{/gist_id}","starred_url":"https://api.github.com/users/matthew-brett/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/matthew-brett/subscriptions","organizations_url":"https://api.github.com/users/matthew-brett/orgs","repos_url":"https://api.github.com/users/matthew-brett/repos","events_url":"https://api.github.com/users/matthew-brett/events{/privacy}","received_events_url":"https://api.github.com/users/matthew-brett/received_events","type":"User","site_admin":false},"body":"It doesn't matter which dimensions are 1 for cifti?   Could be 10, 1, 1 or 1, 10, 1 or 1, 1, 10?","created_at":"2016-03-14T23:55:50Z","updated_at":"2016-03-14T23:55:50Z","html_url":"https://github.com/nipy/nibabel/pull/249#discussion_r56096529","pull_request_url":"https://api.github.com/repos/nipy/nibabel/pulls/249","_links":{"self":{"href":"https://api.github.com/repos/nipy/nibabel/pulls/comments/56096529"},"html":{"href":"https://github.com/nipy/nibabel/pull/249#discussion_r56096529"},"pull_request":{"href":"https://api.github.com/repos/nipy/nibabel/pulls/249"}}},"pull_request":{"url":"https://api.github.com/repos/nipy/nibabel/pulls/249","id":19220564,"html_url":"https://github.com/nipy/nibabel/pull/249","diff_url":"https://github.com/nipy/nibabel/pull/249.diff","patch_url":"https://github.com/nipy/nibabel/pull/249.patch","issue_url":"https://api.github.com/repos/nipy/nibabel/issues/249","number":249,"state":"open","locked":false,"title":"WIP: CIFTI2 spec reader and writer","user":{"login":"satra","id":184063,"avatar_url":"https://avatars.githubusercontent.com/u/184063?v=3","gravatar_id":"","url":"https://api.github.com/users/satra","html_url":"https://github.com/satra","followers_url":"https://api.github.com/users/satra/followers","following_url":"https://api.github.com/users/satra/following{/other_user}","gists_url":"https://api.github.com/users/satra/gists{/gist_id}","starred_url":"https://api.github.com/users/satra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/satra/subscriptions","organizations_url":"https://api.github.com/users/satra/orgs","repos_url":"https://api.github.com/users/satra/repos","events_url":"https://api.github.com/users/satra/events{/privacy}","received_events_url":"https://api.github.com/users/satra/received_events","type":"User","site_admin":false},"body":"- [x] CIFTI2 reader\r\n- [x] CIFTI2 writer\r\n- [x] tests\r\n- [x] integration with nifti2","created_at":"2014-08-01T16:41:50Z","updated_at":"2016-03-14T23:55:50Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f4e4cb51e27b6213520c425b9b3390196d175061","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/nipy/nibabel/pulls/249/commits","review_comments_url":"https://api.github.com/repos/nipy/nibabel/pulls/249/comments","review_comment_url":"https://api.github.com/repos/nipy/nibabel/pulls/comments{/number}","comments_url":"https://api.github.com/repos/nipy/nibabel/issues/249/comments","statuses_url":"https://api.github.com/repos/nipy/nibabel/statuses/ca37d4c6e87aca47d4f113d13dd88af6c5df1c43","head":{"label":"satra:enh/cifti2","ref":"enh/cifti2","sha":"ca37d4c6e87aca47d4f113d13dd88af6c5df1c43","user":{"login":"satra","id":184063,"avatar_url":"https://avatars.githubusercontent.com/u/184063?v=3","gravatar_id":"","url":"https://api.github.com/users/satra","html_url":"https://github.com/satra","followers_url":"https://api.github.com/users/satra/followers","following_url":"https://api.github.com/users/satra/following{/other_user}","gists_url":"https://api.github.com/users/satra/gists{/gist_id}","starred_url":"https://api.github.com/users/satra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/satra/subscriptions","organizations_url":"https://api.github.com/users/satra/orgs","repos_url":"https://api.github.com/users/satra/repos","events_url":"https://api.github.com/users/satra/events{/privacy}","received_events_url":"https://api.github.com/users/satra/received_events","type":"User","site_admin":false},"repo":{"id":811176,"name":"nibabel","full_name":"satra/nibabel","owner":{"login":"satra","id":184063,"avatar_url":"https://avatars.githubusercontent.com/u/184063?v=3","gravatar_id":"","url":"https://api.github.com/users/satra","html_url":"https://github.com/satra","followers_url":"https://api.github.com/users/satra/followers","following_url":"https://api.github.com/users/satra/following{/other_user}","gists_url":"https://api.github.com/users/satra/gists{/gist_id}","starred_url":"https://api.github.com/users/satra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/satra/subscriptions","organizations_url":"https://api.github.com/users/satra/orgs","repos_url":"https://api.github.com/users/satra/repos","events_url":"https://api.github.com/users/satra/events{/privacy}","received_events_url":"https://api.github.com/users/satra/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/satra/nibabel","description":"Python package to access a cacophony of neuro-imaging file formats","fork":true,"url":"https://api.github.com/repos/satra/nibabel","forks_url":"https://api.github.com/repos/satra/nibabel/forks","keys_url":"https://api.github.com/repos/satra/nibabel/keys{/key_id}","collaborators_url":"https://api.github.com/repos/satra/nibabel/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/satra/nibabel/teams","hooks_url":"https://api.github.com/repos/satra/nibabel/hooks","issue_events_url":"https://api.github.com/repos/satra/nibabel/issues/events{/number}","events_url":"https://api.github.com/repos/satra/nibabel/events","assignees_url":"https://api.github.com/repos/satra/nibabel/assignees{/user}","branches_url":"https://api.github.com/repos/satra/nibabel/branches{/branch}","tags_url":"https://api.github.com/repos/satra/nibabel/tags","blobs_url":"https://api.github.com/repos/satra/nibabel/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/satra/nibabel/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/satra/nibabel/git/refs{/sha}","trees_url":"https://api.github.com/repos/satra/nibabel/git/trees{/sha}","statuses_url":"https://api.github.com/repos/satra/nibabel/statuses/{sha}","languages_url":"https://api.github.com/repos/satra/nibabel/languages","stargazers_url":"https://api.github.com/repos/satra/nibabel/stargazers","contributors_url":"https://api.github.com/repos/satra/nibabel/contributors","subscribers_url":"https://api.github.com/repos/satra/nibabel/subscribers","subscription_url":"https://api.github.com/repos/satra/nibabel/subscription","commits_url":"https://api.github.com/repos/satra/nibabel/commits{/sha}","git_commits_url":"https://api.github.com/repos/satra/nibabel/git/commits{/sha}","comments_url":"https://api.github.com/repos/satra/nibabel/comments{/number}","issue_comment_url":"https://api.github.com/repos/satra/nibabel/issues/comments{/number}","contents_url":"https://api.github.com/repos/satra/nibabel/contents/{+path}","compare_url":"https://api.github.com/repos/satra/nibabel/compare/{base}...{head}","merges_url":"https://api.github.com/repos/satra/nibabel/merges","archive_url":"https://api.github.com/repos/satra/nibabel/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/satra/nibabel/downloads","issues_url":"https://api.github.com/repos/satra/nibabel/issues{/number}","pulls_url":"https://api.github.com/repos/satra/nibabel/pulls{/number}","milestones_url":"https://api.github.com/repos/satra/nibabel/milestones{/number}","notifications_url":"https://api.github.com/repos/satra/nibabel/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/satra/nibabel/labels{/name}","releases_url":"https://api.github.com/repos/satra/nibabel/releases{/id}","deployments_url":"https://api.github.com/repos/satra/nibabel/deployments","created_at":"2010-08-01T21:53:24Z","updated_at":"2014-08-06T23:53:09Z","pushed_at":"2016-03-02T20:21:35Z","git_url":"git://github.com/satra/nibabel.git","ssh_url":"git@github.com:satra/nibabel.git","clone_url":"https://github.com/satra/nibabel.git","svn_url":"https://github.com/satra/nibabel","homepage":"http://nipy.sourceforge.net/nibabel/","size":8562,"stargazers_count":1,"watchers_count":1,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":0,"forks":2,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"nipy:master","ref":"master","sha":"a7c652301a05c42c396a7552287a15f9f65fae29","user":{"login":"nipy","id":233707,"avatar_url":"https://avatars.githubusercontent.com/u/233707?v=3","gravatar_id":"","url":"https://api.github.com/users/nipy","html_url":"https://github.com/nipy","followers_url":"https://api.github.com/users/nipy/followers","following_url":"https://api.github.com/users/nipy/following{/other_user}","gists_url":"https://api.github.com/users/nipy/gists{/gist_id}","starred_url":"https://api.github.com/users/nipy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nipy/subscriptions","organizations_url":"https://api.github.com/users/nipy/orgs","repos_url":"https://api.github.com/users/nipy/repos","events_url":"https://api.github.com/users/nipy/events{/privacy}","received_events_url":"https://api.github.com/users/nipy/received_events","type":"Organization","site_admin":false},"repo":{"id":791352,"name":"nibabel","full_name":"nipy/nibabel","owner":{"login":"nipy","id":233707,"avatar_url":"https://avatars.githubusercontent.com/u/233707?v=3","gravatar_id":"","url":"https://api.github.com/users/nipy","html_url":"https://github.com/nipy","followers_url":"https://api.github.com/users/nipy/followers","following_url":"https://api.github.com/users/nipy/following{/other_user}","gists_url":"https://api.github.com/users/nipy/gists{/gist_id}","starred_url":"https://api.github.com/users/nipy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nipy/subscriptions","organizations_url":"https://api.github.com/users/nipy/orgs","repos_url":"https://api.github.com/users/nipy/repos","events_url":"https://api.github.com/users/nipy/events{/privacy}","received_events_url":"https://api.github.com/users/nipy/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/nipy/nibabel","description":"Python package to access a cacophony of neuro-imaging file formats","fork":false,"url":"https://api.github.com/repos/nipy/nibabel","forks_url":"https://api.github.com/repos/nipy/nibabel/forks","keys_url":"https://api.github.com/repos/nipy/nibabel/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nipy/nibabel/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nipy/nibabel/teams","hooks_url":"https://api.github.com/repos/nipy/nibabel/hooks","issue_events_url":"https://api.github.com/repos/nipy/nibabel/issues/events{/number}","events_url":"https://api.github.com/repos/nipy/nibabel/events","assignees_url":"https://api.github.com/repos/nipy/nibabel/assignees{/user}","branches_url":"https://api.github.com/repos/nipy/nibabel/branches{/branch}","tags_url":"https://api.github.com/repos/nipy/nibabel/tags","blobs_url":"https://api.github.com/repos/nipy/nibabel/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nipy/nibabel/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nipy/nibabel/git/refs{/sha}","trees_url":"https://api.github.com/repos/nipy/nibabel/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nipy/nibabel/statuses/{sha}","languages_url":"https://api.github.com/repos/nipy/nibabel/languages","stargazers_url":"https://api.github.com/repos/nipy/nibabel/stargazers","contributors_url":"https://api.github.com/repos/nipy/nibabel/contributors","subscribers_url":"https://api.github.com/repos/nipy/nibabel/subscribers","subscription_url":"https://api.github.com/repos/nipy/nibabel/subscription","commits_url":"https://api.github.com/repos/nipy/nibabel/commits{/sha}","git_commits_url":"https://api.github.com/repos/nipy/nibabel/git/commits{/sha}","comments_url":"https://api.github.com/repos/nipy/nibabel/comments{/number}","issue_comment_url":"https://api.github.com/repos/nipy/nibabel/issues/comments{/number}","contents_url":"https://api.github.com/repos/nipy/nibabel/contents/{+path}","compare_url":"https://api.github.com/repos/nipy/nibabel/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nipy/nibabel/merges","archive_url":"https://api.github.com/repos/nipy/nibabel/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nipy/nibabel/downloads","issues_url":"https://api.github.com/repos/nipy/nibabel/issues{/number}","pulls_url":"https://api.github.com/repos/nipy/nibabel/pulls{/number}","milestones_url":"https://api.github.com/repos/nipy/nibabel/milestones{/number}","notifications_url":"https://api.github.com/repos/nipy/nibabel/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nipy/nibabel/labels{/name}","releases_url":"https://api.github.com/repos/nipy/nibabel/releases{/id}","deployments_url":"https://api.github.com/repos/nipy/nibabel/deployments","created_at":"2010-07-22T16:28:30Z","updated_at":"2016-03-04T04:54:44Z","pushed_at":"2016-03-14T23:28:18Z","git_url":"git://github.com/nipy/nibabel.git","ssh_url":"git@github.com:nipy/nibabel.git","clone_url":"https://github.com/nipy/nibabel.git","svn_url":"https://github.com/nipy/nibabel","homepage":"http://nipy.org/nibabel/","size":19944,"stargazers_count":102,"watchers_count":102,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":103,"mirror_url":null,"open_issues_count":68,"forks":103,"open_issues":68,"watchers":102,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/nipy/nibabel/pulls/249"},"html":{"href":"https://github.com/nipy/nibabel/pull/249"},"issue":{"href":"https://api.github.com/repos/nipy/nibabel/issues/249"},"comments":{"href":"https://api.github.com/repos/nipy/nibabel/issues/249/comments"},"review_comments":{"href":"https://api.github.com/repos/nipy/nibabel/pulls/249/comments"},"review_comment":{"href":"https://api.github.com/repos/nipy/nibabel/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/nipy/nibabel/pulls/249/commits"},"statuses":{"href":"https://api.github.com/repos/nipy/nibabel/statuses/ca37d4c6e87aca47d4f113d13dd88af6c5df1c43"}}}}