[med-svn] [ciftilib] 01/03: New upstream version 1.5.1

Ghislain Vaillant ghisvail-guest at moszumanska.debian.org
Wed Nov 9 08:26:26 UTC 2016


This is an automated email from the git hooks/post-receive script.

ghisvail-guest pushed a commit to branch debian/master
in repository ciftilib.

commit 649638d7c6d33cca935ad83e76ff7e246a2fc51a
Author: Ghislain Antony Vaillant <ghisvail at gmail.com>
Date:   Sat Nov 5 17:24:45 2016 +0000

    New upstream version 1.5.1
---
 .gitignore                                         |    2 +
 .travis.yml                                        |   34 +
 CMakeLists.txt                                     |  153 ++
 CiftiLib.pc.in                                     |   10 +
 Doxyfile.in                                        | 1516 +++++++++++++++++
 LICENSE                                            |   10 +
 README                                             |   30 +
 USAGE                                              |   25 +
 cmake/Modules/Findglib.cmake                       |   16 +
 cmake/Modules/Findglibmm.cmake                     |   28 +
 cmake/Modules/Findlibxml++.cmake                   |   32 +
 cmake/Modules/Findsigc++.cmake                     |   16 +
 cmake/Modules/UseDoxygen/CMakeLists.txt            |   16 +
 cmake/Modules/UseDoxygen/COPYING-CMAKE-SCRIPTS     |   22 +
 cmake/Modules/UseDoxygen/Doxyfile.in               | 1510 +++++++++++++++++
 cmake/Modules/UseDoxygen/UseDoxygen.cmake          |  144 ++
 cmake/Modules/UseDoxygen/cmake_uninstall.cmake.in  |   21 +
 cmake/scripts/testmd5.cmake                        |    5 +
 example/CMakeLists.txt                             |   83 +
 ...69.MyelinAndCorrThickness.32k_fs_LR.dscalar.nii |  Bin 0 -> 840936 bytes
 ...9.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii |  Bin 0 -> 837864 bytes
 ...9.MyelinAndCorrThickness.32k_fs_LR.ptseries.nii |  Bin 0 -> 139264 bytes
 ...nte69.parcellations_VGD11b.32k_fs_LR.dlabel.nii |  Bin 0 -> 1178640 bytes
 example/data/ODC_PDDL.rtf                          |  331 ++++
 example/data/README                                |   19 +
 example/data/ones.dscalar.nii                      |  Bin 0 -> 995912 bytes
 example/rewrite.cxx                                |   61 +
 example/xmlinfo.cxx                                |  106 ++
 mainpage.dox                                       |   40 +
 src/CMakeLists.txt                                 |   42 +
 src/Cifti/CMakeLists.txt                           |   40 +
 src/Cifti/CiftiBrainModelsMap.cxx                  | 1170 +++++++++++++
 src/Cifti/CiftiBrainModelsMap.h                    |  151 ++
 src/Cifti/CiftiLabelsMap.cxx                       |  469 ++++++
 src/Cifti/CiftiLabelsMap.h                         |   78 +
 src/Cifti/CiftiMappingType.cxx                     |   55 +
 src/Cifti/CiftiMappingType.h                       |   65 +
 src/Cifti/CiftiParcelsMap.cxx                      |  971 +++++++++++
 src/Cifti/CiftiParcelsMap.h                        |   99 ++
 src/Cifti/CiftiScalarsMap.cxx                      |  412 +++++
 src/Cifti/CiftiScalarsMap.h                        |   78 +
 src/Cifti/CiftiSeriesMap.cxx                       |  303 ++++
 src/Cifti/CiftiSeriesMap.h                         |  112 ++
 src/Cifti/CiftiVersion.cxx                         |  135 ++
 src/Cifti/CiftiVersion.h                           |   59 +
 src/Cifti/CiftiXML.cxx                             | 1045 ++++++++++++
 src/Cifti/CiftiXML.h                               |  120 ++
 src/Cifti/Label.cxx                                |  503 ++++++
 src/Cifti/Label.h                                  |  161 ++
 src/Cifti/LabelTable.cxx                           |  928 +++++++++++
 src/Cifti/LabelTable.h                             |  173 ++
 src/Cifti/MetaData.cxx                             |  478 ++++++
 src/Cifti/MetaData.h                               |  117 ++
 src/Cifti/StructureEnum.cxx                        |  669 ++++++++
 src/Cifti/StructureEnum.h                          |  173 ++
 src/Cifti/VolumeSpace.cxx                          |  536 ++++++
 src/Cifti/VolumeSpace.h                            |  156 ++
 src/CiftiFile.cxx                                  |  494 ++++++
 src/CiftiFile.h                                    |  133 ++
 src/Common/AString.cxx                             |  151 ++
 src/Common/AString.h                               |  122 ++
 src/Common/BinaryFile.cxx                          |  520 ++++++
 src/Common/BinaryFile.h                            |   85 +
 src/Common/ByteSwapping.h                          |   82 +
 src/Common/CMakeLists.txt                          |   31 +
 src/Common/CiftiAssert.h                           |   11 +
 src/Common/CiftiException.cxx                      |   98 ++
 src/Common/CiftiException.h                        |   62 +
 src/Common/CiftiMutex.h                            |  105 ++
 src/Common/Compact3DLookup.h                       |  101 ++
 src/Common/CompactLookup.h                         |  237 +++
 src/Common/FloatMatrix.cxx                         |  338 ++++
 src/Common/FloatMatrix.h                           |  126 ++
 src/Common/MathFunctions.cxx                       | 1713 ++++++++++++++++++++
 src/Common/MathFunctions.h                         |  313 ++++
 src/Common/MatrixFunctions.h                       |  708 ++++++++
 src/Common/MultiDimArray.h                         |  124 ++
 src/Common/MultiDimIterator.h                      |  157 ++
 src/Common/Vector3D.cxx                            |  197 +++
 src/Common/Vector3D.h                              |   72 +
 src/Common/VoxelIJK.h                              |   65 +
 src/Common/XmlAdapter.cxx                          |  194 +++
 src/Common/XmlAdapter.h                            |  166 ++
 src/Nifti/CMakeLists.txt                           |   10 +
 src/Nifti/NiftiHeader.cxx                          |  825 ++++++++++
 src/Nifti/NiftiHeader.h                            |   95 ++
 src/Nifti/nifti1.h                                 | 1468 +++++++++++++++++
 src/Nifti/nifti2.h                                 |  124 ++
 src/NiftiIO.cxx                                    |  134 ++
 src/NiftiIO.h                                      |  305 ++++
 90 files changed, 22894 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a39713e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.kdev4
+*.kate-swp
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e984710
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,34 @@
+language: cpp
+sudo: false
+
+cache:
+    - apt
+    - ccache
+
+addons:
+    apt:
+        packages:
+            - libqt4-dev
+            - libxml++2.6-dev
+            - libboost-dev
+            - libboost-filesystem-dev
+            - zlib1g-dev
+
+compiler:
+    - clang
+    - gcc
+
+env:
+    - IGNORE_QT=false SHARED=true
+    - IGNORE_QT=false SHARED=false
+    - IGNORE_QT=true SHARED=true
+    - IGNORE_QT=true SHARED=false
+
+before_install:
+    - mkdir ../build
+    - cd ../build
+
+script:
+    - cmake -D BUILD_SHARED_LIBS:BOOL=$SHARED -D IGNORE_QT:BOOL=$IGNORE_QT ../CiftiLib
+    - make -j 4
+    - ctest
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..77ce1a3
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,153 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+
+#UseDoxygen trips over a policy change in cmake 3, suppress the warning
+CMAKE_POLICY(VERSION 2.8.7)
+#the suggested version-type policy command doesn't shut this warning up, so set it manually
+IF (POLICY CMP0045)
+    CMAKE_POLICY(SET CMP0045 OLD)
+ENDIF (POLICY CMP0045)
+
+PROJECT(CiftiLib)
+
+SET(CIFTILIB_VERSION 1.5)
+
+#MSVC seems like the only compiler that chokes on -W -Wall
+IF (NOT MSVC)
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall")
+ENDIF (NOT MSVC)
+
+SET(IGNORE_QT FALSE CACHE BOOL "don't try to use QT")
+
+SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/" "${CMAKE_SOURCE_DIR}/cmake/Modules/UseDoxygen/")
+
+INCLUDE(UseDoxygen)
+
+#QT
+IF (NOT IGNORE_QT)
+    FIND_PACKAGE(Qt4 4.8.0 QUIET)
+    IF (QT_FOUND)
+        SET(QT_DONT_USE_QTGUI TRUE)
+        ADD_DEFINITIONS(-DCIFTILIB_USE_QT)
+        INCLUDE(${QT_USE_FILE})
+        SET(LIBS ${LIBS} ${QT_LIBRARIES})
+        #for pkg-config file
+        SET(CIFTILIB_PKGCONFIG_REQUIRES_LINE "Requires: QtCore >= 4.8.0 QtXml")
+        SET(CIFTILIB_PKGCONFIG_DEFINE "-DCIFTILIB_USE_QT")
+    ELSE (QT_FOUND)
+        FIND_PACKAGE(Qt5Core)
+        IF (Qt5Core_FOUND)
+            SET(QT_FOUND TRUE)
+            INCLUDE_DIRECTORIES(${Qt5Core_INCLUDE_DIRS})
+            SET(LIBS ${LIBS} Qt5::Core)
+            #whatever that means
+            ADD_DEFINITIONS(-DCIFTILIB_USE_QT)
+            SET(CIFTILIB_PKGCONFIG_REQUIRES_LINE "Requires: Qt5Core Qt5Xml")
+            SET(CIFTILIB_PKGCONFIG_DEFINE "-DCIFTILIB_USE_QT")
+        ENDIF (Qt5Core_FOUND)
+    ENDIF (QT_FOUND)
+ENDIF (NOT IGNORE_QT)
+
+#alternative to QT xml, string
+IF (NOT QT_FOUND)
+    FIND_PACKAGE(libxml++ 2.17.0 REQUIRED)
+    INCLUDE_DIRECTORIES(${libxml++_INCLUDE_DIRS})
+    SET(LIBS ${LIBS} ${libxml++_LIBRARIES})
+    ADD_DEFINITIONS(-DCIFTILIB_USE_XMLPP)
+    #for pkg-config file
+    SET(CIFTILIB_PKGCONFIG_REQUIRES_LINE "Requires: libxml++-2.6 >= 2.17.0")
+    SET(CIFTILIB_PKGCONFIG_DEFINE "-DCIFTILIB_USE_XMLPP")
+ENDIF (NOT QT_FOUND)
+
+#boost, including filesystem if not using QT
+IF (NOT QT_FOUND)
+    FIND_PACKAGE(Boost REQUIRED COMPONENTS filesystem system)
+ELSE (NOT QT_FOUND)
+    FIND_PACKAGE(Boost REQUIRED)
+ENDIF (NOT QT_FOUND)
+INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS})
+SET(LIBS ${LIBS} ${Boost_LIBRARIES})
+#boost quirks
+IF (Boost_VERSION LESS 104400)
+    #absolute() was added in 1.44.0, with filesystem v3
+    ADD_DEFINITIONS(-DCIFTILIB_BOOST_NO_FSV3)
+ENDIF (Boost_VERSION LESS 104400)
+IF (Boost_VERSION LESS 104800)
+    #canonical() was added in 1.48.0
+    ADD_DEFINITIONS(-DCIFTILIB_BOOST_NO_CANONICAL)
+ENDIF (Boost_VERSION LESS 104800)
+IF (Boost_VERSION LESS 105600)
+    #try_lexical_cast was added in 1.56.0
+    ADD_DEFINITIONS(-DCIFTILIB_BOOST_NO_TRY_LEXICAL)
+ENDIF (Boost_VERSION LESS 105600)
+
+#zlib, useful for volume reading
+FIND_PACKAGE(ZLIB)
+IF (ZLIB_FOUND)
+    INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS})
+    SET(LIBS ${LIBS} ${ZLIB_LIBRARIES})
+    ADD_DEFINITIONS("-DCIFTILIB_HAVE_ZLIB")
+ENDIF (ZLIB_FOUND)
+#OS X has some weirdness in its zlib, so let the preprocessor know
+IF (APPLE)
+    ADD_DEFINITIONS(-DCIFTILIB_OS_MACOSX)
+ENDIF (APPLE)
+
+#openmp provides a fast mutex implementation, faster than QT (and probably faster than glibmm)
+FIND_PACKAGE(OpenMP)
+IF (OPENMP_FOUND)
+    SET(CMAKE_CXX_FLAGS "${OpenMP_CXX_FLAGS} ${CMAKE_CXX_FLAGS}")
+ENDIF (OPENMP_FOUND)
+
+ENABLE_TESTING()
+
+#the library source, doesn't contain build targets
+ADD_SUBDIRECTORY(src)
+
+#example directory, has build targets and tests
+ADD_SUBDIRECTORY(example)
+
+macro(append_subdir_files variable dirname)
+    get_directory_property(holder DIRECTORY ${dirname} DEFINITION ${variable})
+    foreach(depfile ${holder})
+        list(APPEND ${variable} "${dirname}/${depfile}")
+    endforeach()
+endmacro()
+
+#get the sources and install info
+append_subdir_files(SOURCES src)
+append_subdir_files(HEADERS src)
+append_subdir_files(PUBLIC_HEADERS src)
+append_subdir_files(PRIVATE_DIRS src)
+
+ADD_LIBRARY(Cifti
+${SOURCES}
+${HEADERS}
+)
+
+#one way to get qt5's new compiler flag restrictions into the build - does it have other consequences?
+TARGET_LINK_LIBRARIES(Cifti ${LIBS})
+
+#NOTE: soversion set to 0 because ABI compatibility was not designed into the interface
+#soversion defines what symlinks are created, version defines what to put on the end of the actual library file
+SET_TARGET_PROPERTIES(Cifti
+PROPERTIES
+OUTPUT_NAME Cifti
+SOVERSION 0
+VERSION ${CIFTILIB_VERSION}
+)
+
+INCLUDE_DIRECTORIES(
+${CMAKE_SOURCE_DIR}/src
+)
+
+#install dirs
+INCLUDE(GNUInstallDirs)
+
+#pkg-config
+CONFIGURE_FILE(CiftiLib.pc.in CiftiLib.pc @ONLY)
+INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/CiftiLib.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+
+#install
+INSTALL(TARGETS Cifti DESTINATION ${CMAKE_INSTALL_LIBDIR})
+INSTALL(FILES ${PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CiftiLib)
+INSTALL(DIRECTORY ${PRIVATE_DIRS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CiftiLib FILES_MATCHING PATTERN "*.h")
diff --git a/CiftiLib.pc.in b/CiftiLib.pc.in
new file mode 100644
index 0000000..13cd78c
--- /dev/null
+++ b/CiftiLib.pc.in
@@ -0,0 +1,10 @@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+
+Name: CiftiLib
+Description: C++ Library for reading and writing CIFTI-2 and CIFTI-1 files
+Version: @CIFTILIB_VERSION@
+URL: https://github.com/Washington-University/CiftiLib
+Cflags: -I${includedir}/CiftiLib @CIFTILIB_PKGCONFIG_DEFINE@
+Libs: -L${libdir} -lCifti
+ at CIFTILIB_PKGCONFIG_REQUIRES_LINE@
diff --git a/Doxyfile.in b/Doxyfile.in
new file mode 100644
index 0000000..e06f0a7
--- /dev/null
+++ b/Doxyfile.in
@@ -0,0 +1,1516 @@
+# Doxyfile 1.5.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file 
+# that follow. The default is UTF-8 which is also the encoding used for all 
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the 
+# iconv built into libc) for the transcoding. See 
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = "@PROJECT_NAME@"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = "@PROJECT_VERSION@"
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description 
+# for a project that appears at the top of each page and should give viewer 
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "A C++ library for CIFTI-2 and CIFTI-1 files"
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = "@DOXYFILE_OUTPUT_DIR@"
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, 
+# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, 
+# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), 
+# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, 
+# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, 
+# Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like regular Qt-style comments 
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will 
+# interpret the first line (until the first dot) of a Qt-style 
+# comment as the brief description. If set to NO, the comments 
+# will behave just like regular Qt-style comments (thus requiring 
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java 
+# sources only. Doxygen will then generate output that is more tailored for 
+# Java. For instance, namespaces will be presented as packages, qualified 
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran 
+# sources only. Doxygen will then generate output that is more tailored for 
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL 
+# sources. Doxygen will then generate output that is tailored for 
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it parses. 
+# With this tag you can assign which parser to use for a given extension. 
+# Doxygen has a built-in mapping, but you can override or extend it using this tag. 
+# The format is ext=language, where ext is a file extension, and language is one of 
+# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, 
+# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat 
+# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), 
+# use: inc=Fortran f=C
+
+EXTENSION_MAPPING      = 
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want 
+# to include (a tag file for) the STL sources as input, then you should 
+# set this tag to YES in order to let doxygen match functions declarations and 
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. 
+# func(std::string) {}). This also make the inheritance and collaboration 
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to 
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. 
+# Doxygen will parse them like normal C++ but will assume all classes use public 
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter 
+# and setter methods for a property. Setting this option to YES (the default) 
+# will make doxygen to replace the get and set methods by a property in the 
+# documentation. This will only work if the methods are indeed getting or 
+# setting a simple type. If this is not the case, or you want to show the 
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum 
+# is documented as struct, union, or enum with the name of the typedef. So 
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct 
+# with name TypeT. When disabled the typedef will appear as a member of a file, 
+# namespace, or class. And the struct will be named TypeS. This can typically 
+# be useful for C code in case the coding convention dictates that all compound 
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to 
+# determine which symbols to keep in memory and which to flush to disk. 
+# When the cache is full, less often used symbols will be written to disk. 
+# For small to medium size projects (<1000 input files) the default value is 
+# probably good enough. For larger projects a too small cache size can cause 
+# doxygen to be busy swapping symbols to and from disk most of the time 
+# causing a significant performance penality. 
+# If the system has enough physical memory increasing the cache will improve the 
+# performance by keeping more symbols in memory. Note that the value works on 
+# a logarithmic scale so increasing the size by one will rougly double the 
+# memory usage. The cache size is given by this formula: 
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, 
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be 
+# extracted and appear in the documentation as a namespace called 
+# 'anonymous_namespace{file}', where file will be replaced with the base 
+# name of the file that contains the anonymous namespace. By default 
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the 
+# hierarchy of group names into alphabetical order. If set to NO (the default) 
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. 
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. 
+# This will remove the Files entry from the Quick Index and from the 
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the 
+# Namespaces page. 
+# This will remove the Namespaces entry from the Quick Index 
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from 
+# the version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the program writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by 
+# doxygen. The layout file controls the global structure of the generated output files 
+# in an output format independent way. The create the layout file that represents 
+# doxygen's defaults, run doxygen with the -l option. You can optionally specify a 
+# file name after the option, if omitted DoxygenLayout.xml will be used as the name 
+# of the layout file.
+
+LAYOUT_FILE            = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = @DOXYFILE_SOURCE_DIRS@
+
+# This tag can be used to specify the character encoding of the source files 
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is 
+# also the default input encoding. Doxygen uses libiconv (or the iconv built 
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for 
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = 
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = "_darcs"
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories. Note that the wildcards are matched 
+# against the file with absolute path, so to exclude all test directories 
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = "*/.*" "*/.*/*"
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
+# (namespaces, classes, functions, etc.) that should be excluded from the 
+# output. The symbol name can be a fully qualified name, a word, or if the 
+# wildcard * is used, a substring. Examples: ANamespace, AClass, 
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = "@CMAKE_CURRENT_SOURCE_DIR@/example"
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = "@CMAKE_CURRENT_SOURCE_DIR@"
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output. 
+# If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis. 
+# Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match. 
+# The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) 
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from 
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will 
+# link to the source code. 
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code 
+# will point to the HTML generated by the htags(1) tool instead of doxygen 
+# built-in source browser. The htags tool is part of GNU's global source 
+# tagging system (see http://www.gnu.org/software/global/global.html). You 
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = "@DOXYFILE_HTML_DIR@"
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML 
+# documentation will contain sections that can be hidden and shown after the 
+# page has loaded. For this to work a browser that supports 
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox 
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files 
+# will be generated that can be used as input for Apple's Xcode 3 
+# integrated development environment, introduced with OSX 10.5 (Leopard). 
+# To create a documentation set, doxygen will generate a Makefile in the 
+# HTML output directory. Running make will produce the docset in that 
+# directory and running "make install" will install the docset in 
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find 
+# it at startup. 
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the 
+# feed. A documentation feed provides an umbrella under which multiple 
+# documentation sets from a single provider (such as a company or product suite) 
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that 
+# should uniquely identify the documentation set bundle. This should be a 
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen 
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING 
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file 
+# content.
+
+CHM_INDEX_ENCODING     = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER 
+# are set, an additional index file will be generated that can be used as input for 
+# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated 
+# HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can 
+# be used to specify the file name of the resulting .qch file. 
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               = 
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating 
+# Qt Help Project output. For more information please see 
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = 
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating 
+# Qt Help Project output. For more information please see 
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. 
+# For more information please see 
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   = 
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see 
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  = 
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's 
+# filter section matches. 
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  = 
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can 
+# be used to specify the location of Qt's qhelpgenerator. 
+# If non-empty doxygen will try to run qhelpgenerator on the generated 
+# .qhp file.
+
+QHG_LOCATION           = 
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index 
+# structure should be generated to display hierarchical information. 
+# If the tag value is set to FRAME, a side panel will be generated 
+# containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature. Other possible values 
+# for this tag are: HIERARCHIES, which will generate the Groups, Directories, 
+# and Class Hierarchy pages using a tree view instead of an ordered list; 
+# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which 
+# disables this behavior completely. For backwards compatibility with previous 
+# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE 
+# respectively.
+
+GENERATE_TREEVIEW      = NONE
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# Use this tag to change the font size of Latex formulas included 
+# as images in the HTML documentation. The default is 10. Note that 
+# when you change the font size after a successful doxygen run you need 
+# to manually remove any form_*.png images from the HTML output directory 
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = @DOXYFILE_GENERATE_LATEX@
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = "@DOXYFILE_LATEX_DIR@"
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = "@LATEX_COMPILER@"
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = "@MAKEINDEX_COMPILER@"
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = @DOXYFILE_PDFLATEX@
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = YES
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader. 
+# This is useful 
+# if you want to understand what is going on. 
+# On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#  
+# TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#  
+# TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links. 
+# Note that each tag file must have a unique name 
+# (where the name does NOT include the path) 
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc 
+# command. Doxygen will then run the mscgen tool (see 
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the 
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where 
+# the mscgen tool resides. If left empty the tool is assumed to be found in the 
+# default search path.
+
+MSCGEN_PATH            = 
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = @DOXYFILE_DOT@
+
+# By default doxygen will write a font called FreeSans.ttf to the output 
+# directory and reference it in all dot files that doxygen generates. This 
+# font does not include all possible unicode characters however, so when you need 
+# these (or just want a differently looking font) you can specify the font name 
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font, 
+# which can be done by putting it in a standard location or by setting the 
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory 
+# containing the font.
+
+DOT_FONTNAME           = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. 
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the 
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a 
+# different font using DOT_FONTNAME you can set the path where dot 
+# can find it using this tag.
+
+DOT_FONTPATH           = 
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then 
+# doxygen will generate a call dependency graph for every global function 
+# or class method. Note that enabling this option will significantly increase 
+# the time of a run. So in most cases it will be better to enable call graphs 
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then 
+# doxygen will generate a caller dependency graph for every global function 
+# or class method. Note that enabling this option will significantly increase 
+# the time of a run. So in most cases it will be better to enable caller 
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include 
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif 
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = "@DOXYGEN_DOT_PATH@"
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of 
+# nodes that will be shown in the graph. If the number of nodes in a graph 
+# becomes larger than this value, doxygen will truncate the graph, which is 
+# visualized by representing a node as a red box. Note that doxygen if the 
+# number of direct children of the root node in a graph is already larger than 
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note 
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that the size of a graph can be further restricted by 
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, because dot on Windows does not 
+# seem to support this out of the box. Warning: Depending on the platform used, 
+# enabling this option may lead to badly anti-aliased labels on the edges of 
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = YES
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Options related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..72ffd1b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,10 @@
+Copyright (c) 2014, Washington University School of Medicine
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROF [...]
diff --git a/README b/README
new file mode 100644
index 0000000..0890eac
--- /dev/null
+++ b/README
@@ -0,0 +1,30 @@
+The CiftiLib library requires boost headers and either QT (4.8.x or 5), or libxml++ 2.17.x or newer (and its dependencies: libxml2, glib, sigc++, gtkmm and glibmm) and the boost filesystem library to compile, and optionally uses zlib if you want to use its NIfTI reading capabilities for other purposes.
+
+It is currently set up to be compiled, along with a few simple examples, by cmake:
+
+#start one level up from the source tree
+#make build directory beside the source tree, enter it
+mkdir build; cd build
+
+#you may want to set the cmake variable CMAKE_BUILD_TYPE to Release or Debug before building
+#the default build behavior may be non-optimized and without debug symbols.
+
+#run cmake to generate makefiles
+cmake ../CiftiLib -D CMAKE_BUILD_TYPE=Release
+#OR
+cmake-gui ../CiftiLib
+
+#build
+make
+
+#OPTIONAL: run tests, make docs
+make test
+make doc
+
+The resulting library and example executables will be in subdirectories of the build directory, not in the source directory.  You can install the library and headers (location controlled by cmake variable CMAKE_INSTALL_PREFIX and make variable DESTDIR) with 'make install'.
+
+Troubleshooting:
+
+If you are using manually-installed libraries rather than distribution-packaged libraries, you may need to use the cmake variables CMAKE_LIBRARY_PATH and CMAKE_INCLUDE_PATH.
+
+If you are getting link errors related to boost, try setting a cmake variable Boost_NO_BOOST_CMAKE to true.
diff --git a/USAGE b/USAGE
new file mode 100644
index 0000000..c4de869
--- /dev/null
+++ b/USAGE
@@ -0,0 +1,25 @@
+The main object for dealing with Cifti is CiftiFile.  To set up a new CiftiFile,
+make a CiftiXML object with the mappings you want, and call setCiftiXML on the CiftiFile object.
+
+The XML tells you things about the mappings, and lets you set new mappings or modify existing ones:
+
+int64_t rowLength = myXML.getDimensionLength(CiftiXML::ALONG_ROW);
+CiftiMappingType::MappingType rowType = myXML.getMappingType(CiftiXML::ALONG_ROW);
+if (rowType == CiftiMappingType::SCALARS)
+{
+    CiftiScalarsMap& myScalarMap = myXML.getScalarsMap(CiftiXML::ALONG_ROW);
+    QString firstMapName = myScalarMap.getMapName(0);
+    ...
+    myScalarMap.setLength(1);
+}
+
+See the rewrite example for how to read and write data to CiftiFile.
+
+CiftiFile internally uses NiftiIO, which is a NIfTI reader for both NIfTI-1 and NIfTI-2 single-file (.nii),
+including reading .nii.gz files if zlib is found (NOTE: .nii.gz should not be used for CIFTI files,
+as seeking is slow, and seeking backwards is impossible).
+
+Our nifti1.h and nifti2.h are slightly modified, replacing the #defines of standard values for header fields
+with constant integers.  We also declare CIFTI-specific intent codes and the extension code in nifti2.h,
+and have some macros for determining header version.
+
diff --git a/cmake/Modules/Findglib.cmake b/cmake/Modules/Findglib.cmake
new file mode 100644
index 0000000..725586e
--- /dev/null
+++ b/cmake/Modules/Findglib.cmake
@@ -0,0 +1,16 @@
+
+#use pkg-config
+FIND_PACKAGE(PkgConfig)
+PKG_CHECK_MODULES(PC_GLIB glib-2.0)
+
+FIND_PATH(glib_INCLUDE_DIR NAMES glib.h HINTS ${PC_GLIB_INCLUDEDIR} ${PC_GLIB_INCLUDE_DIRS})
+FIND_PATH(glib_config_INCLUDE_DIR NAMES glibconfig.h HINTS ${PC_GLIB_INCLUDEDIR} ${PC_GLIB_INCLUDE_DIRS})
+FIND_LIBRARY(glib_LIBRARY NAMES glib glib-2.0 HINTS ${PC_GLIB_LIBDIR} ${PC_GLIB_LIBRARY_DIRS})
+
+SET(glib_LIBRARIES ${glib_LIBRARY} ${PC_GLIB_PKGCONF_LIBRARIES})
+SET(glib_INCLUDE_DIRS ${glib_INCLUDE_DIR} ${glib_config_INCLUDE_DIR} ${PC_GLIB_PKGCONF_INCLUDE_DIRS})
+
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(glib DEFAULT_MSG glib_LIBRARY glib_INCLUDE_DIR)
+
+MARK_AS_ADVANCED(glib_INCLUDE_DIR glib_config_INCLUDE_DIR glib_LIBRARY)
diff --git a/cmake/Modules/Findglibmm.cmake b/cmake/Modules/Findglibmm.cmake
new file mode 100644
index 0000000..c9b9bef
--- /dev/null
+++ b/cmake/Modules/Findglibmm.cmake
@@ -0,0 +1,28 @@
+
+IF(glibmm_FIND_REQUIRED)
+    FIND_PACKAGE(glib REQUIRED)
+    FIND_PACKAGE(sigc++ REQUIRED)
+ELSE(glibmm_FIND_REQUIRED)
+    FIND_PACKAGE(glib)
+    FIND_PACKAGE(sigc++)
+ENDIF(glibmm_FIND_REQUIRED)
+
+IF(GLIB_FOUND)
+
+    #use pkg-config
+    FIND_PACKAGE(PkgConfig)
+    PKG_CHECK_MODULES(PC_GLIBMM glibmm-2.4)
+
+    FIND_PATH(glibmm_INCLUDE_DIR NAMES glibmm/main.h HINTS ${PC_GLIBMM_INCLUDEDIR} ${PC_GLIBMM_INCLUDE_DIRS})
+    FIND_PATH(glibmm_config_INCLUDE_DIR NAMES glibmmconfig.h HINTS ${PC_GLIBMM_INCLUDEDIR} ${PC_GLIBMM_INCLUDE_DIRS})
+    FIND_LIBRARY(glibmm_LIBRARY NAMES glibmm glibmm-2.4 HINTS ${PC_GLIBMM_LIBDIR} ${PC_GLIBMM_LIBRARY_DIRS})
+
+    SET(glibmm_LIBRARIES ${glibmm_LIBRARY} ${PC_GLIBMM_PKGCONF_LIBRARIES} ${glib_LIBRARIES} ${sigc++_LIBRARIES})
+    SET(glibmm_INCLUDE_DIRS ${glibmm_INCLUDE_DIR} ${glibmm_config_INCLUDE_DIR} ${PC_GLIBMM_PKGCONF_INCLUDE_DIRS} ${glib_INCLUDE_DIRS} ${sigc++_INCLUDE_DIRS})
+
+ENDIF(GLIB_FOUND)
+
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(glibmm DEFAULT_MSG glibmm_LIBRARY glibmm_INCLUDE_DIR)
+
+MARK_AS_ADVANCED(glibmm_INCLUDE_DIR glibmm_config_INCLUDE_DIR glibmm_LIBRARY)
diff --git a/cmake/Modules/Findlibxml++.cmake b/cmake/Modules/Findlibxml++.cmake
new file mode 100644
index 0000000..1a80939
--- /dev/null
+++ b/cmake/Modules/Findlibxml++.cmake
@@ -0,0 +1,32 @@
+
+IF(libxml++_FIND_REQUIRED)
+    FIND_PACKAGE(glibmm REQUIRED)
+    FIND_PACKAGE(LibXml2 REQUIRED)
+ELSE(libxml++_FIND_REQUIRED)
+    FIND_PACKAGE(glibmm)
+    FIND_PACKAGE(LibXml2)
+ENDIF(libxml++_FIND_REQUIRED)
+
+IF(GLIBMM_FOUND AND LIBXML2_FOUND)
+
+    #use pkg-config
+    FIND_PACKAGE(PkgConfig)
+    PKG_CHECK_MODULES(PC_LIBXMLPP QUIET libxml++-2.6)
+
+    FIND_PATH(libxml++_INCLUDE_DIR NAMES libxml++/libxml++.h HINTS ${PC_LIBXMLPP_INCLUDEDIR} ${PC_LIBXMLPP_INCLUDE_DIRS})
+    FIND_PATH(libxml++_config_INCLUDE_DIR NAMES libxml++config.h HINTS ${PC_LIBXMLPP_INCLUDEDIR} ${PC_LIBXMLPP_INCLUDE_DIRS})
+    FIND_LIBRARY(libxml++_LIBRARY NAMES xml++ xml++-2.6 HINTS ${PC_LIBXMLPP_LIBDIR} ${PC_LIBXMLPP_LIBRARY_DIRS})
+
+    SET(libxml++_LIBRARIES ${libxml++_LIBRARY} ${PC_LIBXMLPP_PKGCONF_LIBRARIES} ${glibmm_LIBRARIES} ${LIBXML2_LIBRARIES})
+IF(libxml++_config_INCLUDE_DIR)
+    SET(libxml++_INCLUDE_DIRS ${libxml++_INCLUDE_DIR} ${PC_LIBXMLPP_PKGCONF_INCLUDE_DIRS} ${libxml++_config_INCLUDE_DIR} ${glibmm_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR})
+ELSE(libxml++_config_INCLUDE_DIR)
+    SET(libxml++_INCLUDE_DIRS ${libxml++_INCLUDE_DIR} ${PC_LIBXMLPP_PKGCONF_INCLUDE_DIRS} ${glibmm_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR})
+ENDIF(libxml++_config_INCLUDE_DIR)
+
+ENDIF(GLIBMM_FOUND AND LIBXML2_FOUND)
+
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(libxml++ DEFAULT_MSG libxml++_LIBRARY libxml++_INCLUDE_DIR)
+
+MARK_AS_ADVANCED(libxml++_INCLUDE_DIR libxml++_config_INCLUDE_DIR libxml++_LIBRARY)
diff --git a/cmake/Modules/Findsigc++.cmake b/cmake/Modules/Findsigc++.cmake
new file mode 100644
index 0000000..64ed168
--- /dev/null
+++ b/cmake/Modules/Findsigc++.cmake
@@ -0,0 +1,16 @@
+
+#use pkg-config
+FIND_PACKAGE(PkgConfig)
+PKG_CHECK_MODULES(PC_SIGCXX sigc++-2.0)
+
+FIND_PATH(sigc++_INCLUDE_DIR NAMES sigc++/sigc++.h HINTS ${PC_SIGCXX_INCLUDEDIR} ${PC_SIGCXX_INCLUDE_DIRS})
+FIND_PATH(sigc++_config_INCLUDE_DIR NAMES sigc++config.h HINTS ${PC_SIGCXX_INCLUDEDIR} ${PC_SIGCXX_INCLUDE_DIRS})
+FIND_LIBRARY(sigc++_LIBRARY NAMES sigc sigc-2.0 HINTS ${PC_SIGCXX_LIBDIR} ${PC_SIGCXX_LIBRARY_DIRS})
+
+SET(sigc++_LIBRARIES ${sigc++_LIBRARY} ${PC_SIGCXX_PKGCONF_LIBRARIES})
+SET(sigc++_INCLUDE_DIRS ${sigc++_INCLUDE_DIR} ${sigc++_config_INCLUDE_DIR} ${PC_SIGCXX_PKGCONF_INCLUDE_DIRS})
+
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(sigc++ DEFAULT_MSG sigc++_LIBRARY sigc++_INCLUDE_DIR)
+
+MARK_AS_ADVANCED(sigc++_INCLUDE_DIR sigc++_config_INCLUDE_DIR sigc++_LIBRARY)
diff --git a/cmake/Modules/UseDoxygen/CMakeLists.txt b/cmake/Modules/UseDoxygen/CMakeLists.txt
new file mode 100644
index 0000000..c4d06e3
--- /dev/null
+++ b/cmake/Modules/UseDoxygen/CMakeLists.txt
@@ -0,0 +1,16 @@
+cmake_minimum_required(VERSION 2.6)
+
+project(UseDoxygen)
+
+set(CMAKE_INSTALL_PREFIX ${CMAKE_ROOT})
+
+install(FILES UseDoxygen.cmake Doxyfile.in
+	DESTINATION "Modules")
+
+configure_file(
+  "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
+  "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
+  IMMEDIATE @ONLY)
+
+add_custom_target(uninstall
+	COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
diff --git a/cmake/Modules/UseDoxygen/COPYING-CMAKE-SCRIPTS b/cmake/Modules/UseDoxygen/COPYING-CMAKE-SCRIPTS
new file mode 100644
index 0000000..4b41776
--- /dev/null
+++ b/cmake/Modules/UseDoxygen/COPYING-CMAKE-SCRIPTS
@@ -0,0 +1,22 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products 
+   derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cmake/Modules/UseDoxygen/Doxyfile.in b/cmake/Modules/UseDoxygen/Doxyfile.in
new file mode 100644
index 0000000..b27549c
--- /dev/null
+++ b/cmake/Modules/UseDoxygen/Doxyfile.in
@@ -0,0 +1,1510 @@
+# Doxyfile 1.5.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file 
+# that follow. The default is UTF-8 which is also the encoding used for all 
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the 
+# iconv built into libc) for the transcoding. See 
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = "@PROJECT_NAME@"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = "@PROJECT_VERSION@"
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = "@DOXYFILE_OUTPUT_DIR@"
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, 
+# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, 
+# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), 
+# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, 
+# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, 
+# Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like regular Qt-style comments 
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will 
+# interpret the first line (until the first dot) of a Qt-style 
+# comment as the brief description. If set to NO, the comments 
+# will behave just like regular Qt-style comments (thus requiring 
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java 
+# sources only. Doxygen will then generate output that is more tailored for 
+# Java. For instance, namespaces will be presented as packages, qualified 
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran 
+# sources only. Doxygen will then generate output that is more tailored for 
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL 
+# sources. Doxygen will then generate output that is tailored for 
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it parses. 
+# With this tag you can assign which parser to use for a given extension. 
+# Doxygen has a built-in mapping, but you can override or extend it using this tag. 
+# The format is ext=language, where ext is a file extension, and language is one of 
+# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, 
+# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat 
+# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), 
+# use: inc=Fortran f=C
+
+EXTENSION_MAPPING      = 
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want 
+# to include (a tag file for) the STL sources as input, then you should 
+# set this tag to YES in order to let doxygen match functions declarations and 
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. 
+# func(std::string) {}). This also make the inheritance and collaboration 
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to 
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. 
+# Doxygen will parse them like normal C++ but will assume all classes use public 
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter 
+# and setter methods for a property. Setting this option to YES (the default) 
+# will make doxygen to replace the get and set methods by a property in the 
+# documentation. This will only work if the methods are indeed getting or 
+# setting a simple type. If this is not the case, or you want to show the 
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum 
+# is documented as struct, union, or enum with the name of the typedef. So 
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct 
+# with name TypeT. When disabled the typedef will appear as a member of a file, 
+# namespace, or class. And the struct will be named TypeS. This can typically 
+# be useful for C code in case the coding convention dictates that all compound 
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to 
+# determine which symbols to keep in memory and which to flush to disk. 
+# When the cache is full, less often used symbols will be written to disk. 
+# For small to medium size projects (<1000 input files) the default value is 
+# probably good enough. For larger projects a too small cache size can cause 
+# doxygen to be busy swapping symbols to and from disk most of the time 
+# causing a significant performance penality. 
+# If the system has enough physical memory increasing the cache will improve the 
+# performance by keeping more symbols in memory. Note that the value works on 
+# a logarithmic scale so increasing the size by one will rougly double the 
+# memory usage. The cache size is given by this formula: 
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, 
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be 
+# extracted and appear in the documentation as a namespace called 
+# 'anonymous_namespace{file}', where file will be replaced with the base 
+# name of the file that contains the anonymous namespace. By default 
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the 
+# hierarchy of group names into alphabetical order. If set to NO (the default) 
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. 
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. 
+# This will remove the Files entry from the Quick Index and from the 
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the 
+# Namespaces page. 
+# This will remove the Namespaces entry from the Quick Index 
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from 
+# the version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the program writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by 
+# doxygen. The layout file controls the global structure of the generated output files 
+# in an output format independent way. The create the layout file that represents 
+# doxygen's defaults, run doxygen with the -l option. You can optionally specify a 
+# file name after the option, if omitted DoxygenLayout.xml will be used as the name 
+# of the layout file.
+
+LAYOUT_FILE            = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = @DOXYFILE_SOURCE_DIRS@
+
+# This tag can be used to specify the character encoding of the source files 
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is 
+# also the default input encoding. Doxygen uses libiconv (or the iconv built 
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for 
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = 
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = "_darcs"
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories. Note that the wildcards are matched 
+# against the file with absolute path, so to exclude all test directories 
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = "*/.*" "*/.*/*"
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
+# (namespaces, classes, functions, etc.) that should be excluded from the 
+# output. The symbol name can be a fully qualified name, a word, or if the 
+# wildcard * is used, a substring. Examples: ANamespace, AClass, 
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = "@CMAKE_CURRENT_SOURCE_DIR@/examples"
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = "@CMAKE_CURRENT_SOURCE_DIR@"
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output. 
+# If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis. 
+# Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match. 
+# The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) 
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from 
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will 
+# link to the source code. 
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code 
+# will point to the HTML generated by the htags(1) tool instead of doxygen 
+# built-in source browser. The htags tool is part of GNU's global source 
+# tagging system (see http://www.gnu.org/software/global/global.html). You 
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = "@DOXYFILE_HTML_DIR@"
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML 
+# documentation will contain sections that can be hidden and shown after the 
+# page has loaded. For this to work a browser that supports 
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox 
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files 
+# will be generated that can be used as input for Apple's Xcode 3 
+# integrated development environment, introduced with OSX 10.5 (Leopard). 
+# To create a documentation set, doxygen will generate a Makefile in the 
+# HTML output directory. Running make will produce the docset in that 
+# directory and running "make install" will install the docset in 
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find 
+# it at startup. 
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the 
+# feed. A documentation feed provides an umbrella under which multiple 
+# documentation sets from a single provider (such as a company or product suite) 
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that 
+# should uniquely identify the documentation set bundle. This should be a 
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen 
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING 
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file 
+# content.
+
+CHM_INDEX_ENCODING     = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER 
+# are set, an additional index file will be generated that can be used as input for 
+# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated 
+# HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can 
+# be used to specify the file name of the resulting .qch file. 
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               = 
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating 
+# Qt Help Project output. For more information please see 
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = 
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating 
+# Qt Help Project output. For more information please see 
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. 
+# For more information please see 
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   = 
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see 
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  = 
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's 
+# filter section matches. 
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  = 
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can 
+# be used to specify the location of Qt's qhelpgenerator. 
+# If non-empty doxygen will try to run qhelpgenerator on the generated 
+# .qhp file.
+
+QHG_LOCATION           = 
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index 
+# structure should be generated to display hierarchical information. 
+# If the tag value is set to FRAME, a side panel will be generated 
+# containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature. Other possible values 
+# for this tag are: HIERARCHIES, which will generate the Groups, Directories, 
+# and Class Hierarchy pages using a tree view instead of an ordered list; 
+# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which 
+# disables this behavior completely. For backwards compatibility with previous 
+# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE 
+# respectively.
+
+GENERATE_TREEVIEW      = NONE
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# Use this tag to change the font size of Latex formulas included 
+# as images in the HTML documentation. The default is 10. Note that 
+# when you change the font size after a successful doxygen run you need 
+# to manually remove any form_*.png images from the HTML output directory 
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = @DOXYFILE_GENERATE_LATEX@
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = "@DOXYFILE_LATEX_DIR@"
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = "@LATEX_COMPILER@"
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = "@MAKEINDEX_COMPILER@"
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = @DOXYFILE_PDFLATEX@
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = YES
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader. 
+# This is useful 
+# if you want to understand what is going on. 
+# On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#  
+# TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#  
+# TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links. 
+# Note that each tag file must have a unique name 
+# (where the name does NOT include the path) 
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc 
+# command. Doxygen will then run the mscgen tool (see 
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the 
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where 
+# the mscgen tool resides. If left empty the tool is assumed to be found in the 
+# default search path.
+
+MSCGEN_PATH            = 
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = @DOXYFILE_DOT@
+
+# By default doxygen will write a font called FreeSans.ttf to the output 
+# directory and reference it in all dot files that doxygen generates. This 
+# font does not include all possible unicode characters however, so when you need 
+# these (or just want a differently looking font) you can specify the font name 
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font, 
+# which can be done by putting it in a standard location or by setting the 
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory 
+# containing the font.
+
+DOT_FONTNAME           = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. 
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the 
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a 
+# different font using DOT_FONTNAME you can set the path where dot 
+# can find it using this tag.
+
+DOT_FONTPATH           = 
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then 
+# doxygen will generate a call dependency graph for every global function 
+# or class method. Note that enabling this option will significantly increase 
+# the time of a run. So in most cases it will be better to enable call graphs 
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then 
+# doxygen will generate a caller dependency graph for every global function 
+# or class method. Note that enabling this option will significantly increase 
+# the time of a run. So in most cases it will be better to enable caller 
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include 
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif 
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = "@DOXYGEN_DOT_PATH@"
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of 
+# nodes that will be shown in the graph. If the number of nodes in a graph 
+# becomes larger than this value, doxygen will truncate the graph, which is 
+# visualized by representing a node as a red box. Note that doxygen if the 
+# number of direct children of the root node in a graph is already larger than 
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note 
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that the size of a graph can be further restricted by 
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, because dot on Windows does not 
+# seem to support this out of the box. Warning: Depending on the platform used, 
+# enabling this option may lead to badly anti-aliased labels on the edges of 
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = YES
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Options related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
diff --git a/cmake/Modules/UseDoxygen/UseDoxygen.cmake b/cmake/Modules/UseDoxygen/UseDoxygen.cmake
new file mode 100644
index 0000000..245ba56
--- /dev/null
+++ b/cmake/Modules/UseDoxygen/UseDoxygen.cmake
@@ -0,0 +1,144 @@
+# - Run Doxygen
+#
+# Adds a doxygen target that runs doxygen to generate the html
+# and optionally the LaTeX API documentation.
+# The doxygen target is added to the doc target as a dependency.
+# i.e.: the API documentation is built with:
+#  make doc
+#
+# USAGE: GLOBAL INSTALL
+#
+# Install it with:
+#  cmake ./ && sudo make install
+# Add the following to the CMakeLists.txt of your project:
+#  include(UseDoxygen OPTIONAL)
+# Optionally copy Doxyfile.in in the directory of CMakeLists.txt and edit it.
+#
+# USAGE: INCLUDE IN PROJECT
+#
+#  set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
+#  include(UseDoxygen)
+# Add the Doxyfile.in and UseDoxygen.cmake files to the projects source directory.
+#
+#
+# CONFIGURATION
+#
+# To configure Doxygen you can edit Doxyfile.in and set some variables in cmake.
+# Variables you may define are:
+#  DOXYFILE_SOURCE_DIR - Path where the Doxygen input files are.
+#  	Defaults to the current source directory.
+#  DOXYFILE_EXTRA_SOURCES - Additional source diretories/files for Doxygen to scan.
+#  	The Paths should be in double quotes and separated by space. e.g.:
+#  	 "${CMAKE_CURRENT_BINARY_DIR}/foo.c" "${CMAKE_CURRENT_BINARY_DIR}/bar/"
+#  
+#  DOXYFILE_OUTPUT_DIR - Path where the Doxygen output is stored.
+#  	Defaults to "${CMAKE_CURRENT_BINARY_DIR}/doc".
+#  
+#  DOXYFILE_LATEX - ON/OFF; Set to "ON" if you want the LaTeX documentation
+#  	to be built.
+#  DOXYFILE_LATEX_DIR - Directory relative to DOXYFILE_OUTPUT_DIR where
+#  	the Doxygen LaTeX output is stored. Defaults to "latex".
+#  
+#  DOXYFILE_HTML_DIR - Directory relative to DOXYFILE_OUTPUT_DIR where
+#  	the Doxygen html output is stored. Defaults to "html".
+#
+
+#
+#  Copyright (c) 2009, 2010, 2011 Tobias Rautenkranz <tobias at rautenkranz.ch>
+#
+#  Redistribution and use is allowed according to the terms of the New
+#  BSD license.
+#  For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+#
+
+macro(usedoxygen_set_default name value type docstring)
+	if(NOT DEFINED "${name}")
+		set("${name}" "${value}" CACHE "${type}" "${docstring}")
+	endif()
+endmacro()
+
+find_package(Doxygen)
+
+if(DOXYGEN_FOUND)
+	find_file(DOXYFILE_IN "Doxyfile.in"
+			PATHS "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_ROOT}/Modules/"
+			NO_DEFAULT_PATH
+			DOC "Path to the doxygen configuration template file")
+	set(DOXYFILE "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile")
+	include(FindPackageHandleStandardArgs)
+	find_package_handle_standard_args(DOXYFILE_IN DEFAULT_MSG "DOXYFILE_IN")
+endif()
+
+if(DOXYGEN_FOUND AND DOXYFILE_IN_FOUND)
+	usedoxygen_set_default(DOXYFILE_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/doc"
+		PATH "Doxygen output directory")
+	usedoxygen_set_default(DOXYFILE_HTML_DIR "html"
+		STRING "Doxygen HTML output directory")
+	usedoxygen_set_default(DOXYFILE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}"
+		PATH "Input files source directory")
+	usedoxygen_set_default(DOXYFILE_EXTRA_SOURCE_DIRS ""
+		STRING "Additional source files/directories separated by space")
+	set(DOXYFILE_SOURCE_DIRS "\"${DOXYFILE_SOURCE_DIR}\" ${DOXYFILE_EXTRA_SOURCES}")
+
+	usedoxygen_set_default(DOXYFILE_LATEX YES BOOL "Generate LaTeX API documentation" OFF)
+	usedoxygen_set_default(DOXYFILE_LATEX_DIR "latex" STRING "LaTex output directory")
+
+	mark_as_advanced(DOXYFILE_OUTPUT_DIR DOXYFILE_HTML_DIR DOXYFILE_LATEX_DIR
+		DOXYFILE_SOURCE_DIR DOXYFILE_EXTRA_SOURCE_DIRS DOXYFILE_IN)
+
+
+	set_property(DIRECTORY 
+		APPEND PROPERTY
+		ADDITIONAL_MAKE_CLEAN_FILES
+		"${DOXYFILE_OUTPUT_DIR}/${DOXYFILE_HTML_DIR}")
+
+	add_custom_target(doxygen
+		COMMAND "${DOXYGEN_EXECUTABLE}"
+			"${DOXYFILE}" 
+		COMMENT "Writing documentation to ${DOXYFILE_OUTPUT_DIR}..."
+		WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
+
+	set(DOXYFILE_DOT "NO")
+	if(DOXYGEN_DOT_EXECUTABLE)
+		set(DOXYFILE_DOT "YES")
+	endif()
+
+	## LaTeX
+	set(DOXYFILE_PDFLATEX "NO")
+
+	set_property(DIRECTORY APPEND PROPERTY
+		ADDITIONAL_MAKE_CLEAN_FILES
+		"${DOXYFILE_OUTPUT_DIR}/${DOXYFILE_LATEX_DIR}")
+
+	if(DOXYFILE_LATEX STREQUAL "ON")
+		set(DOXYFILE_GENERATE_LATEX "YES")
+		find_package(LATEX)
+		find_program(DOXYFILE_MAKE make)
+		mark_as_advanced(DOXYFILE_MAKE)
+		if(LATEX_COMPILER AND MAKEINDEX_COMPILER AND DOXYFILE_MAKE)
+			if(PDFLATEX_COMPILER)
+				set(DOXYFILE_PDFLATEX "YES")
+			endif()
+
+			add_custom_command(TARGET doxygen
+				POST_BUILD
+				COMMAND "${DOXYFILE_MAKE}"
+				COMMENT	"Running LaTeX for Doxygen documentation in ${DOXYFILE_OUTPUT_DIR}/${DOXYFILE_LATEX_DIR}..."
+				WORKING_DIRECTORY "${DOXYFILE_OUTPUT_DIR}/${DOXYFILE_LATEX_DIR}")
+		else()
+			set(DOXYGEN_LATEX "NO")
+		endif()
+	else()
+		set(DOXYFILE_GENERATE_LATEX "NO")
+	endif()
+
+
+	configure_file("${DOXYFILE_IN}" "${DOXYFILE}" @ONLY)
+
+	get_target_property(DOC_TARGET doc TYPE)
+	if(NOT DOC_TARGET)
+		add_custom_target(doc)
+	endif()
+
+	add_dependencies(doc doxygen)
+endif()
diff --git a/cmake/Modules/UseDoxygen/cmake_uninstall.cmake.in b/cmake/Modules/UseDoxygen/cmake_uninstall.cmake.in
new file mode 100644
index 0000000..df95fb9
--- /dev/null
+++ b/cmake/Modules/UseDoxygen/cmake_uninstall.cmake.in
@@ -0,0 +1,21 @@
+IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
+  MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
+ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
+
+FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
+STRING(REGEX REPLACE "\n" ";" files "${files}")
+FOREACH(file ${files})
+  MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
+  IF(EXISTS "$ENV{DESTDIR}${file}")
+    EXEC_PROGRAM(
+      "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
+      OUTPUT_VARIABLE rm_out
+      RETURN_VALUE rm_retval
+      )
+    IF(NOT "${rm_retval}" STREQUAL 0)
+      MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
+    ENDIF(NOT "${rm_retval}" STREQUAL 0)
+  ELSE(EXISTS "$ENV{DESTDIR}${file}")
+    MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.")
+  ENDIF(EXISTS "$ENV{DESTDIR}${file}")
+ENDFOREACH(file)
diff --git a/cmake/scripts/testmd5.cmake b/cmake/scripts/testmd5.cmake
new file mode 100644
index 0000000..1ed69a9
--- /dev/null
+++ b/cmake/scripts/testmd5.cmake
@@ -0,0 +1,5 @@
+FILE(MD5 ${check_file} real_sum)
+
+IF(NOT (${real_sum} STREQUAL ${good_sum}))
+    MESSAGE(FATAL_ERROR "expected ${good_sum}, got ${real_sum}")
+ENDIF(NOT (${real_sum} STREQUAL ${good_sum}))
diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt
new file mode 100644
index 0000000..71f4b0a
--- /dev/null
+++ b/example/CMakeLists.txt
@@ -0,0 +1,83 @@
+
+PROJECT(example)
+
+ADD_EXECUTABLE(rewrite
+rewrite.cxx)
+
+TARGET_LINK_LIBRARIES(rewrite
+Cifti
+${LIBS})
+
+ADD_EXECUTABLE(xmlinfo
+xmlinfo.cxx)
+
+TARGET_LINK_LIBRARIES(xmlinfo
+Cifti
+${LIBS})
+
+INCLUDE_DIRECTORIES(
+${CMAKE_SOURCE_DIR}/example
+${CMAKE_SOURCE_DIR}/src
+)
+
+SET(cifti_files
+Conte69.MyelinAndCorrThickness.32k_fs_LR.dscalar.nii
+Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii
+Conte69.MyelinAndCorrThickness.32k_fs_LR.ptseries.nii
+Conte69.parcellations_VGD11b.32k_fs_LR.dlabel.nii
+ones.dscalar.nii
+)
+
+IF(QT_FOUND)
+    #QT4
+    SET(cifti_be_md5s
+        3ba20f6ef590735c1211991f8e0144e6
+        e3a1639ef4b354752b0abb0613eedb73
+        9fd1b6fb7b938155226ca53fdaff5a12
+        48903d80589d294b5421cad9a7ba9264
+        93bb7f76c8d273817601ea5be236f83d
+    )
+    SET(cifti_le_md5s
+        3e24e7908122905c2a2d357347ecee7c
+        4ee31da414dcd26fe37855601ae3e9e2
+        e096a1cf0713f7a36590445e9f6564b1
+        32345267599b07083092b7dedfd8796c
+        512e0359c64d69dde93d605f8797f3a2
+    )
+ELSE(QT_FOUND)
+    #xml++
+    SET(cifti_be_md5s
+        a2581f76a94b7c4371d6970f1262e1bc
+        ca4cfb02fb7f0d8f167f546aab85e91d
+        892c26e691fe62c4edf358f7f91942a3
+        e761d405c945e724b37f96e356dc19ad
+        2e4efa4e36cdb13f514c265e6c9c8c4b
+    )
+    SET(cifti_le_md5s
+        39ecfba4c1dade1a2ce3e95b325bd8b1
+        921a42e4181052ef4f211d389a2e9328
+        764c816004ebe23d188d98f9f29c7337
+        6fabac021e377efd35dede7198feefd4
+        fe0cbb768e26aa12a0e03990f4f50a30
+    )
+ENDIF(QT_FOUND)
+
+#ADD_TEST(timer ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver timer)
+
+LIST(LENGTH cifti_files num_cifti_files)
+#FOREACH(... RANGE x) is stupid, it does all of [0, x] inclusive, totaling x + 1 iterations
+MATH(EXPR loop_end "${num_cifti_files} - 1")
+
+FOREACH(index RANGE ${loop_end})
+    LIST(GET cifti_files ${index} testfile)
+    #ADD_TEST doesn't seem to have a way to capture stdout, so checking the output for consistency seems to be a problem
+    ADD_TEST(info-${testfile} xmlinfo ${CMAKE_SOURCE_DIR}/example/data/${testfile})
+
+    ADD_TEST(rewrite-little-${testfile} rewrite ${CMAKE_SOURCE_DIR}/example/data/${testfile} little-${testfile} LITTLE)
+    LIST(GET cifti_le_md5s ${index} goodsum)
+    ADD_TEST(rewrite-little-md5-${testfile} ${CMAKE_COMMAND} -Dgood_sum=${goodsum} -Dcheck_file=little-${testfile} -P ${CMAKE_SOURCE_DIR}/cmake/scripts/testmd5.cmake)
+
+    ADD_TEST(rewrite-big-${testfile} rewrite ${CMAKE_SOURCE_DIR}/example/data/${testfile} big-${testfile} BIG)
+    LIST(GET cifti_be_md5s ${index} goodsum)
+    ADD_TEST(rewrite-big-md5-${testfile} ${CMAKE_COMMAND} -Dgood_sum=${goodsum} -Dcheck_file=big-${testfile} -P ${CMAKE_SOURCE_DIR}/cmake/scripts/testmd5.cmake)
+ENDFOREACH(index RANGE ${loop_end})
diff --git a/example/data/Conte69.MyelinAndCorrThickness.32k_fs_LR.dscalar.nii b/example/data/Conte69.MyelinAndCorrThickness.32k_fs_LR.dscalar.nii
new file mode 100644
index 0000000..55dfcbc
Binary files /dev/null and b/example/data/Conte69.MyelinAndCorrThickness.32k_fs_LR.dscalar.nii differ
diff --git a/example/data/Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii b/example/data/Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii
new file mode 100644
index 0000000..7655b21
Binary files /dev/null and b/example/data/Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii differ
diff --git a/example/data/Conte69.MyelinAndCorrThickness.32k_fs_LR.ptseries.nii b/example/data/Conte69.MyelinAndCorrThickness.32k_fs_LR.ptseries.nii
new file mode 100644
index 0000000..063231e
Binary files /dev/null and b/example/data/Conte69.MyelinAndCorrThickness.32k_fs_LR.ptseries.nii differ
diff --git a/example/data/Conte69.parcellations_VGD11b.32k_fs_LR.dlabel.nii b/example/data/Conte69.parcellations_VGD11b.32k_fs_LR.dlabel.nii
new file mode 100644
index 0000000..0052382
Binary files /dev/null and b/example/data/Conte69.parcellations_VGD11b.32k_fs_LR.dlabel.nii differ
diff --git a/example/data/ODC_PDDL.rtf b/example/data/ODC_PDDL.rtf
new file mode 100644
index 0000000..65059be
--- /dev/null
+++ b/example/data/ODC_PDDL.rtf
@@ -0,0 +1,331 @@
+{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf440
+{\fonttbl\f0\fswiss\fcharset77 Arial-BoldMS;\f1\fswiss\fcharset77 ArialMS;\f2\froman\fcharset77 TimesNewRomanMS;
+\f3\froman\fcharset77 TimesNewRomanItMS;\f4\froman\fcharset77 TimesNewRomanBdMS;}
+{\colortbl;\red255\green255\blue255;}
+\paperw11899\paperh16839\margl1134\margr1134\margb1134\margt1134\vieww7740\viewh9940\viewkind0
+\deftab709
+\pard\pardeftab709\sb240\sa120
+
+\f0\b\fs32 \cf0 PLEASE READ: \
+Open Data Commons is not a law firm and does not provide legal services of any kind.\
+\pard\pardeftab709\sb240\sa120
+
+\f1\b0 \cf0 Open Data Commons has no formal relationship with you. Your receipt of this document does not create any kind of agent-client relationship.\
+Please seek the advice of a suitably qualified legal professional licensed to practice in your jurisdiction before using this document.
+\f0\b \
+Use of this document.\
+Do not link to this document as a means of using it for your content. If you choose, after consulting with an appropriate legal professional, to use this document, please host it on your own site or apply it directly to the content you wish to be covered. You do so at your own risk.\
+No warranties and disclaimer of any damages.\
+
+\f1\b0 This information is provided \'d4as is\'d4, and this site makes no warranties on the information provided. Any damages resulting from its use are disclaimed.\
+\pard\pardeftab709\sa120
+
+\f2\fs24 \cf0 \
+\
+\
+\
+\
+\
+\
+\
+\
+\
+\
+*****\
+\
+\pard\pardeftab709\sb240\sa120
+
+\f0\b\fs32 \cf0 Open Data Commons \'d0 Public Domain Dedication & Licence (PDDL)\
+Preamble\
+\pard\pardeftab709\ql\qnatural
+
+\f2\b0\fs24 \cf0 The Open Data Commons \'d0 Public Domain Dedication & Licence is a document intended to allow you to freely share, modify, and use this work for any purpose and without any restrictions. This licence is intended for use on databases or their contents ("data"), either together or individually.\
+\pard\pardeftab709
+\cf0 \
+Many databases are covered by copyright. Some jurisdictions, mainly in Europe, have specific special rights that cover databases called the "sui generis" database right. Both of these sets of rights, as well as other legal rights used to protect databases and data, can create uncertainty or practical difficulty for those wishing to share databases and their underlying data but retain a limited amount of rights under a "some rights reserved" approach to licensing as outlined in the Scienc [...]
+\f3\i Community Norms
+\f2\i0  or similar statements of use of the database or data do not form a part of this document, and do not act as a contract for access or other terms of use for the database or data.\
+\
+\pard\pardeftab709\ql\qnatural
+
+\f4\b \cf0 The position of the recipient of the work\
+\pard\pardeftab709
+
+\f2\b0 \cf0 \
+\pard\pardeftab709\ql\qnatural
+\cf0 Because this document places the database and its contents in or as close as possible within the public domain, there are no restrictions or requirements placed on the recipient by this document. Recipients may use this work commercially, use technical protection measures, combine this data or database with other databases or data, and share their changes and additions or keep them secret. It is not a requirement that recipients provide further users with a copy of this licence or a [...]
+\pard\pardeftab709
+\cf0 \
+\pard\pardeftab709\ql\qnatural
+
+\f4\b \cf0 The position of the dedicator of the work\
+\pard\pardeftab709\ql\qnatural
+
+\f2\b0 \cf0 \
+\pard\pardeftab709
+\cf0 Copyright law, as with most other law under the banner of "intellectual property", is inherently national law. This means that there exists several differences in how copyright and other intellectual property rights can be relinquished, waived or licensed in the many legal jurisdictions of the world. This is despite much harmonisation of minimum levels of protection. The internet and other communication technologies span these many disparate legal jurisdictions and thus pose special [...]
+\
+The purpose of this document is to enable rightsholders to place their work into the public domain. Unlike licences for free and open source software, free cultural works, or open content licences, rightsholders will not be able to "dual license" their work by releasing the same work under different licences. This is because they have allowed anyone to use the work in whatever way they choose. Rightsholders therefore can't re-license it under copyright or database rights on different ter [...]
+\
+This document can cover either or both of the database and its contents (the data). Because databases can have a wide variety of content \'d0 not just factual data \'d0 rightsholders should use the Open Data Commons \'d0 Public Domain Dedication & Licence for an entire database and its contents only if everything can be placed under the terms of this document. Because even factual data can sometimes have intellectual property rights, rightsholders should use this licence to cover both th [...]
+\
+Rightsholders can also use this document to cover any copyright or database rights claims over only a database, and leave the contents to be covered by other licences or documents. They can do this because this document refers to the "Work", which can be either \'d0 or both \'d0 the database and its contents. As a result, rightsholders need to clearly state what they are dedicating under this document when they dedicate it.\
+\
+Just like any licence or other document dealing with intellectual property, rightsholders should be aware that one can only license what one owns. Please ensure that the rights have been cleared to make this material available under this document.\
+\
+This document permanently and irrevocably makes the Work available to the public for any use of any kind, and it should not be used unless the rightsholder is prepared for this to happen. \
+\pard\pardeftab709\ql\qnatural
+\cf0 \
+\pard\pardeftab709\sb240\sa120
+
+\f0\b\fs32 \cf0 Part I: Introduction\
+\pard\pardeftab709\sa120
+
+\f2\b0\fs24 \cf0 \
+The 
+\f4\b Rightsholder
+\f2\b0  (the Person holding rights or claims over the Work) agrees as follows: \
+\pard\pardeftab709\li283\fi-283\ri-6\sb240\sa120\ql\qnatural
+
+\f4\b\fs32 \cf0 1.0 	
+\f0 Definitions of Capitalised Words\
+\pard\pardeftab709\ql\qnatural
+
+\f4\fs24 \cf0 \
+"Copyright" 
+\f2\b0  \'d0 Includes rights under copyright and under neighbouring rights and similarly related sets of rights under the law of the relevant jurisdiction under Section 6.4.\
+\
+
+\f4\b "Data"
+\f2\b0  \'d0 The contents of the Database, which includes the information, independent works, or other material collected into the Database offered under the terms of this Document. \
+\pard\pardeftab709\sa120
+\cf0 \
+\pard\pardeftab709\ql\qnatural
+
+\f4\b \cf0 "Database" 
+\f2\b0 \'d0 A collection of Data arranged in a systematic or methodical way and individually accessible by electronic or other means offered under the terms of this Document. \
+\pard\pardeftab709
+\cf0 \
+\pard\pardeftab709\ql\qnatural
+
+\f4\b \cf0 "Database Right"
+\f2\b0   \'d0 Means rights over Data resulting from the Chapter III ("sui generis") rights in the Database Directive (Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases)  and any future updates as well as any similar rights available in the relevant jurisdiction under Section 6.4. 
+\f4\b \
+\pard\pardeftab709\ql\qnatural
+
+\f2\b0 \cf0 \
+\pard\pardeftab709\ql\qnatural
+
+\f4\b \cf0 "Document"
+\f2\b0   \'d0 means this relinquishment and waiver of rights and claims and back up licence agreement. \
+\
+
+\f4\b "Person"
+\f2\b0  \'d0 Means a natural or legal person or a body of persons corporate or incorporate.\
+\pard\pardeftab709
+\cf0 \
+\pard\pardeftab709
+
+\f4\b \cf0 "Use"
+\f2\b0  \'d0  As a verb, means doing any act that is restricted by Copyright or Database Rights whether in the original medium or any other; and includes modifying the Work as may be technically necessary to use it in a different mode or format.  This includes the right to sublicense the Work.\
+\
+
+\f4\b "Work"
+\f2\b0  \'d0 Means either or both of the Database and Data offered under the terms of this Document. \
+\pard\pardeftab709\ql\qnatural
+
+\f4\b \cf0 \
+\pard\pardeftab709
+\cf0 "You" 
+\f2\b0  \'d0 the Person acquiring rights under the licence elements of this Document.\
+\pard\pardeftab709\ql\qnatural
+\cf0 \
+\pard\pardeftab709\ql\qnatural
+
+\f4\b \cf0 Words in the singular include the plural and vice versa.\
+\pard\pardeftab709\li283\fi-283\ri-6\sb240\sa120\ql\qnatural
+
+\fs32 \cf0 2.0 	
+\f0 What this document covers\
+\pard\pardeftab709\ql\qnatural
+
+\f2\b0\fs24 \cf0 \
+\pard\pardeftab709
+\cf0 2.1. 
+\f4\b Legal effect of this Document.
+\f2\b0  This Document is:\
+\pard\pardeftab709\li705\ri-6\ql\qnatural
+\cf0 \
+\pard\pardeftab709\li660\fi20
+\cf0 a. A dedication to the public domain and waiver of Copyright and Database Rights over the Work; and\
+\pard\pardeftab709\li660\fi20\ql\qnatural
+\cf0 \
+\pard\pardeftab709\li660\fi20
+\cf0 b. A licence of Copyright and Database Rights over the Work in jurisdictions that do not allow for relinquishment or waiver.\
+\pard\pardeftab709\ql\qnatural
+\cf0 \
+2.2.
+\f4\b  Legal rights covered.
+\f2\b0 \
+\pard\pardeftab709\li690\ri-6\ql\qnatural
+\cf0 \
+ a. 
+\f4\b Copyright.
+\f2\b0  Any copyright or neighbouring rights in the Work. Copyright law varies between jurisdictions, but is likely to cover: the 
+\f3\i Database model or schema
+\f2\i0 , which is the structure, arrangement, and organisation of the Database, and can also include the Database tables and table indexes; the 
+\f3\i data entry and output sheets
+\f2\i0 ; and the 
+\f3\i Field names 
+\f2\i0 of Data stored in the Database. Copyright may also cover the Data depending on the jurisdiction and type of Data; and\
+\pard\pardeftab709
+\cf0 \
+\pard\pardeftab709\li690\ri-6\ql\qnatural
+\cf0  b. 
+\f4\b Database Rights.
+\f2\b0  Database Rights only extend to the extraction and re-utilisation of the whole or a substantial part of the Data. Database Rights can apply even when there is no copyright over the Database. Database Rights can also apply when the Data is removed from the Database and is selected and arranged in a way that would not infringe any applicable copyright.\
+\pard\pardeftab709\ql\qnatural
+\cf0 \
+2.2 
+\f4\b Rights not covered. 
+\f2\b0 \
+\pard\pardeftab709
+\cf0 \
+\pard\pardeftab709\li690\ri-6\ql\qnatural
+\cf0 a. This Document does not apply to computer programs used in the making or operation of the Database; \
+\pard\pardeftab709\li700
+\cf0 \
+b. This Document does not cover any patents over the Data or the Database. Please see Section 4.2 later in this Document for further details; and\
+\
+c. This Document does not cover any trade marks associated with the Database. Please see Section 4.3 later in this Document for further details.\
+\
+\pard\pardeftab709\ql\qnatural
+\cf0 Users of this Database are cautioned that they may have to clear other rights or consult other licences.\
+\pard\pardeftab709
+\cf0 \
+ 2.3 
+\f4\b Facts are free.
+\f2\b0  The Rightsholder takes the position that factual information is not covered by Copyright. This Document however covers the Work in jurisdictions that may protect the factual information in the Work by Copyright, and to cover any information protected by Copyright that is contained in the Work.\
+\pard\pardeftab709\sb240\sa120
+
+\f0\b\fs32 \cf0 Part II: Dedication to the public domain\
+\pard\pardeftab709\li283\fi-283\ri-6\sb240\sa120\ql\qnatural
+
+\f4 \cf0 3.0 	
+\f0 Dedication, waiver, and licence of Copyright and Database Rights\
+\pard\pardeftab709\ql\qnatural
+
+\f2\b0\fs24 \cf0 \
+\pard\pardeftab709
+\cf0 3.1 
+\f4\b Dedication of Copyright and Database Rights to the public domain.
+\f2\b0  The Rightsholder by using this Document, dedicates the Work to the public domain for the benefit of the public and relinquishes all rights in Copyright and Database Rights over the Work.\
+\
+\pard\pardeftab709\li630\ri-6\ql\qnatural
+\cf0 a. The Rightsholder realises that once these rights are relinquished, that the Rightsholder has no further rights in Copyright and Database Rights over the Work, and that the Work is free and open for others to Use.\
+\pard\pardeftab709
+\cf0 \
+b. The Rightsholder intends for their relinquishment to cover all present and future rights in the Work under Copyright and Database Rights, whether they are vested or contingent rights, and that this relinquishment of rights covers all their heirs and successors.\
+\pard\pardeftab709\ql\qnatural
+\cf0 \
+\pard\pardeftab709
+\cf0 The above relinquishment of rights applies worldwide and includes media and formats now known or created in the future.\
+\
+3.2 
+\f4\b Waiver of rights and claims in Copyright and Database Rights when Section 3.1 dedication inapplicable. 
+\f2\b0 If the dedication in Section 3.1 does not apply in the relevant jurisdiction under Section 6.4, the Rightsholder waives any rights and claims that the Rightsholder may have or acquire in the future over the Work in:\
+\
+\pard\pardeftab709\li630\ri-6\ql\qnatural
+\cf0 a. Copyright; and\
+\pard\pardeftab709
+\cf0 \
+\pard\pardeftab709\li560\fi-20
+\cf0 b. Database Rights.\
+\pard\pardeftab709\ql\qnatural
+\cf0 \
+\pard\pardeftab709
+\cf0  To the extent possible in the relevant jurisdiction, the above waiver of rights and claims applies worldwide and includes media and formats now known or created in the future. The Rightsholder agrees not to assert the above rights and waives the right to enforce them over the Work. \
+\
+ 3.3 
+\f4\b Licence of Copyright and Database Rights when Sections 3.1 and 3.2 inapplicable. 
+\f2\b0  If the dedication and waiver in Sections 3.1 and 3.2 does not apply in the relevant jurisdiction under Section 6.4, the Rightsholder and You agree as follows:\
+\
+\pard\pardeftab709\li630\ri-6\ql\qnatural
+\cf0 a. The Licensor grants to You a worldwide, royalty-free, non-exclusive, licence to Use the Work for the duration of any applicable Copyright and Database Rights. These rights explicitly include commercial use, and do not exclude any field of endeavour. To the extent possible in the relevant jurisdiction, these rights may be exercised in all media and formats whether now known or created in the future.\
+\pard\pardeftab709\ql\qnatural
+\cf0 \
+3.4 
+\f4\b Moral rights. 
+\f2\b0 This section covers moral rights, including the right to be identified as the author of the Work or to object to treatment that would otherwise prejudice the author's honour and reputation, or any other derogatory treatment:\
+\pard\pardeftab709\sa120
+\cf0 \
+\pard\pardeftab709\li615\ri-6\ql\qnatural
+\cf0 a. For jurisdictions allowing waiver of moral rights, Licensor waives all moral rights that Licensor may have in the Work to the fullest extent possible by the law of the relevant jurisdiction under Section 6.4; \
+\pard\pardeftab709\li560\fi-40\sa120
+\cf0 \
+b. If waiver of moral rights under Section 3.4 a in the relevant jurisdiction is not possible, Licensor agrees not to assert any moral rights over the Work and waives all claims in moral rights to the fullest extent possible by the law of the relevant jurisdiction under Section 6.4; and\
+\
+\pard\pardeftab709\li615\ri-6\ql\qnatural
+\cf0 c. For jurisdictions not allowing waiver or an agreement not to assert moral rights under Section 3.4 a and b, the author may retain their moral rights over the copyrighted aspects of the Work.\
+\pard\pardeftab709\ql\qnatural
+\cf0 \
+\pard\pardeftab709
+\cf0 Please note that some jurisdictions do not allow for the waiver of moral rights, and so moral rights may still subsist over the work in some jurisdictions.\
+\
+\pard\pardeftab709\li283\fi-283\ri-6\sb240\sa120\ql\qnatural
+
+\f4\b\fs32 \cf0 4.0 	
+\f0 Relationship to other rights\
+\pard\pardeftab709\ql\qnatural
+
+\f2\b0\fs24 \cf0 \
+ 4.1 
+\f4\b No other contractual conditions. 
+\f2\b0 The Rightsholder makes this Work available to You without any other contractual obligations, either express or implied. Any 
+\f3\i Community Norms 
+\f2\i0 statement associated with the Work is not a contract and does not form part of this Document.\
+\
+\pard\pardeftab709
+\cf0  4.2
+\f4\b  Relationship to patents. 
+\f2\b0 This Document does not grant You a licence for any patents that the Rightsholder may own. Users of this Database are cautioned that they may have to clear other rights or consult other licences.\
+\pard\pardeftab709\li675\ri-6\ql\qnatural
+\cf0 \
+\pard\pardeftab709\ql\qnatural
+\cf0  4.3 
+\f4\b Relationship to trade marks. 
+\f2\b0 This Document does not grant You a licence for any trade marks that the Rightsholder may own or that the Rightsholder may use to cover the Work. Users of this Database are cautioned that they may have to clear other rights or consult other licences.\
+\
+\pard\pardeftab709\sb240\sa120
+
+\f0\b\fs32 \cf0 Part III: General provisions\
+\pard\pardeftab709\li283\fi-283\ri-6\sb240\sa120\ql\qnatural
+
+\f4 \cf0 5.0 	
+\f0 Warranties, disclaimer, and limitation of liability\
+\pard\pardeftab709\ql\qnatural
+
+\f2\b0\fs24 \cf0 5.1 The Work is provided by the Rightsholder "as is" and without any warranty of any kind, either express or implied, whether of title, of accuracy or completeness, of the presence of absence of errors, of fitness for purpose, or otherwise. Some jurisdictions do not allow the exclusion of implied warranties, so this exclusion may not apply to You.\
+\pard\pardeftab709
+\cf0 \
+5.2 Subject to any liability that may not be excluded or limited by law, the Rightsholder is not \
+liable for, and expressly excludes, all liability for loss or damage however and whenever caused to anyone by any use under this Document, whether by You or by anyone else, and whether caused by any fault on the part of the Rightsholder or not. This exclusion of liability includes, but is not limited to, any special, incidental, consequential, punitive, or exemplary damages. This exclusion applies even if the Rightsholder has been advised of the possibility of such damages.\
+\
+5.3 If liability may not be excluded by law, it is limited to actual and direct financial loss to the extent it is caused by proved negligence on the part of the Rightsholder.\
+\pard\pardeftab709\li283\fi-283\ri-6\sb240\sa120\ql\qnatural
+
+\f4\b\fs32 \cf0 6.0 	
+\f0 General\
+\pard\pardeftab709\ql\qnatural
+
+\f2\b0\fs24 \cf0 \
+\pard\pardeftab709
+\cf0 6.1 If any provision of this Document is held to be invalid or unenforceable, that must not affect the validity or enforceability of the remainder of the terms of this Document. \
+\
+6.2 This Document is the entire agreement between the parties with respect to the Work covered here. It replaces any earlier understandings, agreements or representations with respect to the Work not specified here. \
+\
+\pard\pardeftab709\ql\qnatural
+\cf0 6.3 This Document does not affect any rights that You or anyone else may independently have under any applicable law to make any use of this Work, including (for jurisdictions where this Document is a licence) fair dealing, fair use, database exceptions, or any other legally recognised limitation or exception to infringement of copyright or other applicable laws. \
+\
+6.4 This Document takes effect in the relevant jurisdiction in which the Document terms are sought to be enforced. If the rights waived or granted under applicable law in the relevant jurisdiction includes additional rights not waived or granted under this Document, these additional rights are included in this Document in order to meet the intent of this Document.\
+}
\ No newline at end of file
diff --git a/example/data/README b/example/data/README
new file mode 100644
index 0000000..aec783c
--- /dev/null
+++ b/example/data/README
@@ -0,0 +1,19 @@
+These files are intended for testing CIFTI-2 reading code, identical to the ones in the v1.x zip files from https://www.nitrc.org/projects/cifti/.  There are 5 CIFTI-2 files, the other data files that were in the zip have been removed to save space, as they were in different (but related) formats.
+
+The files:
+
+Conte69.MyelinAndCorrThickness.32k_fs_LR.dscalar.nii
+Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii
+
+contain the same data, which are two fairly smooth maps, with somewhat different value distributions (MyelinMap_BC_decurv ranges from 1 to 2, while corrThickness ranges from 1 to 4.8).  If you see a pattern that alternates from one vertex to the next, you have probably read the data matrix incorrectly, which may happen if you previously read data from CIFTI-1, and used the same code.  The CIFTI-2 matrix data should be read as a standard NIFTI-2 file, it does not require the special treat [...]
+
+The "ones.dscalar.nii" is the only cifti file in the zip that contains voxel data.  All of the data values are 1.  The various volume components should align well with subcortical structures in "Conte69_AverageT1w_restore.nii.gz" from any of the v1.x zip files from https://www.nitrc.org/projects/cifti/.
+
+The files:
+
+Conte69.MyelinAndCorrThickness.32k_fs_LR.ptseries.nii
+Conte69.parcellations_VGD11b.32k_fs_LR.dlabel.nii
+
+should have the first map of the .dlabel.nii file align with the areas in the .ptseries.nii file that have values.  The values in the .ptseries.nii file should match the vertex averages over the regions they cover, from the same-index map in "Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii".
+
+These files are provided under the Open Data Commons Public Domain Dedication and Licence (PDDL), see ODC_PDDL.rtf and/or http://opendatacommons.org/licenses/pddl/1.0/ for details.
diff --git a/example/data/ones.dscalar.nii b/example/data/ones.dscalar.nii
new file mode 100644
index 0000000..1e01772
Binary files /dev/null and b/example/data/ones.dscalar.nii differ
diff --git a/example/rewrite.cxx b/example/rewrite.cxx
new file mode 100644
index 0000000..0bfbd6d
--- /dev/null
+++ b/example/rewrite.cxx
@@ -0,0 +1,61 @@
+#include "CiftiFile.h"
+
+#include <iostream>
+#include <vector>
+
+using namespace std;
+using namespace cifti;
+
+/**\file rewrite.cxx
+This program reads a Cifti file from argv[1], and writes it out to argv[2] with a second CiftiFile object.
+It uses on-disk reading and writing, so DO NOT have both filenames point to the same file,
+CiftiFile truncates without any warning when told to write to an existing file.
+
+\include rewrite.cxx
+*/
+
+int main(int argc, char** argv)
+{
+    if (argc < 3)
+    {
+        cout << "usage: " << argv[0] << " <input cifti> <output cifti> [<endian>]" << endl;
+        cout << "  rewrite the input cifti file to the output filename." << endl;
+        cout << "  endian can be 'LITTLE' or 'BIG', and uses native endianness if not specified" << endl;
+        return 1;
+    }
+    CiftiFile::ENDIAN myEndian = CiftiFile::NATIVE;
+    if (argc > 3)
+    {
+        if (AString(argv[3]) == "LITTLE")
+        {
+            myEndian = CiftiFile::LITTLE;
+        } else if (AString(argv[3]) == "BIG") {
+            myEndian = CiftiFile::BIG;
+        } else {
+            cerr << "unrecognized endianness string: " << argv[3] << endl;
+            return 1;
+        }
+    }
+    try
+    {
+        CiftiFile inputFile(argv[1]);//on-disk reading by default
+        //inputFile.convertToInMemory();//if you want to read it into memory first
+        CiftiFile outputFile;
+        outputFile.setWritingFile(argv[2], CiftiVersion(), myEndian);//sets up on-disk writing with default writing version, from CiftiVersion's default constructor
+        outputFile.setCiftiXML(inputFile.getCiftiXML());//the CiftiXML is how you access all the mapping information
+        const vector<int64_t>& dims = inputFile.getDimensions();
+        vector<float> scratchRow(dims[0]);//read/write a row at a time
+        for (MultiDimIterator<int64_t> iter = inputFile.getIteratorOverRows(); !iter.atEnd(); ++iter)
+        {//helper class to iterate over 2D and 3D cifti with the same code - the "+ 1" is to drop the first dimension (row length)
+            inputFile.getRow(scratchRow.data(), *iter);
+            outputFile.setRow(scratchRow.data(), *iter);
+        }
+        outputFile.writeFile(argv[2]);//because we called setWritingFile with this filename (and default cifti version), this will return immediately
+        //NOTE: if you call writeFile with a different writing version (takes its default from CiftiVersion constructor) than setWritingFile, it will rewrite the entire file after reading it into memory
+        //it will also rewrite it if you specify an endianness other than ANY (which is the default), and the endianness doesn't match what it was already written as (via setWritingFile)
+    } catch (CiftiException& e) {
+        cerr << "Caught CiftiException: " + AString_to_std_string(e.whatString()) << endl;
+        return 1;
+    }
+    return 0;
+}
diff --git a/example/xmlinfo.cxx b/example/xmlinfo.cxx
new file mode 100644
index 0000000..6958665
--- /dev/null
+++ b/example/xmlinfo.cxx
@@ -0,0 +1,106 @@
+#include "CiftiFile.h"
+
+#include <iostream>
+#include <vector>
+
+using namespace std;
+using namespace cifti;
+
+/**\file xmlinfo.cxx
+This program reads a Cifti file from argv[1], and prints out a summary of the XML.
+
+\include xmlinfo.cxx
+*/
+
+int main(int argc, char** argv)
+{
+    if (argc < 2)
+    {
+        cout << "this program requires one argument: an input cifti file" << endl;
+        return 1;
+    }
+    try
+    {
+        CiftiFile inputFile(argv[1]);//on-disk reading by default, and we only need the XML header anyway
+        const CiftiXML& myXML = inputFile.getCiftiXML();
+        for (int whichDim = 0; whichDim < myXML.getNumberOfDimensions(); ++whichDim)
+        {
+            cout << "Dimension " << whichDim << ": ";
+            switch (myXML.getMappingType(whichDim))
+            {
+                case CiftiMappingType::BRAIN_MODELS:
+                {
+                    const CiftiBrainModelsMap& myMap = myXML.getBrainModelsMap(whichDim);
+                    cout << "Brain Models, length " << myMap.getLength() << endl;
+                    vector<CiftiBrainModelsMap::ModelInfo> myInfo = myMap.getModelInfo();//this is only summary info, same order as the models are in the cifti indices
+                    for (int i = 0; i < (int)myInfo.size(); ++i)//to get the lists of vertices/voxels for a model, see getSurfaceMap, getVolumeStructureMap, and getFullVolumeMap
+                    {
+                        switch (myInfo[i].m_type)
+                        {
+                            case CiftiBrainModelsMap::SURFACE:
+                                cout << "   Surface " << AString_to_std_string(StructureEnum::toName(myInfo[i].m_structure)) << ": ";
+                                cout << myInfo[i].m_indexCount << " out of " << myMap.getSurfaceNumberOfNodes(myInfo[i].m_structure) << " vertices" << endl;
+                                break;
+                            case CiftiBrainModelsMap::VOXELS:
+                                cout << "   Voxels " << AString_to_std_string(StructureEnum::toName(myInfo[i].m_structure)) << ": ";
+                                cout << myInfo[i].m_indexCount << " voxels" << endl;
+                                break;
+                        }
+                    }
+                    break;
+                }
+                case CiftiMappingType::LABELS:
+                {
+                    const CiftiLabelsMap& myMap = myXML.getLabelsMap(whichDim);
+                    cout << "Labels, length " << myMap.getLength() << endl;
+                    for (int i = 0; i < myMap.getLength(); ++i)
+                    {
+                        cout << "   Index " << i << ": " << AString_to_std_string(myMap.getMapName(i)) << endl;
+                    }
+                    break;
+                }
+                case CiftiMappingType::PARCELS:
+                {
+                    const CiftiParcelsMap& myMap = myXML.getParcelsMap(whichDim);
+                    cout << "Parcels, length " << myMap.getLength() << endl;
+                    const vector<CiftiParcelsMap::Parcel>& myParcels = myMap.getParcels();
+                    for (int i = 0; i < (int)myParcels.size(); ++i)
+                    {
+                        cout << "   Index " << i << ", name '" << AString_to_std_string(myParcels[i].m_name) << "': ";
+                        int numVerts = 0;
+                        for (map<StructureEnum::Enum, set<int64_t> >::const_iterator iter = myParcels[i].m_surfaceNodes.begin(); iter != myParcels[i].m_surfaceNodes.end(); ++iter)
+                        {
+                            numVerts += iter->second.size();
+                        }
+                        cout << numVerts << " vertices, " << myParcels[i].m_voxelIndices.size() << " voxels" << endl;
+                    }
+                    break;
+                }
+                case CiftiMappingType::SCALARS:
+                {
+                    const CiftiScalarsMap& myMap = myXML.getScalarsMap(whichDim);
+                    cout << "Scalars, length " << myMap.getLength() << endl;
+                    for (int i = 0; i < myMap.getLength(); ++i)
+                    {
+                        cout << "   Index " << i << ": " << AString_to_std_string(myMap.getMapName(i)) << endl;
+                    }
+                    break;
+                }
+                case CiftiMappingType::SERIES:
+                {
+                    const CiftiSeriesMap& myMap = myXML.getSeriesMap(whichDim);
+                    cout << "Series, length " << myMap.getLength() << endl;
+                    cout << "   Start: " << myMap.getStart() << endl;
+                    cout << "   Step: " << myMap.getStep() << endl;
+                    cout << "   Unit: " << AString_to_std_string(CiftiSeriesMap::unitToString(myMap.getUnit())) << endl;
+                    break;
+                }
+            }
+            cout << endl;//extra line break between dimensions
+        }
+    } catch (CiftiException& e) {
+        cerr << "Caught CiftiException: " + AString_to_std_string(e.whatString()) << endl;
+        return 1;
+    }
+    return 0;
+}
diff --git a/mainpage.dox b/mainpage.dox
new file mode 100644
index 0000000..d3d82b3
--- /dev/null
+++ b/mainpage.dox
@@ -0,0 +1,40 @@
+/**
+\mainpage CiftiLib
+
+CiftiLib is a library for the CIFTI file format, as documented here: http://www.nitrc.org/projects/cifti/
+
+It builds with either QT, or libxml++ and libboost-filesystem.
+
+To use it, include CiftiFile.h, and use the CiftiFile class to read and write cifti files.
+
+Reading example:
+
+\code
+CiftiFile myFile(filename);//defaults to reading data on demand
+const CiftiXML& myXML = myFile.getCiftiXML();//mapping and dimension information
+vector<float> rowData(myXML.getDimensionLength(CiftiXML::ALONG_ROW));//allocate array for row data
+myFile.getRow(rowData.data(), 0);//read the first row
+\endcode
+
+Writing example:
+
+\code
+CiftiXML myXML;//first, need to set up the dimension mappings
+CiftiScalarsMap exampleMap;
+exampleMap.setLength(40);//just as an example
+myXML.setNumberOfDimensions(2);//2D matrix
+myXML.setMap(CiftiXML::ALONG_ROW, exampleMap);//set the mappings
+myXML.setMap(CiftiXML::ALONG_COLUMN, exampleMap);
+CiftiFile myFile;
+myFile.setWritingFile(filename);//optional: write rows as they are set
+myFile.setCiftiXML(myXML);//set the file's mappings
+myFile.setRow(somedata, 0);//write a row
+...
+myFile.writeFile(filename);//if filename matches what was given to setWritingFile, this does nothing
+\endcode
+
+See rewrite.cxx for a program that does a row-by-row copy that works on cifti of any dimensionality.
+
+See xmlinfo.cxx for a program that prints XML summary information of all dimensions of a cifti file.
+
+*/
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..bfd94fd
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,42 @@
+PROJECT(src)
+
+SET(HEADERS
+CiftiFile.h
+NiftiIO.h
+)
+
+SET(SOURCES
+CiftiFile.cxx
+NiftiIO.cxx
+)
+
+#only the headers that should end up in the top level of CiftiLib when installed
+#do not move this below the append_subdir_files calls
+SET(PUBLIC_HEADERS
+${HEADERS}
+)
+
+#list the subdirectories here, so each build system file only references one additional level
+SET(PRIVATE_DIRS
+Cifti
+Common
+Nifti
+)
+
+ADD_SUBDIRECTORY(Cifti)
+ADD_SUBDIRECTORY(Common)
+ADD_SUBDIRECTORY(Nifti)
+
+macro(append_subdir_files variable dirname)
+    get_directory_property(holder DIRECTORY ${dirname} DEFINITION ${variable})
+    foreach(depfile ${holder})
+        list(APPEND ${variable} "${dirname}/${depfile}")
+    endforeach()
+endmacro()
+
+append_subdir_files(SOURCES Nifti)
+append_subdir_files(HEADERS Nifti)
+append_subdir_files(SOURCES Cifti)
+append_subdir_files(HEADERS Cifti)
+append_subdir_files(SOURCES Common)
+append_subdir_files(HEADERS Common)
diff --git a/src/Cifti/CMakeLists.txt b/src/Cifti/CMakeLists.txt
new file mode 100644
index 0000000..f95c31c
--- /dev/null
+++ b/src/Cifti/CMakeLists.txt
@@ -0,0 +1,40 @@
+
+PROJECT(Cifti)
+
+SET(HEADERS
+CiftiXML.h
+CiftiVersion.h
+
+CiftiMappingType.h
+CiftiBrainModelsMap.h
+CiftiLabelsMap.h
+CiftiParcelsMap.h
+CiftiScalarsMap.h
+CiftiSeriesMap.h
+
+Label.h
+LabelTable.h
+MetaData.h
+
+StructureEnum.h
+VolumeSpace.h
+)
+
+SET(SOURCES
+CiftiXML.cxx
+CiftiVersion.cxx
+
+CiftiMappingType.cxx
+CiftiBrainModelsMap.cxx
+CiftiLabelsMap.cxx
+CiftiParcelsMap.cxx
+CiftiScalarsMap.cxx
+CiftiSeriesMap.cxx
+
+Label.cxx
+LabelTable.cxx
+MetaData.cxx
+
+StructureEnum.cxx
+VolumeSpace.cxx
+)
diff --git a/src/Cifti/CiftiBrainModelsMap.cxx b/src/Cifti/CiftiBrainModelsMap.cxx
new file mode 100644
index 0000000..39e65c6
--- /dev/null
+++ b/src/Cifti/CiftiBrainModelsMap.cxx
@@ -0,0 +1,1170 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiBrainModelsMap.h"
+
+#include "Common/CiftiException.h"
+
+#include <algorithm>
+
+using namespace std;
+using namespace cifti;
+
+void CiftiBrainModelsMap::addSurfaceModel(const int64_t& numberOfNodes, const StructureEnum::Enum& structure, const float* roi)
+{
+    vector<int64_t> tempVector;//pass-through to the other addSurfaceModel after converting roi to vector of indices
+    tempVector.reserve(numberOfNodes);//to make it allocate only once
+    for (int64_t i = 0; i < numberOfNodes; ++i)
+    {
+        if (roi == NULL || roi[i] > 0.0f)
+        {
+            tempVector.push_back(i);
+        }
+    }
+    addSurfaceModel(numberOfNodes, structure, tempVector);
+}
+
+void CiftiBrainModelsMap::addSurfaceModel(const int64_t& numberOfNodes, const StructureEnum::Enum& structure, const vector<int64_t>& nodeList)
+{
+    if (m_surfUsed.find(structure) != m_surfUsed.end())
+    {
+        throw CiftiException("surface structures cannot be repeated in a brain models map");
+    }
+    BrainModelPriv myModel;
+    myModel.m_type = SURFACE;
+    myModel.m_brainStructure = structure;
+    myModel.m_surfaceNumberOfNodes = numberOfNodes;
+    myModel.m_nodeIndices = nodeList;
+    myModel.setupSurface(getNextStart());//do internal setup - also does error checking
+    m_modelsInfo.push_back(myModel);
+    m_surfUsed[structure] = m_modelsInfo.size() - 1;
+}
+
+void CiftiBrainModelsMap::BrainModelPriv::setupSurface(const int64_t& start)
+{
+    if (m_surfaceNumberOfNodes < 1)
+    {
+        throw CiftiException("surface must have at least 1 vertex");
+    }
+    m_modelStart = start;
+    int64_t listSize = (int64_t)m_nodeIndices.size();
+    if (listSize == 0)
+    {
+        throw CiftiException("vertex list must have nonzero length");//NOTE: technically not required by Cifti-1, would need some rewriting to support
+    }
+    m_modelEnd = start + listSize;//one after last
+    vector<bool> used(m_surfaceNumberOfNodes, false);
+    m_nodeToIndexLookup = vector<int64_t>(m_surfaceNumberOfNodes, -1);//reset all to -1 to start
+    for (int64_t i = 0; i < listSize; ++i)
+    {
+        if (m_nodeIndices[i] < 0)
+        {
+            throw CiftiException("vertex list contains negative index");
+        }
+        if (m_nodeIndices[i] >= m_surfaceNumberOfNodes)
+        {
+            throw CiftiException("vertex list contains an index that don't exist in the surface");
+        }
+        if (used[m_nodeIndices[i]])
+        {
+            throw CiftiException("vertex list contains reused index");
+        }
+        used[m_nodeIndices[i]] = true;
+        m_nodeToIndexLookup[m_nodeIndices[i]] = start + i;
+    }
+}
+
+void CiftiBrainModelsMap::addVolumeModel(const StructureEnum::Enum& structure, const vector<int64_t>& ijkList)
+{
+    if (m_volUsed.find(structure) != m_volUsed.end())
+    {
+        throw CiftiException("volume structures cannot be repeated in a brain models map");
+    }
+    int64_t listSize = (int64_t)ijkList.size();
+    if (listSize == 0)
+    {
+        throw CiftiException("voxel list must have nonzero length");//NOTE: technically not required by Cifti-1, would need some rewriting to support
+    }
+    if (listSize % 3 != 0)
+    {
+        throw CiftiException("voxel list must have a length that is a multiple of 3");
+    }
+    int64_t numElems = listSize / 3;
+    const int64_t* dims = NULL;
+    if (!m_ignoreVolSpace)
+    {
+        if (!m_haveVolumeSpace)
+        {
+            throw CiftiException("you must set the volume space before adding volume models");
+        }
+        dims = m_volSpace.getDims();
+    }
+    Compact3DLookup<std::pair<int64_t, StructureEnum::Enum> > tempLookup = m_voxelToIndexLookup;//a copy of the lookup should be faster than other methods of checking for overlap and repeat
+    int64_t nextStart = getNextStart();
+    for (int64_t index = 0; index < numElems; ++index)//do all error checking before adding to lookup
+    {
+        int64_t index3 = index * 3;
+        if (ijkList[index3] < 0 || ijkList[index3 + 1] < 0 || ijkList[index3 + 2] < 0)
+        {
+            throw CiftiException("found negative index in voxel list");
+        }
+        if (!m_ignoreVolSpace && (ijkList[index3] >= dims[0] ||
+                                    ijkList[index3 + 1] >= dims[1] ||
+                                    ijkList[index3 + 2] >= dims[2]))
+        {
+            throw CiftiException("found invalid index triple in voxel list: (" + AString_number(ijkList[index3]) + ", "
+                                  + AString_number(ijkList[index3 + 1]) + ", " + AString_number(ijkList[index3 + 2]) + ")");
+        }
+        if (tempLookup.find(ijkList[index3], ijkList[index3 + 1], ijkList[index3 + 2]) != NULL)
+        {
+            throw CiftiException("volume models may not reuse voxels, either internally or from other structures");
+        }
+        tempLookup.at(ijkList[index3], ijkList[index3 + 1], ijkList[index3 + 2]) = pair<int64_t, StructureEnum::Enum>(nextStart + index, structure);
+    }
+    m_voxelToIndexLookup = tempLookup;
+    BrainModelPriv myModel;
+    myModel.m_type = VOXELS;
+    myModel.m_brainStructure = structure;
+    myModel.m_voxelIndicesIJK = ijkList;
+    myModel.m_modelStart = nextStart;
+    myModel.m_modelEnd = nextStart + numElems;//one after last
+    m_modelsInfo.push_back(myModel);
+    m_volUsed[structure] = m_modelsInfo.size() - 1;
+}
+
+void CiftiBrainModelsMap::clear()
+{
+    m_modelsInfo.clear();
+    m_haveVolumeSpace = false;
+    m_ignoreVolSpace = false;
+    m_voxelToIndexLookup.clear();
+    m_surfUsed.clear();
+    m_volUsed.clear();
+}
+
+int64_t CiftiBrainModelsMap::getIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const
+{
+    CiftiAssert(node >= 0);
+    map<StructureEnum::Enum, int>::const_iterator iter = m_surfUsed.find(structure);
+    if (iter == m_surfUsed.end())
+    {
+        return -1;
+    }
+    CiftiAssertVectorIndex(m_modelsInfo, iter->second);
+    const BrainModelPriv& myModel = m_modelsInfo[iter->second];
+    if (node >= myModel.m_surfaceNumberOfNodes) return -1;
+    CiftiAssertVectorIndex(myModel.m_nodeToIndexLookup, node);
+    return myModel.m_nodeToIndexLookup[node];
+}
+
+int64_t CiftiBrainModelsMap::getIndexForVoxel(const int64_t* ijk, StructureEnum::Enum* structureOut) const
+{
+    return getIndexForVoxel(ijk[0], ijk[1], ijk[2], structureOut);
+}
+
+int64_t CiftiBrainModelsMap::getIndexForVoxel(const int64_t& i, const int64_t& j, const int64_t& k, StructureEnum::Enum* structureOut) const
+{
+    const pair<int64_t, StructureEnum::Enum>* iter = m_voxelToIndexLookup.find(i, j, k);//the lookup tolerates weirdness like negatives
+    if (iter == NULL) return -1;
+    if (structureOut != NULL) *structureOut = iter->second;
+    return iter->first;
+}
+
+CiftiBrainModelsMap::IndexInfo CiftiBrainModelsMap::getInfoForIndex(const int64_t index) const
+{
+    CiftiAssert(index >= 0 && index < getLength());
+    IndexInfo ret;
+    int numModels = (int)m_modelsInfo.size();
+    int low = 0, high = numModels - 1;//bisection search
+    while (low != high)
+    {
+        int guess = (low + high) / 2;
+        if (m_modelsInfo[guess].m_modelEnd > index)//modelEnd is 1 after last valid index, equal to next start if there is a next
+        {
+            if (m_modelsInfo[guess].m_modelStart > index)
+            {
+                high = guess - 1;
+            } else {
+                high = guess;
+                low = guess;
+            }
+        } else {
+            low = guess + 1;
+        }
+    }
+    CiftiAssert(index >= m_modelsInfo[low].m_modelStart && index < m_modelsInfo[low].m_modelEnd);//otherwise we have a broken invariant
+    ret.m_structure = m_modelsInfo[low].m_brainStructure;
+    ret.m_type = m_modelsInfo[low].m_type;
+    if (ret.m_type == SURFACE)
+    {
+        ret.m_surfaceNode = m_modelsInfo[low].m_nodeIndices[index - m_modelsInfo[low].m_modelStart];
+    } else {
+        int64_t baseIndex = 3 * (index - m_modelsInfo[low].m_modelStart);
+        ret.m_ijk[0] = m_modelsInfo[low].m_voxelIndicesIJK[baseIndex];
+        ret.m_ijk[1] = m_modelsInfo[low].m_voxelIndicesIJK[baseIndex + 1];
+        ret.m_ijk[2] = m_modelsInfo[low].m_voxelIndicesIJK[baseIndex + 2];
+    }
+    return ret;
+}
+
+int64_t CiftiBrainModelsMap::getLength() const
+{
+    return getNextStart();
+}
+
+vector<CiftiBrainModelsMap::ModelInfo> CiftiBrainModelsMap::getModelInfo() const
+{
+    vector<ModelInfo> ret;
+    int numModels = (int)m_modelsInfo.size();
+    ret.resize(numModels);
+    for (int i = 0; i < numModels; ++i)
+    {
+        ret[i].m_structure = m_modelsInfo[i].m_brainStructure;
+        ret[i].m_type = m_modelsInfo[i].m_type;
+        ret[i].m_indexStart = m_modelsInfo[i].m_modelStart;
+        ret[i].m_indexCount = m_modelsInfo[i].m_modelEnd - m_modelsInfo[i].m_modelStart;
+    }
+    return ret;
+}
+
+int64_t CiftiBrainModelsMap::getNextStart() const
+{
+    if (m_modelsInfo.size() == 0) return 0;
+    return m_modelsInfo.back().m_modelEnd;//NOTE: the models are sorted by their index range, so this works
+}
+
+const vector<int64_t>& CiftiBrainModelsMap::getNodeList(const StructureEnum::Enum& structure) const
+{
+    map<StructureEnum::Enum, int>::const_iterator iter = m_surfUsed.find(structure);
+    if (iter == m_surfUsed.end())
+    {
+        throw CiftiException("getNodeList called for nonexistant structure");//throw if it doesn't exist, because we don't have a reference to return - things should identify which structures exist before calling this
+    }
+    CiftiAssertVectorIndex(m_modelsInfo, iter->second);
+    return m_modelsInfo[iter->second].m_nodeIndices;
+}
+
+vector<CiftiBrainModelsMap::SurfaceMap> CiftiBrainModelsMap::getSurfaceMap(const StructureEnum::Enum& structure) const
+{
+    vector<SurfaceMap> ret;
+    map<StructureEnum::Enum, int>::const_iterator iter = m_surfUsed.find(structure);
+    if (iter == m_surfUsed.end())
+    {
+        throw CiftiException("getSurfaceMap called for nonexistant structure");//also throw, for consistency
+    }
+    CiftiAssertVectorIndex(m_modelsInfo, iter->second);
+    const BrainModelPriv& myModel = m_modelsInfo[iter->second];
+    int64_t numUsed = (int64_t)myModel.m_nodeIndices.size();
+    ret.resize(numUsed);
+    for (int64_t i = 0; i < numUsed; ++i)
+    {
+        ret[i].m_ciftiIndex = myModel.m_modelStart + i;
+        ret[i].m_surfaceNode = myModel.m_nodeIndices[i];
+    }
+    return ret;
+}
+
+int64_t CiftiBrainModelsMap::getSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const
+{
+    map<StructureEnum::Enum, int>::const_iterator iter = m_surfUsed.find(structure);
+    if (iter == m_surfUsed.end())
+    {
+        return -1;
+    }
+    CiftiAssertVectorIndex(m_modelsInfo, iter->second);
+    const BrainModelPriv& myModel = m_modelsInfo[iter->second];
+    return myModel.m_surfaceNumberOfNodes;
+}
+
+vector<StructureEnum::Enum> CiftiBrainModelsMap::getSurfaceStructureList() const
+{
+    vector<StructureEnum::Enum> ret;
+    ret.reserve(m_surfUsed.size());//we can use this to tell us how many there are, but it has reordered them
+    int numModels = (int)m_modelsInfo.size();
+    for (int i = 0; i < numModels; ++i)//we need them in the order they occur in
+    {
+        if (m_modelsInfo[i].m_type == SURFACE)
+        {
+            ret.push_back(m_modelsInfo[i].m_brainStructure);
+        }
+    }
+    return ret;
+}
+
+bool CiftiBrainModelsMap::hasSurfaceData(const StructureEnum::Enum& structure) const
+{
+    map<StructureEnum::Enum, int>::const_iterator iter = m_surfUsed.find(structure);
+    return (iter != m_surfUsed.end());
+}
+
+vector<CiftiBrainModelsMap::VolumeMap> CiftiBrainModelsMap::getFullVolumeMap() const
+{
+    vector<VolumeMap> ret;
+    int numModels = (int)m_modelsInfo.size();
+    for (int i = 0; i < numModels; ++i)
+    {
+        if (m_modelsInfo[i].m_type == VOXELS)
+        {
+            const BrainModelPriv& myModel = m_modelsInfo[i];
+            int64_t listSize = (int64_t)myModel.m_voxelIndicesIJK.size();
+            CiftiAssert(listSize % 3 == 0);
+            int64_t numUsed = listSize / 3;
+            if (ret.size() == 0) ret.reserve(numUsed);//keep it from doing multiple expansion copies on the first model
+            for (int64_t i = 0; i < numUsed; ++i)
+            {
+                int64_t i3 = i * 3;
+                VolumeMap temp;
+                temp.m_ciftiIndex = myModel.m_modelStart + i;
+                temp.m_ijk[0] = myModel.m_voxelIndicesIJK[i3];
+                temp.m_ijk[1] = myModel.m_voxelIndicesIJK[i3 + 1];
+                temp.m_ijk[2] = myModel.m_voxelIndicesIJK[i3 + 2];
+                ret.push_back(temp);
+            }
+        }
+    }
+    return ret;
+}
+
+const VolumeSpace& CiftiBrainModelsMap::getVolumeSpace() const
+{
+    CiftiAssert(!m_ignoreVolSpace);
+    if (!m_haveVolumeSpace)
+    {
+        throw CiftiException("getVolumeSpace called when no volume space exists");
+    }
+    return m_volSpace;
+}
+
+vector<StructureEnum::Enum> CiftiBrainModelsMap::getVolumeStructureList() const
+{
+    vector<StructureEnum::Enum> ret;
+    ret.reserve(m_volUsed.size());//we can use this to tell us how many there are, but it has reordered them
+    int numModels = (int)m_modelsInfo.size();
+    for (int i = 0; i < numModels; ++i)//we need them in the order they occur in
+    {
+        if (m_modelsInfo[i].m_type == VOXELS)
+        {
+            ret.push_back(m_modelsInfo[i].m_brainStructure);
+        }
+    }
+    return ret;
+}
+
+vector<CiftiBrainModelsMap::VolumeMap> CiftiBrainModelsMap::getVolumeStructureMap(const StructureEnum::Enum& structure) const
+{
+    vector<VolumeMap> ret;
+    map<StructureEnum::Enum, int>::const_iterator iter = m_volUsed.find(structure);
+    if (iter == m_volUsed.end())
+    {
+        throw CiftiException("getVolumeStructureMap called for nonexistant structure");//also throw, for consistency
+    }
+    CiftiAssertVectorIndex(m_modelsInfo, iter->second);
+    const BrainModelPriv& myModel = m_modelsInfo[iter->second];
+    int64_t listSize = (int64_t)myModel.m_voxelIndicesIJK.size();
+    CiftiAssert(listSize % 3 == 0);
+    int64_t numUsed = listSize / 3;
+    ret.resize(numUsed);
+    for (int64_t i = 0; i < numUsed; ++i)
+    {
+        int64_t i3 = i * 3;
+        ret[i].m_ciftiIndex = myModel.m_modelStart + i;
+        ret[i].m_ijk[0] = myModel.m_voxelIndicesIJK[i3];
+        ret[i].m_ijk[1] = myModel.m_voxelIndicesIJK[i3 + 1];
+        ret[i].m_ijk[2] = myModel.m_voxelIndicesIJK[i3 + 2];
+    }
+    return ret;
+}
+
+const vector<int64_t>& CiftiBrainModelsMap::getVoxelList(const StructureEnum::Enum& structure) const
+{
+    map<StructureEnum::Enum, int>::const_iterator iter = m_volUsed.find(structure);
+    if (iter == m_volUsed.end())
+    {
+        throw CiftiException("getVoxelList called for nonexistant structure");//throw if it doesn't exist, because we don't have a reference to return - things should identify which structures exist before calling this
+    }
+    CiftiAssertVectorIndex(m_modelsInfo, iter->second);
+    return m_modelsInfo[iter->second].m_voxelIndicesIJK;
+}
+
+bool CiftiBrainModelsMap::hasVolumeData() const
+{
+    return (m_volUsed.size() != 0);
+}
+
+bool CiftiBrainModelsMap::hasVolumeData(const StructureEnum::Enum& structure) const
+{
+    map<StructureEnum::Enum, int>::const_iterator iter = m_volUsed.find(structure);
+    return (iter != m_volUsed.end());
+}
+
+void CiftiBrainModelsMap::setVolumeSpace(const VolumeSpace& space)
+{
+    for (map<StructureEnum::Enum, int>::const_iterator iter = m_volUsed.begin(); iter != m_volUsed.end(); ++iter)//the main time this loop isn't empty is parsing cifti-1
+    {
+        CiftiAssertVectorIndex(m_modelsInfo, iter->second);
+        const BrainModelPriv& myModel = m_modelsInfo[iter->second];
+        int64_t listSize = (int64_t)myModel.m_voxelIndicesIJK.size();
+        CiftiAssert(listSize % 3 == 0);
+        for (int64_t i3 = 0; i3 < listSize; i3 += 3)
+        {
+            if (!space.indexValid(myModel.m_voxelIndicesIJK[i3], myModel.m_voxelIndicesIJK[i3 + 1], myModel.m_voxelIndicesIJK[i3 + 2]))
+            {
+                throw CiftiException("invalid voxel found for volume space");
+            }
+        }
+    }
+    m_ignoreVolSpace = false;
+    m_haveVolumeSpace = true;
+    m_volSpace = space;
+}
+
+bool CiftiBrainModelsMap::operator==(const CiftiMappingType& rhs) const
+{
+    if (rhs.getType() != getType()) return false;
+    const CiftiBrainModelsMap& myrhs = dynamic_cast<const CiftiBrainModelsMap&>(rhs);
+    CiftiAssert(!m_ignoreVolSpace && !myrhs.m_ignoreVolSpace);//these should only be true while in the process of parsing cifti-1, never otherwise
+    if (m_haveVolumeSpace != myrhs.m_haveVolumeSpace) return false;
+    if (m_haveVolumeSpace && (m_volSpace != myrhs.m_volSpace)) return false;
+    return (m_modelsInfo == myrhs.m_modelsInfo);//NOTE: these are sorted by index range, so this works
+}
+
+bool CiftiBrainModelsMap::approximateMatch(const CiftiMappingType& rhs, AString* explanation) const
+{
+    if (rhs.getType() != getType())
+    {
+        if (explanation != NULL) *explanation = CiftiMappingType::mappingTypeToName(rhs.getType()) + " mapping never matches " + CiftiMappingType::mappingTypeToName(getType());
+        return false;
+    }
+    const CiftiBrainModelsMap& myrhs = dynamic_cast<const CiftiBrainModelsMap&>(rhs);//there is no user-specified metadata, but we want informative messages, so copy and modify the code from ==
+    CiftiAssert(!m_ignoreVolSpace && !myrhs.m_ignoreVolSpace);//these should only be true while in the process of parsing cifti-1, never otherwise
+    if (m_haveVolumeSpace != myrhs.m_haveVolumeSpace)
+    {
+        if (explanation != NULL) *explanation = "one of the mappings has no volume data";
+        return false;
+    }
+    if (m_haveVolumeSpace && (m_volSpace != myrhs.m_volSpace))
+    {
+        if (explanation != NULL) *explanation = "mappings have a different volume space";
+        return false;
+    }
+    if (m_modelsInfo != myrhs.m_modelsInfo)
+    {
+        if (explanation != NULL) *explanation = "mappings include different brainordinates";
+        return false;
+    }
+    return true;
+}
+
+bool CiftiBrainModelsMap::BrainModelPriv::operator==(const BrainModelPriv& rhs) const
+{
+    if (m_brainStructure != rhs.m_brainStructure) return false;
+    if (m_type != rhs.m_type) return false;
+    if (m_modelStart != rhs.m_modelStart) return false;
+    if (m_modelEnd != rhs.m_modelEnd) return false;
+    if (m_type == SURFACE)
+    {
+        if (m_surfaceNumberOfNodes != rhs.m_surfaceNumberOfNodes) return false;
+        int64_t listSize = (int64_t)m_nodeIndices.size();
+        CiftiAssert((int64_t)rhs.m_nodeIndices.size() == listSize);//this should already be checked by start/end above
+        for (int64_t i = 0; i < listSize; ++i)
+        {
+            if (m_nodeIndices[i] != rhs.m_nodeIndices[i]) return false;
+        }
+    } else {
+        int64_t listSize = (int64_t)m_voxelIndicesIJK.size();
+        CiftiAssert((int64_t)rhs.m_voxelIndicesIJK.size() == listSize);//this should already be checked by start/end above
+        for (int64_t i = 0; i < listSize; ++i)
+        {
+            if (m_voxelIndicesIJK[i] != rhs.m_voxelIndicesIJK[i]) return false;
+        }
+    }
+    return true;
+}
+
+void CiftiBrainModelsMap::readXML1(XmlReader& xml)
+{
+    clear();
+    m_ignoreVolSpace = true;//because in cifti-1, the volume space is not in this element - so, we rely on CiftiXML to check for volume data, and set the volume space afterwards
+    vector<ParseHelperModel> parsedModels;
+#ifdef CIFTILIB_USE_QT
+    for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext())
+    {
+        switch (xml.tokenType())
+        {
+            case QXmlStreamReader::StartElement:
+            {
+                QStringRef name = xml.name();
+                if (name != "BrainModel")
+                {
+                    throw CiftiException("unexpected element in brain models map: " + name.toString());
+                }
+                ParseHelperModel thisModel;
+                thisModel.parseBrainModel1(xml);
+                if (xml.hasError()) return;
+                parsedModels.push_back(thisModel);
+                break;//the readNext in the for will remove the BrainModel end element
+            }
+            default:
+                break;
+        }
+    }
+    if (xml.hasError()) return;
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "BrainModel")
+                {
+                    ParseHelperModel thisModel;
+                    thisModel.parseBrainModel1(xml);
+                    parsedModels.push_back(thisModel);
+                } else {
+                    throw CiftiException("unexpected element in brain models map: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+    sort(parsedModels.begin(), parsedModels.end());
+    int64_t numModels = (int64_t)parsedModels.size();//because we haven't checked them for unique values of BrainStructure yet...yeah, its paranoid
+    int64_t curOffset = 0;
+    for (int64_t i = 0; i < numModels; ++i)
+    {
+        if (parsedModels[i].m_offset != curOffset)
+        {
+            if (parsedModels[i].m_offset < curOffset)
+            {
+                throw CiftiException("models overlap at index " + AString_number(parsedModels[i].m_offset) + ", model " + AString_number(i));
+            } else {
+                throw CiftiException("index " + AString_number(curOffset) + " is not assigned to any model");
+            }
+        }
+        curOffset += parsedModels[i].m_count;
+    }
+    for (int64_t i = 0; i < numModels; ++i)
+    {
+        if (parsedModels[i].m_type == SURFACE)
+        {
+            addSurfaceModel(parsedModels[i].m_surfaceNumberOfNodes,
+                            parsedModels[i].m_brainStructure,
+                            parsedModels[i].m_nodeIndices);
+        } else {
+            addVolumeModel(parsedModels[i].m_brainStructure,
+                           parsedModels[i].m_voxelIndicesIJK);
+        }
+    }
+    m_ignoreVolSpace = false;//in case there are no voxels, but some will be added later
+}
+
+void CiftiBrainModelsMap::readXML2(XmlReader& xml)
+{
+    clear();
+    vector<ParseHelperModel> parsedModels;
+#ifdef CIFTILIB_USE_QT
+    for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext())
+    {
+        switch (xml.tokenType())
+        {
+            case QXmlStreamReader::StartElement:
+            {
+                QStringRef name = xml.name();
+                if (name == "BrainModel")
+                {
+                    ParseHelperModel thisModel;
+                    thisModel.parseBrainModel2(xml);
+                    if (xml.hasError()) break;
+                    parsedModels.push_back(thisModel);
+                } else if (name == "Volume") {
+                    if (m_haveVolumeSpace)
+                    {
+                        throw CiftiException("Volume specified more than once in Brain Models mapping type");
+                    } else {
+                        m_volSpace.readCiftiXML2(xml);
+                        if (xml.hasError()) return;
+                        m_haveVolumeSpace = true;
+                    }
+                } else {
+                    throw CiftiException("unexpected element in brain models map: " + name.toString());
+                }
+                break;//the readNext in the for will remove the BrainModel or Volume end element
+            }
+            default:
+                break;
+        }
+    }
+    if (xml.hasError()) return;
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "BrainModel")
+                {
+                    ParseHelperModel thisModel;
+                    thisModel.parseBrainModel2(xml);
+                    parsedModels.push_back(thisModel);
+                } else if (name == "Volume") {
+                    if (m_haveVolumeSpace)
+                    {
+                        throw CiftiException("Volume specified more than once in Brain Models mapping type");
+                    } else {
+                        m_volSpace.readCiftiXML2(xml);
+                        m_haveVolumeSpace = true;
+                    }
+                } else {
+                    throw CiftiException("unexpected element in brain models map: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+    sort(parsedModels.begin(), parsedModels.end());
+    int64_t numModels = (int64_t)parsedModels.size();//because we haven't checked them for unique values of BrainStructure yet...yeah, its paranoid
+    int64_t curOffset = 0;
+    for (int64_t i = 0; i < numModels; ++i)
+    {
+        if (parsedModels[i].m_offset != curOffset)
+        {
+            if (parsedModels[i].m_offset < curOffset)
+            {
+                throw CiftiException("models overlap at index " + AString_number(parsedModels[i].m_offset) + ", model " + AString_number(i));
+            } else {
+                throw CiftiException("index " + AString_number(curOffset) + " is not assigned to any model");
+            }
+        }
+        curOffset += parsedModels[i].m_count;
+    }
+    for (int64_t i = 0; i < numModels; ++i)
+    {
+        if (parsedModels[i].m_type == SURFACE)
+        {
+            addSurfaceModel(parsedModels[i].m_surfaceNumberOfNodes,
+                            parsedModels[i].m_brainStructure,
+                            parsedModels[i].m_nodeIndices);
+        } else {
+            addVolumeModel(parsedModels[i].m_brainStructure,
+                           parsedModels[i].m_voxelIndicesIJK);
+        }
+    }
+}
+
+void CiftiBrainModelsMap::ParseHelperModel::parseBrainModel1(XmlReader& xml)
+{
+    vector<AString> mandAttrs(4), optAttrs(1, "SurfaceNumberOfNodes");
+    mandAttrs[0] = "ModelType";
+    mandAttrs[1] = "BrainStructure";
+    mandAttrs[2] = "IndexOffset";
+    mandAttrs[3] = "IndexCount";
+    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs, optAttrs);
+    if (myAttrs.mandatoryVals[0] == "CIFTI_MODEL_TYPE_SURFACE")
+    {
+        m_type = SURFACE;
+    } else if (myAttrs.mandatoryVals[0] == "CIFTI_MODEL_TYPE_VOXELS") {
+        m_type = VOXELS;
+    } else {
+        throw CiftiException("invalid value for ModelType: " + myAttrs.mandatoryVals[0]);
+    }
+    bool ok = false;
+    m_brainStructure = StructureEnum::fromCiftiName(myAttrs.mandatoryVals[1], &ok);
+    if (!ok)
+    {
+        throw CiftiException("invalid value for BrainStructure: " + myAttrs.mandatoryVals[1]);
+    }
+    m_offset = AString_toInt(myAttrs.mandatoryVals[2], ok);
+    if (!ok || m_offset < 0)
+    {
+        throw CiftiException("IndexOffset must be a non-negative integer");
+    }
+    m_count = AString_toInt(myAttrs.mandatoryVals[3], ok);
+    if (!ok || m_count < 1)//NOTE: not technically required by cifti-1, would need some rewriting to support empty brainmodels
+    {
+        throw CiftiException("IndexCount must be a positive integer");
+    }
+    if (m_type == SURFACE)
+    {
+        if (!myAttrs.optionalVals[0].present)//actually conditionally required, not optional
+        {
+            throw CiftiException("BrainModel missing required attribute SurfaceNumberOfNodes");
+        }
+        m_surfaceNumberOfNodes = AString_toInt(myAttrs.optionalVals[0].value, ok);
+        if (!ok || m_surfaceNumberOfNodes < 1)
+        {
+            throw CiftiException("SurfaceNumberOfNodes must be a positive integer");
+        }
+#ifdef CIFTILIB_USE_QT
+        if (!xml.readNextStartElement())//special case in cifti-1
+        {
+            m_nodeIndices.resize(m_count);
+            for (int64_t i = 0; i < m_count; ++i)
+            {
+                m_nodeIndices[i] = i;
+            }
+        } else {
+            if (xml.name() != "NodeIndices")
+            {
+                throw CiftiException("unexpected element in BrainModel of SURFACE type: " + xml.name().toString());
+            }
+            m_nodeIndices = readIndexArray(xml);
+            xml.readNext();//remove the end element of NodeIndices
+        }
+        if (xml.hasError()) return;
+#else
+#ifdef CIFTILIB_USE_XMLPP
+        bool haveNodeIndices = false, done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+        while(!done && xml.read())
+        {
+            switch (xml.get_node_type())
+            {
+                case XmlReader::Element:
+                {
+                    AString name = xml.get_local_name();
+                    if (name == "NodeIndices")
+                    {
+                        if (haveNodeIndices)
+                        {
+                            throw CiftiException("NodeIndices element may only be specified once");
+                        }
+                        m_nodeIndices = readIndexArray(xml);
+                        haveNodeIndices = true;
+                    } else {
+                        throw CiftiException("unexpected element in BrainModel: " + name);
+                    }
+                    break;
+                }
+                case XmlReader::EndElement:
+                    done = true;
+                    break;
+                default:
+                    break;
+            }
+        }
+        if (!haveNodeIndices)//special case in cifti-1
+        {
+            m_nodeIndices.resize(m_count);
+            for (int64_t i = 0; i < m_count; ++i)
+            {
+                m_nodeIndices[i] = i;
+            }
+        }
+#else
+#error "not implemented"
+#endif
+#endif
+        if ((int64_t)m_nodeIndices.size() != m_count)
+        {
+            throw CiftiException("number of vertex indices does not match IndexCount");
+        }
+    } else {
+#ifdef CIFTILIB_USE_QT
+        if (!xml.readNextStartElement())
+        {
+            throw CiftiException("BrainModel requires a child element");
+        }
+        if (xml.name() != "VoxelIndicesIJK")
+        {
+            throw CiftiException("unexpected element in BrainModel of VOXELS type: " + xml.name().toString());
+        }
+        m_voxelIndicesIJK = readIndexArray(xml);
+        if (xml.hasError()) return;
+        xml.readNext();//remove the end element of VoxelIndicesIJK
+#else
+#ifdef CIFTILIB_USE_XMLPP
+        bool haveVoxelIndices = false, done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+        while(!done && xml.read())
+        {
+            switch (xml.get_node_type())
+            {
+                case XmlReader::Element:
+                {
+                    AString name = xml.get_local_name();
+                    if (name == "VoxelIndicesIJK")
+                    {
+                        if (haveVoxelIndices)
+                        {
+                            throw CiftiException("VoxelIndicesIJK element may only be specified once");
+                        }
+                        m_voxelIndicesIJK = readIndexArray(xml);
+                        haveVoxelIndices = true;
+                    } else {
+                        throw CiftiException("unexpected element in BrainModel: " + name);
+                    }
+                    break;
+                }
+                case XmlReader::EndElement:
+                    done = true;
+                    break;
+                default:
+                    break;
+            }
+        }
+        if (!haveVoxelIndices)//special case in cifti-1
+        {
+            throw CiftiException("BrainModel requires a child element");
+        }
+#else
+#error "not implemented"
+#endif
+#endif
+        if (m_voxelIndicesIJK.size() % 3 != 0)
+        {
+            throw CiftiException("number of voxel indices is not a multiple of 3");
+        }
+        if ((int64_t)m_voxelIndicesIJK.size() != m_count * 3)
+        {
+            throw CiftiException("number of voxel indices does not match IndexCount");
+        }
+    }
+#ifdef CIFTILIB_USE_QT
+    while (!xml.atEnd() && !xml.isEndElement())//locate the end element of BrainModel
+    {
+        switch(xml.readNext())
+        {
+            case QXmlStreamReader::StartElement:
+                throw CiftiException("unexpected second element in BrainModel: " + xml.name().toString());
+            default:
+                break;
+        }
+    }
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "BrainModel"));
+}
+
+void CiftiBrainModelsMap::ParseHelperModel::parseBrainModel2(XmlReader& xml)
+{
+    vector<AString> mandAttrs(4), optAttrs(1, "SurfaceNumberOfVertices");
+    mandAttrs[0] = "ModelType";
+    mandAttrs[1] = "BrainStructure";
+    mandAttrs[2] = "IndexOffset";
+    mandAttrs[3] = "IndexCount";
+    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs, optAttrs);
+    if (myAttrs.mandatoryVals[0] == "CIFTI_MODEL_TYPE_SURFACE")
+    {
+        m_type = SURFACE;
+    } else if (myAttrs.mandatoryVals[0] == "CIFTI_MODEL_TYPE_VOXELS") {
+        m_type = VOXELS;
+    } else {
+        throw CiftiException("invalid value for ModelType: " + myAttrs.mandatoryVals[0]);
+    }
+    bool ok = false;
+    m_brainStructure = StructureEnum::fromCiftiName(myAttrs.mandatoryVals[1], &ok);
+    if (!ok)
+    {
+        throw CiftiException("invalid value for BrainStructure: " + myAttrs.mandatoryVals[1]);
+    }
+    m_offset = AString_toInt(myAttrs.mandatoryVals[2], ok);
+    if (!ok || m_offset < 0)
+    {
+        throw CiftiException("IndexOffset must be a non-negative integer");
+    }
+    m_count = AString_toInt(myAttrs.mandatoryVals[3], ok);
+    if (!ok || m_count < 1)
+    {
+        throw CiftiException("IndexCount must be a positive integer");
+    }
+    if (m_type == SURFACE)
+    {
+        if (!myAttrs.optionalVals[0].present)//actually conditionally required, not optional
+        {
+            throw CiftiException("BrainModel missing required attribute SurfaceNumberOfVertices");
+        }
+        m_surfaceNumberOfNodes = AString_toInt(myAttrs.optionalVals[0].value, ok);
+        if (!ok || m_surfaceNumberOfNodes < 1)
+        {
+            throw CiftiException("SurfaceNumberOfVertices must be a positive integer");
+        }
+#ifdef CIFTILIB_USE_QT
+        if (!xml.readNextStartElement())
+        {
+            throw CiftiException("BrainModel requires a child element");
+        }
+        if (xml.name() != "VertexIndices")
+        {
+            throw CiftiException("unexpected element in BrainModel of SURFACE type: " + xml.name().toString());
+        }
+        m_nodeIndices = readIndexArray(xml);
+        xml.readNext();//remove the end element of NodeIndices
+        if (xml.hasError()) return;
+#else
+#ifdef CIFTILIB_USE_XMLPP
+        bool haveVertexIndices = false, done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+        while(!done && xml.read())
+        {
+            switch (xml.get_node_type())
+            {
+                case XmlReader::Element:
+                {
+                    AString name = xml.get_local_name();
+                    if (name == "VertexIndices")
+                    {
+                        if (haveVertexIndices)
+                        {
+                            throw CiftiException("VertexIndices element may only be specified once");
+                        }
+                        m_nodeIndices = readIndexArray(xml);
+                        haveVertexIndices = true;
+                    } else {
+                        throw CiftiException("unexpected element in BrainModel: " + name);
+                    }
+                    break;
+                }
+                case XmlReader::EndElement:
+                    done = true;
+                    break;
+                default:
+                    break;
+            }
+        }
+        if (!haveVertexIndices) throw CiftiException("VertexIndices element is missing");
+#else
+#error "not implemented"
+#endif
+#endif
+        if ((int64_t)m_nodeIndices.size() != m_count)
+        {
+            throw CiftiException("number of vertex indices does not match IndexCount");
+        }
+    } else {
+#ifdef CIFTILIB_USE_QT
+        if (!xml.readNextStartElement())
+        {
+            throw CiftiException("BrainModel requires a child element");
+        }
+        if (xml.name() != "VoxelIndicesIJK")
+        {
+            throw CiftiException("unexpected element in BrainModel of VOXELS type: " + xml.name().toString());
+        }
+        m_voxelIndicesIJK = readIndexArray(xml);
+        xml.readNext();//remove the end element of VoxelIndicesIJK
+        if (xml.hasError()) return;
+#else
+#ifdef CIFTILIB_USE_XMLPP
+        bool haveVoxelIndices = false, done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+        while(!done && xml.read())
+        {
+            switch (xml.get_node_type())
+            {
+                case XmlReader::Element:
+                {
+                    AString name = xml.get_local_name();
+                    if (name == "VoxelIndicesIJK")
+                    {
+                        if (haveVoxelIndices)
+                        {
+                            throw CiftiException("VoxelIndicesIJK element may only be specified once");
+                        }
+                        m_voxelIndicesIJK = readIndexArray(xml);
+                        haveVoxelIndices = true;
+                    } else {
+                        throw CiftiException("unexpected element in BrainModel: " + name);
+                    }
+                    break;
+                }
+                case XmlReader::EndElement:
+                    done = true;
+                    break;
+                default:
+                    break;
+            }
+        }
+        if (!haveVoxelIndices)//special case in cifti-1
+        {
+            throw CiftiException("BrainModel requires a child element");
+        }
+#else
+#error "not implemented"
+#endif
+#endif
+        if (m_voxelIndicesIJK.size() % 3 != 0)
+        {
+            throw CiftiException("number of voxel indices is not a multiple of 3");
+        }
+        if ((int64_t)m_voxelIndicesIJK.size() != m_count * 3)
+        {
+            throw CiftiException("number of voxel indices does not match IndexCount");
+        }
+    }
+#ifdef CIFTILIB_USE_QT
+    while (!xml.atEnd() && !xml.isEndElement())//locate the end element of BrainModel
+    {
+        switch(xml.readNext())
+        {
+            case QXmlStreamReader::StartElement:
+                throw CiftiException("unexpected second element in BrainModel: " + xml.name().toString());
+            default:
+                break;
+        }
+    }
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "BrainModel"));
+}
+
+vector<int64_t> CiftiBrainModelsMap::ParseHelperModel::readIndexArray(XmlReader& xml)
+{
+    vector<int64_t> ret;
+    AString text = XmlReader_readElementText(xml);//throws if it encounters a start element
+#ifdef CIFTILIB_USE_QT
+    if (xml.hasError()) return ret;
+#endif
+    vector<AString> separated = AString_split_whitespace(text);
+    size_t numElems = separated.size();
+    ret.reserve(numElems);
+    for (size_t i = 0; i < numElems; ++i)
+    {
+        bool ok = false;
+        ret.push_back(AString_toInt(separated[i], ok));
+        if (!ok)
+        {
+            throw CiftiException("found noninteger in index array: " + separated[i]);
+        }
+        if (ret.back() < 0)
+        {
+            throw CiftiException("found negative integer in index array: " + separated[i]);
+        }
+    }
+    return ret;
+}
+
+void CiftiBrainModelsMap::writeXML1(XmlWriter& xml) const
+{
+    CiftiAssert(!m_ignoreVolSpace);
+    xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_BRAIN_MODELS");
+    int numModels = (int)m_modelsInfo.size();
+    for (int i = 0; i < numModels; ++i)
+    {
+        const BrainModelPriv& myModel = m_modelsInfo[i];
+        xml.writeStartElement("BrainModel");
+        xml.writeAttribute("IndexOffset", AString_number(myModel.m_modelStart));
+        xml.writeAttribute("IndexCount", AString_number(myModel.m_modelEnd - myModel.m_modelStart));
+        xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(myModel.m_brainStructure));
+        if (myModel.m_type == SURFACE)
+        {
+            xml.writeAttribute("ModelType", "CIFTI_MODEL_TYPE_SURFACE");
+            xml.writeAttribute("SurfaceNumberOfNodes", AString_number(myModel.m_surfaceNumberOfNodes));
+            xml.writeStartElement("NodeIndices");
+            AString text = "";
+            int64_t numNodes = (int64_t)myModel.m_nodeIndices.size();
+            for (int64_t j = 0; j < numNodes; ++j)
+            {
+                if (j != 0) text += " ";
+                text += AString_number(myModel.m_nodeIndices[j]);
+            }
+            xml.writeCharacters(text);
+            xml.writeEndElement();
+        } else {
+            xml.writeAttribute("ModelType", "CIFTI_MODEL_TYPE_VOXELS");
+            xml.writeStartElement("VoxelIndicesIJK");
+            AString text = "";
+            int64_t listSize = (int64_t)myModel.m_voxelIndicesIJK.size();
+            CiftiAssert(listSize % 3 == 0);
+            for (int64_t j = 0; j < listSize; j += 3)
+            {
+                text += AString_number(myModel.m_voxelIndicesIJK[j]) + " " + AString_number(myModel.m_voxelIndicesIJK[j + 1]) + " " + AString_number(myModel.m_voxelIndicesIJK[j + 2]) + "\n";
+            }
+            xml.writeCharacters(text);
+            xml.writeEndElement();
+        }
+        xml.writeEndElement();
+    }
+}
+
+void CiftiBrainModelsMap::writeXML2(XmlWriter& xml) const
+{
+    CiftiAssert(!m_ignoreVolSpace);
+    xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_BRAIN_MODELS");
+    if (hasVolumeData())//could be m_haveVolumeSpace if we want to be able to write a volspace without having voxels, but that seems silly
+    {
+        m_volSpace.writeCiftiXML2(xml);
+    }
+    int numModels = (int)m_modelsInfo.size();
+    for (int i = 0; i < numModels; ++i)
+    {
+        const BrainModelPriv& myModel = m_modelsInfo[i];
+        xml.writeStartElement("BrainModel");
+        xml.writeAttribute("IndexOffset", AString_number(myModel.m_modelStart));
+        xml.writeAttribute("IndexCount", AString_number(myModel.m_modelEnd - myModel.m_modelStart));
+        xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(myModel.m_brainStructure));
+        if (myModel.m_type == SURFACE)
+        {
+            xml.writeAttribute("ModelType", "CIFTI_MODEL_TYPE_SURFACE");
+            xml.writeAttribute("SurfaceNumberOfVertices", AString_number(myModel.m_surfaceNumberOfNodes));
+            xml.writeStartElement("VertexIndices");
+            AString text = "";
+            int64_t numNodes = (int64_t)myModel.m_nodeIndices.size();
+            for (int64_t j = 0; j < numNodes; ++j)
+            {
+                if (j != 0) text += " ";
+                text += AString_number(myModel.m_nodeIndices[j]);
+            }
+            xml.writeCharacters(text);
+            xml.writeEndElement();
+        } else {
+            xml.writeAttribute("ModelType", "CIFTI_MODEL_TYPE_VOXELS");
+            xml.writeStartElement("VoxelIndicesIJK");
+            AString text = "";
+            int64_t listSize = (int64_t)myModel.m_voxelIndicesIJK.size();
+            CiftiAssert(listSize % 3 == 0);
+            for (int64_t j = 0; j < listSize; j += 3)
+            {
+                text += AString_number(myModel.m_voxelIndicesIJK[j]) + " " + AString_number(myModel.m_voxelIndicesIJK[j + 1]) + " " + AString_number(myModel.m_voxelIndicesIJK[j + 2]) + "\n";
+            }
+            xml.writeCharacters(text);
+            xml.writeEndElement();
+        }
+        xml.writeEndElement();
+    }
+}
diff --git a/src/Cifti/CiftiBrainModelsMap.h b/src/Cifti/CiftiBrainModelsMap.h
new file mode 100644
index 0000000..0b210f6
--- /dev/null
+++ b/src/Cifti/CiftiBrainModelsMap.h
@@ -0,0 +1,151 @@
+#ifndef __CIFTI_BRAIN_MODELS_MAP_H__
+#define __CIFTI_BRAIN_MODELS_MAP_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiMappingType.h"
+
+#include "Common/Compact3DLookup.h"
+#include "StructureEnum.h"
+#include "VolumeSpace.h"
+
+#include <map>
+#include <utility>
+#include <vector>
+
+namespace cifti
+{
+    class CiftiBrainModelsMap : public CiftiMappingType
+    {
+    public:
+        enum ModelType
+        {
+            SURFACE,
+            VOXELS
+        };
+        struct SurfaceMap
+        {
+            int64_t m_ciftiIndex;
+            int64_t m_surfaceNode;
+        };
+        struct VolumeMap
+        {
+            int64_t m_ciftiIndex;
+            int64_t m_ijk[3];
+        };
+        struct ModelInfo
+        {
+            ModelType m_type;
+            StructureEnum::Enum m_structure;
+            int64_t m_indexStart, m_indexCount;//these are intended only for summary info, use getSurfaceMap, etc for the index to vertex/voxel mappings
+        };
+        struct IndexInfo
+        {
+            ModelType m_type;
+            StructureEnum::Enum m_structure;
+            int64_t m_surfaceNode;//only one of these two will be valid
+            int64_t m_ijk[3];
+        };
+        bool hasVolumeData() const;
+        bool hasVolumeData(const StructureEnum::Enum& structure) const;
+        bool hasSurfaceData(const StructureEnum::Enum& structure) const;
+        int64_t getIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const;
+        int64_t getIndexForVoxel(const int64_t* ijk, StructureEnum::Enum* structureOut = NULL) const;
+        int64_t getIndexForVoxel(const int64_t& i, const int64_t& j, const int64_t& k, StructureEnum::Enum* structureOut = NULL) const;
+        IndexInfo getInfoForIndex(const int64_t index) const;
+        std::vector<SurfaceMap> getSurfaceMap(const StructureEnum::Enum& structure) const;
+        std::vector<VolumeMap> getFullVolumeMap() const;
+        std::vector<VolumeMap> getVolumeStructureMap(const StructureEnum::Enum& structure) const;
+        const VolumeSpace& getVolumeSpace() const;
+        int64_t getSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const;
+        std::vector<StructureEnum::Enum> getSurfaceStructureList() const;
+        std::vector<StructureEnum::Enum> getVolumeStructureList() const;
+        const std::vector<int64_t>& getNodeList(const StructureEnum::Enum& structure) const;//useful for copying mappings to a new dense mapping
+        const std::vector<int64_t>& getVoxelList(const StructureEnum::Enum& structure) const;
+        std::vector<ModelInfo> getModelInfo() const;
+        
+        CiftiBrainModelsMap() { m_haveVolumeSpace = false; m_ignoreVolSpace = false; }
+        void addSurfaceModel(const int64_t& numberOfNodes, const StructureEnum::Enum& structure, const float* roi = NULL);
+        void addSurfaceModel(const int64_t& numberOfNodes, const StructureEnum::Enum& structure, const std::vector<int64_t>& nodeList);
+        void addVolumeModel(const StructureEnum::Enum& structure, const std::vector<int64_t>& ijkList);
+        void setVolumeSpace(const VolumeSpace& space);
+        void clear();
+        
+        CiftiMappingType* clone() const { return new CiftiBrainModelsMap(*this); }
+        MappingType getType() const { return BRAIN_MODELS; }
+        int64_t getLength() const;
+        bool operator==(const CiftiMappingType& rhs) const;
+        bool approximateMatch(const CiftiMappingType& rhs, AString* explanation = NULL) const;
+        void readXML1(XmlReader& xml);
+        void readXML2(XmlReader& xml);
+        void writeXML1(XmlWriter& xml) const;
+        void writeXML2(XmlWriter& xml) const;
+    private:
+        struct BrainModelPriv
+        {
+            ModelType m_type;
+            StructureEnum::Enum m_brainStructure;
+            int64_t m_surfaceNumberOfNodes;
+            std::vector<int64_t> m_nodeIndices;
+            std::vector<int64_t> m_voxelIndicesIJK;
+            
+            int64_t m_modelStart, m_modelEnd;//stuff only needed for optimization - models are kept in sorted order by their index ranges
+            std::vector<int64_t> m_nodeToIndexLookup;
+            bool operator==(const BrainModelPriv& rhs) const;
+            bool operator!=(const BrainModelPriv& rhs) const { return !((*this) == rhs); }
+            void setupSurface(const int64_t& start);
+        };
+        VolumeSpace m_volSpace;
+        bool m_haveVolumeSpace, m_ignoreVolSpace;//second is needed for parsing cifti-1
+        std::vector<BrainModelPriv> m_modelsInfo;
+        std::map<StructureEnum::Enum, int> m_surfUsed, m_volUsed;
+        Compact3DLookup<std::pair<int64_t, StructureEnum::Enum> > m_voxelToIndexLookup;//make one unified lookup rather than separate lookups per volume structure
+        int64_t getNextStart() const;
+        struct ParseHelperModel
+        {//specifically to allow the parsed elements to be sorted before using addSurfaceModel/addVolumeModel
+            ModelType m_type;
+            StructureEnum::Enum m_brainStructure;
+            int64_t m_surfaceNumberOfNodes;
+            std::vector<int64_t> m_nodeIndices;
+            std::vector<int64_t> m_voxelIndicesIJK;
+            int64_t m_offset, m_count;
+            bool operator<(const ParseHelperModel& rhs) const
+            {
+                if (m_offset < rhs.m_offset) return true;
+                if (m_offset > rhs.m_offset) return false;//get the common cases first
+                if (m_count < rhs.m_count) return true;//in case we have a zero-length model - this shouldn't happen, usually
+                return false;
+            }
+            void parseBrainModel1(XmlReader& xml);
+            void parseBrainModel2(XmlReader& xml);
+            static std::vector<int64_t> readIndexArray(XmlReader& xml);
+        };
+    };
+}
+
+#endif //__CIFTI_BRAIN_MODELS_MAP_H__
diff --git a/src/Cifti/CiftiLabelsMap.cxx b/src/Cifti/CiftiLabelsMap.cxx
new file mode 100644
index 0000000..2f4ef1f
--- /dev/null
+++ b/src/Cifti/CiftiLabelsMap.cxx
@@ -0,0 +1,469 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiLabelsMap.h"
+
+#include "Common/CiftiAssert.h"
+#include "Common/CiftiException.h"
+
+#include <iostream>
+
+using namespace std;
+using namespace cifti;
+
+void CiftiLabelsMap::clear()
+{
+    m_maps.clear();
+}
+
+const MetaData& CiftiLabelsMap::getMapMetadata(const int64_t& index) const
+{
+    CiftiAssertVectorIndex(m_maps, index);
+    return m_maps[index].m_metaData;
+}
+
+const LabelTable& CiftiLabelsMap::getMapLabelTable(const int64_t& index) const
+{
+    CiftiAssertVectorIndex(m_maps, index);
+    return m_maps[index].m_labelTable;
+}
+
+const AString& CiftiLabelsMap::getMapName(const int64_t& index) const
+{
+    CiftiAssertVectorIndex(m_maps, index);
+    return m_maps[index].m_name;
+}
+
+void CiftiLabelsMap::setMapMetadata(const int64_t& index, const MetaData& md)
+{
+    CiftiAssertVectorIndex(m_maps, index);
+    m_maps[index].m_metaData = md;
+}
+
+void CiftiLabelsMap::setMapLabelTable(const int64_t& index, const LabelTable& lt)
+{
+    CiftiAssertVectorIndex(m_maps, index);
+    m_maps[index].m_labelTable = lt;
+}
+
+void CiftiLabelsMap::setMapName(const int64_t& index, const AString& mapName)
+{
+    CiftiAssertVectorIndex(m_maps, index);
+    m_maps[index].m_name = mapName;
+}
+
+void CiftiLabelsMap::setLength(const int64_t& length)
+{
+    CiftiAssert(length > 0);
+    m_maps.resize(length);
+}
+
+bool CiftiLabelsMap::approximateMatch(const CiftiMappingType& rhs, AString* explanation) const
+{
+    switch (rhs.getType())
+    {
+        case SCALARS:
+        case SERIES://maybe?
+        case LABELS:
+            if (getLength() != rhs.getLength())
+            {
+                if (explanation != NULL) *explanation = "mappings have different length";
+                return false;
+            } else return true;
+        default:
+            if (explanation != NULL) *explanation = CiftiMappingType::mappingTypeToName(rhs.getType()) + " mapping never matches " + CiftiMappingType::mappingTypeToName(getType());
+            return false;
+    }
+}
+
+bool CiftiLabelsMap::operator==(const CiftiMappingType& rhs) const
+{
+    if (rhs.getType() != getType()) return false;
+    const CiftiLabelsMap& myrhs = dynamic_cast<const CiftiLabelsMap&>(rhs);
+    return (m_maps == myrhs.m_maps);
+}
+
+bool CiftiLabelsMap::LabelMap::operator==(const LabelMap& rhs) const
+{
+    if (m_name != rhs.m_name) return false;
+    if (m_labelTable != rhs.m_labelTable) return false;
+    return (m_metaData == rhs.m_metaData);
+}
+
+void CiftiLabelsMap::readXML1(XmlReader& xml)
+{
+    cerr << "parsing nonstandard labels mapping type in cifti-1" << endl;
+    clear();
+#ifdef CIFTILIB_USE_QT
+    for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext())
+    {
+        switch (xml.tokenType())
+        {
+            case QXmlStreamReader::StartElement:
+            {
+                if (xml.name() != "NamedMap")
+                {
+                    throw CiftiException("unexpected element in labels map: " + xml.name().toString());
+                }
+                LabelMap tempMap;
+                tempMap.readXML1(xml);
+                if (xml.hasError()) return;
+                m_maps.push_back(tempMap);
+                break;
+            }
+            default:
+                break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "NamedMap")
+                {
+                    LabelMap tempMap;
+                    tempMap.readXML1(xml);
+                    m_maps.push_back(tempMap);
+                } else {
+                    throw CiftiException("unexpected element in labels map: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+}
+
+void CiftiLabelsMap::readXML2(XmlReader& xml)
+{
+    clear();
+#ifdef CIFTILIB_USE_QT
+    for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext())
+    {
+        switch (xml.tokenType())
+        {
+            case QXmlStreamReader::StartElement:
+            {
+                if (xml.name() != "NamedMap")
+                {
+                    throw CiftiException("unexpected element in labels mapping type: " + xml.name().toString());
+                }
+                LabelMap tempMap;
+                tempMap.readXML2(xml);
+                if (xml.hasError()) return;
+                m_maps.push_back(tempMap);
+                break;
+            }
+            default:
+                break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "NamedMap")
+                {
+                    LabelMap tempMap;
+                    tempMap.readXML2(xml);
+                    m_maps.push_back(tempMap);
+                } else {
+                    throw CiftiException("unexpected element in labels mapping type: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+}
+
+void CiftiLabelsMap::LabelMap::readXML1(XmlReader& xml)
+{
+    bool haveName = false, haveTable = false, haveMetaData = false;
+#ifdef CIFTILIB_USE_QT
+    for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext())
+    {
+        switch (xml.tokenType())
+        {
+            case QXmlStreamReader::StartElement:
+            {
+                QStringRef name = xml.name();
+                if (name == "MetaData")
+                {
+                    if (haveMetaData)
+                    {
+                        throw CiftiException("MetaData specified multiple times in one NamedMap");
+                    }
+                    m_metaData.readCiftiXML1(xml);
+                    if (xml.hasError()) return;
+                    haveMetaData = true;
+                } else if (name == "LabelTable") {
+                    if (haveTable)
+                    {
+                        throw CiftiException("LabelTable specified multiple times in one NamedMap");
+                    }
+                    m_labelTable.readXml(xml);
+                    if (xml.hasError()) return;
+                    haveTable = true;
+                } else if (name == "MapName") {
+                    if (haveName)
+                    {
+                        throw CiftiException("MapName specified multiple times in one NamedMap");
+                    }
+                    m_name = xml.readElementText();//raises error if element encountered
+                    if (xml.hasError()) return;
+                    haveName = true;
+                } else {
+                    throw CiftiException("unexpected element in NamedMap: " + name.toString());
+                }
+                break;
+            }
+            default:
+                break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "MetaData")
+                {
+                    if (haveMetaData)
+                    {
+                        throw CiftiException("MetaData specified multiple times in one NamedMap");
+                    }
+                    m_metaData.readCiftiXML1(xml);
+                    haveMetaData = true;
+                } else if (name == "LabelTable") {
+                    if (haveTable)
+                    {
+                        throw CiftiException("LabelTable specified multiple times in one NamedMap");
+                    }
+                    m_labelTable.readXml(xml);
+                    haveTable = true;
+                } else if (name == "MapName") {
+                    if (haveName)
+                    {
+                        throw CiftiException("MapName specified multiple times in one NamedMap");
+                    }
+                    m_name = XmlReader_readElementText(xml);//raises error if element encountered
+                    haveName = true;
+                } else {
+                    throw CiftiException("unexpected element in labels mapping type: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "NamedMap"));
+    if (!haveName)
+    {
+        throw CiftiException("NamedMap missing required child element MapName");
+    }
+    if (!haveTable)
+    {
+        throw CiftiException("NamedMap in labels mapping missing required child element LabelTable");
+    }
+}
+
+void CiftiLabelsMap::LabelMap::readXML2(XmlReader& xml)
+{
+    bool haveName = false, haveTable = false, haveMetaData = false;
+#ifdef CIFTILIB_USE_QT
+    for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext())
+    {
+        switch (xml.tokenType())
+        {
+            case QXmlStreamReader::StartElement:
+            {
+                QStringRef name = xml.name();
+                if (name == "MetaData")
+                {
+                    if (haveMetaData)
+                    {
+                        throw CiftiException("MetaData specified multiple times in one NamedMap");
+                    }
+                    m_metaData.readCiftiXML2(xml);
+                    if (xml.hasError()) return;
+                    haveMetaData = true;
+                } else if (name == "LabelTable") {
+                    if (haveTable)
+                    {
+                        throw CiftiException("LabelTable specified multiple times in one NamedMap");
+                    }
+                    m_labelTable.readXml(xml);
+                    if (xml.hasError()) return;
+                    haveTable = true;
+                } else if (name == "MapName") {
+                    if (haveName)
+                    {
+                        throw CiftiException("MapName specified multiple times in one NamedMap");
+                    }
+                    m_name = xml.readElementText();//raises error if element encountered
+                    if (xml.hasError()) return;
+                    haveName = true;
+                } else {
+                    throw CiftiException("unexpected element in NamedMap: " + name.toString());
+                }
+                break;
+            }
+            default:
+                break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "MetaData")
+                {
+                    if (haveMetaData)
+                    {
+                        throw CiftiException("MetaData specified multiple times in one NamedMap");
+                    }
+                    m_metaData.readCiftiXML2(xml);
+                    haveMetaData = true;
+                } else if (name == "LabelTable") {
+                    if (haveTable)
+                    {
+                        throw CiftiException("LabelTable specified multiple times in one NamedMap");
+                    }
+                    m_labelTable.readXml(xml);
+                    haveTable = true;
+                } else if (name == "MapName") {
+                    if (haveName)
+                    {
+                        throw CiftiException("MapName specified multiple times in one NamedMap");
+                    }
+                    m_name = XmlReader_readElementText(xml);//raises error if element encountered
+                    haveName = true;
+                } else {
+                    throw CiftiException("unexpected element in labels mapping type: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "NamedMap"));
+    if (!haveName)
+    {
+        throw CiftiException("NamedMap missing required child element MapName");
+    }
+    if (!haveTable)
+    {
+        throw CiftiException("NamedMap in labels mapping missing required child element LabelTable");
+    }
+}
+
+void CiftiLabelsMap::writeXML1(XmlWriter& xml) const
+{
+    cerr << "writing nonstandard labels mapping type in cifti-1" << endl;
+    xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_LABELS");
+    int64_t numMaps = (int64_t)m_maps.size();
+    for (int64_t i = 0; i < numMaps; ++i)
+    {
+        xml.writeStartElement("NamedMap");
+        xml.writeTextElement("MapName", m_maps[i].m_name);
+        m_maps[i].m_metaData.writeCiftiXML1(xml);
+        m_maps[i].m_labelTable.writeXML(xml);
+        xml.writeEndElement();
+    }
+}
+
+void CiftiLabelsMap::writeXML2(XmlWriter& xml) const
+{
+    int64_t numMaps = (int64_t)m_maps.size();
+    xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_LABELS");
+    for (int64_t i = 0; i < numMaps; ++i)
+    {
+        xml.writeStartElement("NamedMap");
+        xml.writeTextElement("MapName", m_maps[i].m_name);
+        m_maps[i].m_metaData.writeCiftiXML2(xml);
+        m_maps[i].m_labelTable.writeXML(xml);
+        xml.writeEndElement();
+    }
+}
diff --git a/src/Cifti/CiftiLabelsMap.h b/src/Cifti/CiftiLabelsMap.h
new file mode 100644
index 0000000..0e1e777
--- /dev/null
+++ b/src/Cifti/CiftiLabelsMap.h
@@ -0,0 +1,78 @@
+#ifndef __CIFTI_LABELS_MAP_H__
+#define __CIFTI_LABELS_MAP_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiMappingType.h"
+
+#include "Common/AString.h"
+#include "MetaData.h"
+#include "LabelTable.h"
+
+#include <map>
+#include <vector>
+
+namespace cifti
+{
+    class CiftiLabelsMap : public CiftiMappingType
+    {
+    public:
+        const MetaData& getMapMetadata(const int64_t& index) const;
+        const LabelTable& getMapLabelTable(const int64_t& index) const;
+        const AString& getMapName(const int64_t& index) const;
+        
+        void setMapMetadata(const int64_t& index, const MetaData& md);
+        void setMapLabelTable(const int64_t& index, const LabelTable& lt);
+        void setMapName(const int64_t& index, const AString& mapName);
+        void setLength(const int64_t& length);
+        void clear();
+        
+        CiftiMappingType* clone() const { return new CiftiLabelsMap(*this); }
+        MappingType getType() const { return LABELS; }
+        int64_t getLength() const { return m_maps.size(); }
+        bool operator==(const CiftiMappingType& rhs) const;
+        bool approximateMatch(const CiftiMappingType& rhs, AString* explanation = NULL) const;
+        void readXML1(XmlReader& xml);
+        void readXML2(XmlReader& xml);
+        void writeXML1(XmlWriter& xml) const;
+        void writeXML2(XmlWriter& xml) const;
+    private:
+        struct LabelMap
+        {
+            AString m_name;
+            MetaData m_metaData;
+            LabelTable m_labelTable;
+            bool operator==(const LabelMap& rhs) const;
+            void readXML1(XmlReader& xml);
+            void readXML2(XmlReader& xml);
+        };
+        std::vector<LabelMap> m_maps;
+    };
+}
+
+#endif //__CIFTI_LABELS_MAP_H__
diff --git a/src/Cifti/CiftiMappingType.cxx b/src/Cifti/CiftiMappingType.cxx
new file mode 100644
index 0000000..ae1b95c
--- /dev/null
+++ b/src/Cifti/CiftiMappingType.cxx
@@ -0,0 +1,55 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiMappingType.h"
+
+#include "Common/CiftiAssert.h"
+
+using namespace cifti;
+
+CiftiMappingType::~CiftiMappingType()
+{//to ensure that the class's vtable gets defined in an object file
+}
+
+AString CiftiMappingType::mappingTypeToName(const CiftiMappingType::MappingType& type)
+{
+    switch (type)
+    {
+        case BRAIN_MODELS:
+            return "BRAIN_MODELS";
+        case PARCELS:
+            return "PARCELS";
+        case SERIES:
+            return "SERIES";
+        case SCALARS:
+            return "SCALARS";
+        case LABELS:
+            return "LABELS";
+    }
+    CiftiAssert(0);
+    return "";
+}
diff --git a/src/Cifti/CiftiMappingType.h b/src/Cifti/CiftiMappingType.h
new file mode 100644
index 0000000..2b09982
--- /dev/null
+++ b/src/Cifti/CiftiMappingType.h
@@ -0,0 +1,65 @@
+#ifndef __CIFTI_MAPPING_TYPE_H__
+#define __CIFTI_MAPPING_TYPE_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "stdint.h"
+
+#include "Common/XmlAdapter.h"
+
+namespace cifti
+{
+    
+    class CiftiMappingType
+    {
+    public:
+        enum MappingType
+        {
+            BRAIN_MODELS,
+            PARCELS,
+            SERIES,
+            SCALARS,
+            LABELS
+        };
+        virtual CiftiMappingType* clone() const = 0;//make a copy, preserving the actual type - NOTE: this returns a dynamic allocation that is not owned by anything
+        virtual MappingType getType() const = 0;
+        virtual int64_t getLength() const = 0;
+        virtual bool operator==(const CiftiMappingType& rhs) const = 0;//used to check for merging mappings when writing the XML - must compare EVERYTHING that goes into the XML
+        bool operator!=(const CiftiMappingType& rhs) const { return !((*this) == rhs); }
+        virtual bool approximateMatch(const CiftiMappingType& rhs, AString* explanation = NULL) const = 0;//check if things like doing index-wise math would make sense
+        virtual void readXML1(XmlReader& xml) = 0;//mainly to shorten the type-specific code in CiftiXML
+        virtual void readXML2(XmlReader& xml) = 0;
+        virtual void writeXML1(XmlWriter& xml) const = 0;
+        virtual void writeXML2(XmlWriter& xml) const = 0;
+        virtual ~CiftiMappingType();
+        
+        static AString mappingTypeToName(const MappingType& type);
+    };
+}
+
+#endif //__CIFTI_MAPPING_TYPE_H__
diff --git a/src/Cifti/CiftiParcelsMap.cxx b/src/Cifti/CiftiParcelsMap.cxx
new file mode 100644
index 0000000..e4e40fe
--- /dev/null
+++ b/src/Cifti/CiftiParcelsMap.cxx
@@ -0,0 +1,971 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiParcelsMap.h"
+
+#include "Common/CiftiAssert.h"
+#include "Common/CiftiException.h"
+
+#include <iostream>
+
+using namespace std;
+using namespace cifti;
+
+void CiftiParcelsMap::addParcel(const CiftiParcelsMap::Parcel& parcel)
+{
+    int64_t thisParcel = m_parcels.size();//slight hack: current number of parcels will be this parcel's index
+    for (int64_t i = 0; i < thisParcel; ++i)//also use it as endpoint of looping over existing parcels
+    {
+        if (parcel.m_name == m_parcels[i].m_name)
+        {
+            throw CiftiException("cannot add parcel with duplicate name '" + parcel.m_name + "'");//NOTE: technically this restriction isn't in the standard, but that was probably an oversight
+        }
+    }
+    int64_t voxelListSize = (int64_t)parcel.m_voxelIndices.size();
+    Compact3DLookup<int64_t> tempLookup = m_volLookup;//a copy of the lookup should be faster than other methods of checking for overlap and repeat
+    if (voxelListSize != 0)
+    {
+        const int64_t* dims = NULL;
+        if (!m_ignoreVolSpace)
+        {
+            if (!m_haveVolumeSpace)
+            {
+                throw CiftiException("you must set the volume space before adding parcels that use voxels");
+            }
+            dims = m_volSpace.getDims();
+        }
+        for (set<VoxelIJK>::const_iterator iter = parcel.m_voxelIndices.begin(); iter != parcel.m_voxelIndices.end(); ++iter)//do all error checking before adding to lookup - might be unnecessary
+        {
+            if (iter->m_ijk[0] < 0 || iter->m_ijk[1] < 0 || iter->m_ijk[2] < 0)
+            {
+                throw CiftiException("found negative index triple in voxel list");
+            }
+            if (!m_ignoreVolSpace && (iter->m_ijk[0] >= dims[0] ||
+                                      iter->m_ijk[1] >= dims[1] ||
+                                      iter->m_ijk[2] >= dims[2]))
+            {
+                throw CiftiException("found invalid index triple in voxel list");
+            }
+            if (tempLookup.find(iter->m_ijk) != NULL)
+            {
+                throw CiftiException("parcels may not overlap in voxels");
+            }
+            tempLookup.at(iter->m_ijk) = thisParcel;
+        }
+    }
+    for (map<StructureEnum::Enum, set<int64_t> >::const_iterator iter = parcel.m_surfaceNodes.begin(); iter != parcel.m_surfaceNodes.end(); ++iter)
+    {
+        map<StructureEnum::Enum, SurfaceInfo>::const_iterator info = m_surfInfo.find(iter->first);
+        if (info == m_surfInfo.end())
+        {
+            throw CiftiException("you must set surfaces before adding parcels that use them");
+        }
+        const set<int64_t>& nodeSet = iter->second;
+        if (nodeSet.size() == 0)
+        {
+            throw CiftiException("parcels may not include empty node lists");//NOTE: technically not required by Cifti, change if problematic, but probably never allow empty list in internal state
+        }
+        for (set<int64_t>::const_iterator iter2 = nodeSet.begin(); iter2 != nodeSet.end(); ++iter2)
+        {
+            if (*iter2 < 0)
+            {
+                throw CiftiException("found negative vertex in parcel");
+            }
+            if (*iter2 >= info->second.m_numNodes)
+            {
+                throw CiftiException("found invalid vertex in parcel");
+            }
+            if (info->second.m_lookup[*iter2] != -1)
+            {
+                throw CiftiException("parcels may not overlap in vertices");
+            }
+        }
+    }
+    if (voxelListSize != 0)//all error checking done, modify
+    {
+        m_volLookup = tempLookup;
+    }
+    for (map<StructureEnum::Enum, set<int64_t> >::const_iterator iter = parcel.m_surfaceNodes.begin(); iter != parcel.m_surfaceNodes.end(); ++iter)
+    {
+        map<StructureEnum::Enum, SurfaceInfo>::iterator info = m_surfInfo.find(iter->first);
+        CiftiAssert(info != m_surfInfo.end());
+        const set<int64_t>& nodeSet = iter->second;
+        for (set<int64_t>::const_iterator iter2 = nodeSet.begin(); iter2 != nodeSet.end(); ++iter2)
+        {
+            CiftiAssertVectorIndex(info->second.m_lookup, *iter2);
+            info->second.m_lookup[*iter2] = thisParcel;
+        }
+    }
+    m_parcels.push_back(parcel);
+}
+
+void CiftiParcelsMap::addSurface(const int64_t& numberOfNodes, const StructureEnum::Enum& structure)
+{
+    map<StructureEnum::Enum, SurfaceInfo>::const_iterator test = m_surfInfo.find(structure);
+    if (test != m_surfInfo.end())
+    {
+        throw CiftiException("parcel surface structures may not be used more than once");
+    }
+    SurfaceInfo tempInfo;
+    tempInfo.m_numNodes = numberOfNodes;
+    tempInfo.m_lookup.resize(numberOfNodes, -1);
+    m_surfInfo[structure] = tempInfo;
+}
+
+void CiftiParcelsMap::clear()
+{
+    m_haveVolumeSpace = false;
+    m_ignoreVolSpace = false;
+    m_parcels.clear();
+    m_surfInfo.clear();
+    m_volLookup.clear();
+}
+
+void CiftiParcelsMap::setVolumeSpace(const VolumeSpace& space)
+{
+    const int64_t* dims = space.getDims();
+    int64_t numParcels = (int64_t)m_parcels.size();
+    for (int64_t i = 0; i < numParcels; ++i)
+    {
+        const set<VoxelIJK>& voxelList = m_parcels[i].m_voxelIndices;
+        for (set<VoxelIJK>::const_iterator iter = voxelList.begin(); iter != voxelList.end(); ++iter)
+        {
+            if (iter->m_ijk[0] >= dims[0] ||
+                iter->m_ijk[1] >= dims[1] ||
+                iter->m_ijk[2] >= dims[2])
+            {
+                throw CiftiException("parcels may not contain voxel indices outside the volume space");
+            }
+        }
+    }
+    m_haveVolumeSpace = true;
+    m_ignoreVolSpace = false;
+    m_volSpace = space;
+}
+
+int64_t CiftiParcelsMap::getIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const
+{
+    CiftiAssert(node >= 0);
+    map<StructureEnum::Enum, SurfaceInfo>::const_iterator test = m_surfInfo.find(structure);
+    if (test == m_surfInfo.end()) return -1;
+    if (node >= test->second.m_numNodes) return -1;
+    CiftiAssertVectorIndex(test->second.m_lookup, node);
+    return test->second.m_lookup[node];
+}
+
+int64_t CiftiParcelsMap::getIndexForVoxel(const int64_t* ijk) const
+{
+    return getIndexForVoxel(ijk[0], ijk[1], ijk[2]);
+}
+
+int64_t CiftiParcelsMap::getIndexForVoxel(const int64_t& i, const int64_t& j, const int64_t& k) const
+{
+    const int64_t* test = m_volLookup.find(i, j, k);//the lookup tolerates weirdness like negatives
+    if (test == NULL) return -1;
+    return *test;
+}
+
+vector<StructureEnum::Enum> CiftiParcelsMap::getParcelSurfaceStructures() const
+{
+    vector<StructureEnum::Enum> ret;
+    ret.reserve(m_surfInfo.size());
+    for (map<StructureEnum::Enum, SurfaceInfo>::const_iterator iter = m_surfInfo.begin(); iter != m_surfInfo.end(); ++iter)
+    {
+        ret.push_back(iter->first);
+    }
+    return ret;
+}
+
+const VolumeSpace& CiftiParcelsMap::getVolumeSpace() const
+{
+    CiftiAssert(!m_ignoreVolSpace);//this should never be set except during parsing of cifti-1
+    if (!m_haveVolumeSpace)
+    {
+        throw CiftiException("getVolumeSpace called when no volume space exists");
+    }
+    return m_volSpace;
+}
+
+int64_t CiftiParcelsMap::getSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const
+{
+    map<StructureEnum::Enum, SurfaceInfo>::const_iterator iter = m_surfInfo.find(structure);
+    if (iter == m_surfInfo.end()) return -1;
+    return iter->second.m_numNodes;
+}
+
+bool CiftiParcelsMap::hasSurface(const StructureEnum::Enum& structure) const
+{
+    return m_surfInfo.find(structure) != m_surfInfo.end();
+}
+
+bool CiftiParcelsMap::hasSurfaceData(const StructureEnum::Enum& structure) const
+{
+    if (m_surfInfo.find(structure) == m_surfInfo.end()) return false;
+    int64_t numParcels = (int64_t)m_parcels.size();
+    for (int64_t i = 0; i < numParcels; ++i)
+    {
+        map<StructureEnum::Enum, set<int64_t> >::const_iterator iter = m_parcels[i].m_surfaceNodes.find(structure);
+        if (iter != m_parcels[i].m_surfaceNodes.end() && iter->second.size() != 0) return true;
+    }
+    return false;
+}
+
+bool CiftiParcelsMap::hasVolumeData() const
+{
+    CiftiAssert(!m_ignoreVolSpace);
+    int64_t numParcels = (int64_t)m_parcels.size();//NOTE: this function is used when reading cifti-1 to determine whether it is an error to not have a volume space, so we can't just check m_haveVolumeSpace
+    for (int64_t i = 0; i < numParcels; ++i)
+    {
+        if (m_parcels[i].m_voxelIndices.size() != 0) return true;
+    }
+    return false;
+}
+
+bool CiftiParcelsMap::approximateMatch(const CiftiMappingType& rhs, AString* explanation) const
+{
+    if (rhs.getType() != getType())
+    {
+        if (explanation != NULL) *explanation = CiftiMappingType::mappingTypeToName(rhs.getType()) + " mapping never matches " + CiftiMappingType::mappingTypeToName(getType());
+        return false;
+    }
+    const CiftiParcelsMap& myrhs = dynamic_cast<const CiftiParcelsMap&>(rhs);
+    CiftiAssert(!m_ignoreVolSpace && !myrhs.m_ignoreVolSpace);
+    if (m_haveVolumeSpace != myrhs.m_haveVolumeSpace)
+    {
+        if (explanation != NULL) *explanation = "one of the mappings has no volume data";
+        return false;
+    }
+    if (m_haveVolumeSpace && (m_volSpace != myrhs.m_volSpace))
+    {
+        if (explanation != NULL) *explanation = "mappings have a different volume space";
+        return false;
+    }
+    if (m_surfInfo.size() != myrhs.m_surfInfo.size())
+    {
+        if (explanation != NULL) *explanation = "mappings have a different number of surfaces used";
+        return false;//as below, return false if they won't write the mapping part to xml the same - 1 to 1 compare only requires 1 simple loop
+    }
+    for (map<StructureEnum::Enum, SurfaceInfo>::const_iterator iter = m_surfInfo.begin(); iter != m_surfInfo.end(); ++iter)
+    {
+        map<StructureEnum::Enum, SurfaceInfo>::const_iterator rhsiter = myrhs.m_surfInfo.find(iter->first);
+        if (rhsiter == myrhs.m_surfInfo.end())
+        {//technically, they might still have the same meaning, if the surface isn't used, but they will still write differently, so false
+            if (explanation != NULL) *explanation = StructureEnum::toName(iter->first) + " surface expected but not found";
+            return false;
+        }
+        if (iter->second.m_numNodes != rhsiter->second.m_numNodes)
+        {
+            if (explanation != NULL) *explanation = "different number of vertices for surface " + StructureEnum::toName(iter->first);
+            return false;
+        }
+    }
+    if (m_parcels.size() != myrhs.m_parcels.size())
+    {
+        if (explanation != NULL) *explanation = "different number of parcels";
+        return false;
+    }
+    for (int64_t i = 0; i < (int64_t)m_parcels.size(); ++i)
+    {
+        if (!m_parcels[i].approximateMatch(myrhs.m_parcels[i], explanation)) return false;
+    }
+    return true;
+}
+
+bool CiftiParcelsMap::operator==(const CiftiMappingType& rhs) const
+{
+    if (rhs.getType() != getType()) return false;
+    const CiftiParcelsMap& myrhs = dynamic_cast<const CiftiParcelsMap&>(rhs);
+    CiftiAssert(!m_ignoreVolSpace && !myrhs.m_ignoreVolSpace);
+    if (m_haveVolumeSpace != myrhs.m_haveVolumeSpace) return false;
+    if (m_haveVolumeSpace && m_volSpace != myrhs.m_volSpace) return false;
+    if (m_surfInfo.size() != myrhs.m_surfInfo.size()) return false;//as below, return false if they won't write to xml the same - 1 to 1 compare only requires 1 simple loop
+    for (map<StructureEnum::Enum, SurfaceInfo>::const_iterator iter = m_surfInfo.begin(); iter != m_surfInfo.end(); ++iter)
+    {
+        map<StructureEnum::Enum, SurfaceInfo>::const_iterator rhsiter = myrhs.m_surfInfo.find(iter->first);
+        if (rhsiter == myrhs.m_surfInfo.end()) return false;//technically, they might still have the same meaning, if the surface isn't used, but they will still write differently, so false
+        if (iter->second.m_numNodes != rhsiter->second.m_numNodes) return false;
+    }
+    return (m_parcels == myrhs.m_parcels);
+}
+
+bool CiftiParcelsMap::Parcel::operator==(const CiftiParcelsMap::Parcel& rhs) const
+{
+    if (m_name != rhs.m_name) return false;
+    if (m_voxelIndices != rhs.m_voxelIndices) return false;
+    return (m_surfaceNodes == rhs.m_surfaceNodes);
+}
+
+//same, but don't test name
+bool CiftiParcelsMap::Parcel::approximateMatch(const CiftiParcelsMap::Parcel& rhs, AString* explanation) const
+{
+    bool nameMatches = (m_name == rhs.m_name);//for more informative explanations
+    if (m_voxelIndices != rhs.m_voxelIndices)
+    {
+        if (explanation != NULL)
+        {
+            if (nameMatches)
+            {
+                *explanation = "parcel '" + m_name + "' uses different voxels than parcel in other map";
+            } else {
+                *explanation = "parcel '" + m_name + "' uses different voxels than same-index parcel '" + rhs.m_name + "' in other map";
+            }
+        }
+        return false;
+    }
+    if (m_surfaceNodes != rhs.m_surfaceNodes)
+    {
+        if (explanation != NULL)
+        {
+            if (nameMatches)
+            {
+                *explanation = "parcel '" + m_name + "' uses different surface vertices than parcel in other map";
+            } else {
+                *explanation = "parcel '" + m_name + "' uses different surface vertices than same-index parcel '" + rhs.m_name + "' in other map";
+            }
+        }
+        return false;
+    }
+    return true;
+}
+
+void CiftiParcelsMap::readXML1(XmlReader& xml)
+{
+    cerr << "parsing nonstandard parcels mapping type in cifti-1" << endl;
+    clear();
+    m_ignoreVolSpace = true;//cifti-1 has volume space outside the index map
+    vector<Parcel> myParcels;//because we need to add the surfaces first
+#ifdef CIFTILIB_USE_QT
+    while (xml.readNextStartElement())
+    {
+        QStringRef name = xml.name();
+        if (name == "Surface")
+        {
+            QXmlStreamAttributes attrs = xml.attributes();
+            if (!attrs.hasAttribute("BrainStructure"))
+            {
+                throw CiftiException("Surface element missing required attribute BrainStructure");
+            }
+            bool ok = false;
+            StructureEnum::Enum tempStructure = StructureEnum::fromCiftiName(attrs.value("BrainStructure").toString(), &ok);
+            if (!ok)
+            {
+                throw CiftiException("invalid value for BrainStructure: " + attrs.value("BrainStructure").toString());
+            }
+            if (!attrs.hasAttribute("SurfaceNumberOfNodes"))
+            {
+                throw CiftiException("Surface element missing required attribute SurfaceNumberOfNodes");
+            }
+            int64_t numNodes = attrs.value("SurfaceNumberOfNodes").toString().toLongLong(&ok);
+            if (!ok || numNodes < 1)
+            {
+                throw CiftiException("invalid value for SurfaceNumberOfNodes: " + attrs.value("SurfaceNumberOfNodes").toString());
+            }
+            addSurface(numNodes, tempStructure);//let the standard modification functions do error checking
+            if (xml.readNextStartElement())
+            {
+                throw CiftiException("unexpected element inside Surface: " + xml.name().toString());
+            }
+        } else if (name == "Parcel") {
+            myParcels.push_back(readParcel1(xml));
+        } else {
+            throw CiftiException("unexpected element in parcels map: " + name.toString());
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "Surface")
+                {
+                    vector<AString> mandAttrs(2);
+                    mandAttrs[0] = "BrainStructure";
+                    mandAttrs[1] = "SurfaceNumberOfNodes";
+                    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs);
+                    bool ok = false;
+                    StructureEnum::Enum tempStructure = StructureEnum::fromCiftiName(myAttrs.mandatoryVals[0], &ok);
+                    if (!ok)
+                    {
+                        throw CiftiException("invalid value for BrainStructure: " + myAttrs.mandatoryVals[0]);
+                    }
+                    int64_t numNodes = AString_toInt(myAttrs.mandatoryVals[1], ok);
+                    if (!ok || numNodes < 1)
+                    {
+                        throw CiftiException("invalid value for SurfaceNumberOfNodes: " + myAttrs.mandatoryVals[1]);
+                    }
+                    addSurface(numNodes, tempStructure);//let the standard modification functions do error checking
+                } else if (name == "Parcel") {
+                    myParcels.push_back(readParcel1(xml));
+                } else {
+                    throw CiftiException("unexpected element in parcels map: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+    int64_t numParcels = (int64_t)myParcels.size();
+    for (int64_t i = 0; i < numParcels; ++i)
+    {
+        addParcel(myParcels[i]);
+    }
+    m_ignoreVolSpace = false;//in case there are no voxels, but some will be added later
+}
+
+void CiftiParcelsMap::readXML2(XmlReader& xml)
+{
+    clear();
+    vector<Parcel> myParcels;//because we need to add the surfaces and volume space first
+#ifdef CIFTILIB_USE_QT
+    while (xml.readNextStartElement())
+    {
+        QStringRef name = xml.name();
+        if (name == "Surface")
+        {
+            QXmlStreamAttributes attrs = xml.attributes();
+            if (!attrs.hasAttribute("BrainStructure"))
+            {
+                throw CiftiException("Surface element missing required attribute BrainStructure");
+            }
+            bool ok = false;
+            StructureEnum::Enum tempStructure = StructureEnum::fromCiftiName(attrs.value("BrainStructure").toString(), &ok);
+            if (!ok)
+            {
+                throw CiftiException("invalid value for BrainStructure: " + attrs.value("BrainStructure").toString());
+            }
+            if (!attrs.hasAttribute("SurfaceNumberOfVertices"))
+            {
+                throw CiftiException("Surface element missing required attribute SurfaceNumberOfVertices");
+            }
+            int64_t numNodes = attrs.value("SurfaceNumberOfVertices").toString().toLongLong(&ok);
+            if (!ok || numNodes < 1)
+            {
+                throw CiftiException("invalid value for SurfaceNumberOfVertices: " + attrs.value("SurfaceNumberOfVertices").toString());
+            }
+            addSurface(numNodes, tempStructure);//let the standard modification functions do error checking
+            if (xml.readNextStartElement())
+            {
+                throw CiftiException("unexpected element inside Surface: " + xml.name().toString());
+            }
+        } else if (name == "Parcel") {
+            myParcels.push_back(readParcel2(xml));
+            if (xml.hasError()) return;
+        } else if (name == "Volume") {
+            if (m_haveVolumeSpace)
+            {
+                throw CiftiException("Volume specified more than once in Parcels mapping type");
+            } else {
+                m_volSpace.readCiftiXML2(xml);
+                if (xml.hasError()) return;
+                m_haveVolumeSpace = true;
+            }
+        } else {
+            throw CiftiException("unexpected element in parcels map: " + name.toString());
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "Surface")
+                {
+                    vector<AString> mandAttrs(2);
+                    mandAttrs[0] = "BrainStructure";
+                    mandAttrs[1] = "SurfaceNumberOfVertices";
+                    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs);
+                    bool ok = false;
+                    StructureEnum::Enum tempStructure = StructureEnum::fromCiftiName(myAttrs.mandatoryVals[0], &ok);
+                    if (!ok)
+                    {
+                        throw CiftiException("invalid value for BrainStructure: " + myAttrs.mandatoryVals[0]);
+                    }
+                    int64_t numNodes = AString_toInt(myAttrs.mandatoryVals[1], ok);
+                    if (!ok || numNodes < 1)
+                    {
+                        throw CiftiException("invalid value for SurfaceNumberOfVertices: " + myAttrs.mandatoryVals[1]);
+                    }
+                    addSurface(numNodes, tempStructure);//let the standard modification functions do error checking
+                } else if (name == "Parcel") {
+                    myParcels.push_back(readParcel2(xml));
+                } else if (name == "Volume") {
+                    if (m_haveVolumeSpace)
+                    {
+                        throw CiftiException("Volume specified more than once in Parcels mapping type");
+                    } else {
+                        m_volSpace.readCiftiXML2(xml);
+                        m_haveVolumeSpace = true;
+                    }
+                } else {
+                    throw CiftiException("unexpected element in parcels map: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+    int64_t numParcels = (int64_t)myParcels.size();
+    for (int64_t i = 0; i < numParcels; ++i)
+    {
+        addParcel(myParcels[i]);
+    }
+}
+
+CiftiParcelsMap::Parcel CiftiParcelsMap::readParcel1(XmlReader& xml)
+{
+    Parcel ret;
+    bool haveVoxels = false;
+    vector<AString> mandAttrs(1, "Name");
+    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs);
+    ret.m_name = myAttrs.mandatoryVals[0];
+#ifdef CIFTILIB_USE_QT
+    while (xml.readNextStartElement())
+    {
+        QStringRef name = xml.name();
+        if (name == "Nodes")
+        {
+            QXmlStreamAttributes attrs1 = xml.attributes();
+            if (!attrs1.hasAttribute("BrainStructure"))
+            {
+                throw CiftiException("Nodes element missing required attribute BrainStructure");
+            }
+            bool ok = false;
+            StructureEnum::Enum myStructure = StructureEnum::fromCiftiName(attrs1.value("BrainStructure").toString(), &ok);
+            if (!ok)
+            {
+                throw CiftiException("unrecognized value for BrainStructure: " + attrs1.value("BrainStructure").toString());
+            }
+            if (ret.m_surfaceNodes.find(myStructure) != ret.m_surfaceNodes.end())
+            {
+                throw CiftiException("Nodes elements may not reuse a BrainStructure within a Parcel");
+            }
+            set<int64_t>& mySet = ret.m_surfaceNodes[myStructure];
+            vector<int64_t> array = readIndexArray(xml);
+            if (xml.hasError()) return ret;
+            int64_t arraySize = (int64_t)array.size();
+            for (int64_t i = 0; i < arraySize; ++i)
+            {
+                if (mySet.find(array[i]) != mySet.end())
+                {
+                    throw CiftiException("Nodes elements may not reuse indices");
+                }
+                mySet.insert(array[i]);
+            }
+        } else if (name == "VoxelIndicesIJK") {
+            if (haveVoxels)
+            {
+                throw CiftiException("VoxelIndicesIJK may only appear once in a Parcel");
+            }
+            vector<int64_t> array = readIndexArray(xml);
+            if (xml.hasError()) return ret;
+            int64_t arraySize = (int64_t)array.size();
+            if (arraySize % 3 != 0)
+            {
+                throw CiftiException("number of indices in VoxelIndicesIJK must be a multiple of 3");
+            }
+            for (int64_t index3 = 0; index3 < arraySize; index3 += 3)
+            {
+                VoxelIJK temp(array.data() + index3);
+                if (ret.m_voxelIndices.find(temp) != ret.m_voxelIndices.end())
+                {
+                    throw CiftiException("VoxelIndicesIJK elements may not reuse voxels");
+                }
+                ret.m_voxelIndices.insert(temp);
+            }
+            haveVoxels = true;
+        } else {
+            throw CiftiException("unexpected element in Parcel: " + name.toString());
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "Nodes")
+                {
+                    vector<AString> mandAttrs1(1, "BrainStructure");
+                    XmlAttributesResult myAttrs1 = XmlReader_parseAttributes(xml, mandAttrs1);
+                    bool ok = false;
+                    StructureEnum::Enum myStructure = StructureEnum::fromCiftiName(myAttrs1.mandatoryVals[0], &ok);
+                    if (!ok)
+                    {
+                        throw CiftiException("unrecognized value for BrainStructure: " + myAttrs1.mandatoryVals[0]);
+                    }
+                    if (ret.m_surfaceNodes.find(myStructure) != ret.m_surfaceNodes.end())
+                    {
+                        throw CiftiException("Nodes elements may not reuse a BrainStructure within a Parcel");
+                    }
+                    set<int64_t>& mySet = ret.m_surfaceNodes[myStructure];
+                    vector<int64_t> array = readIndexArray(xml);
+                    int64_t arraySize = (int64_t)array.size();
+                    for (int64_t i = 0; i < arraySize; ++i)
+                    {
+                        if (mySet.find(array[i]) != mySet.end())
+                        {
+                            throw CiftiException("Nodes elements may not reuse indices");
+                        }
+                        mySet.insert(array[i]);
+                    }
+                } else if (name == "VoxelIndicesIJK") {
+                    if (haveVoxels)
+                    {
+                        throw CiftiException("VoxelIndicesIJK may only appear once in a Parcel");
+                    }
+                    vector<int64_t> array = readIndexArray(xml);
+                    int64_t arraySize = (int64_t)array.size();
+                    if (arraySize % 3 != 0)
+                    {
+                        throw CiftiException("number of indices in VoxelIndicesIJK must be a multiple of 3");
+                    }
+                    for (int64_t index3 = 0; index3 < arraySize; index3 += 3)
+                    {
+                        VoxelIJK temp(array.data() + index3);
+                        if (ret.m_voxelIndices.find(temp) != ret.m_voxelIndices.end())
+                        {
+                            throw CiftiException("VoxelIndicesIJK elements may not reuse voxels");
+                        }
+                        ret.m_voxelIndices.insert(temp);
+                    }
+                    haveVoxels = true;
+                } else {
+                    throw CiftiException("unexpected element in Parcel: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "Parcel"));
+    return ret;
+}
+
+CiftiParcelsMap::Parcel CiftiParcelsMap::readParcel2(XmlReader& xml)
+{
+    Parcel ret;
+    bool haveVoxels = false;
+    vector<AString> mandAttrs(1, "Name");
+    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs);
+    ret.m_name = myAttrs.mandatoryVals[0];
+#ifdef CIFTILIB_USE_QT
+    while (xml.readNextStartElement())
+    {
+        QStringRef name = xml.name();
+        if (name == "Vertices")
+        {
+            QXmlStreamAttributes attrs1 = xml.attributes();
+            if (!attrs1.hasAttribute("BrainStructure"))
+            {
+                throw CiftiException("Vertices element missing required attribute BrainStructure");
+            }
+            bool ok = false;
+            StructureEnum::Enum myStructure = StructureEnum::fromCiftiName(attrs1.value("BrainStructure").toString(), &ok);
+            if (!ok)
+            {
+                throw CiftiException("unrecognized value for BrainStructure: " + attrs1.value("BrainStructure").toString());
+            }
+            if (ret.m_surfaceNodes.find(myStructure) != ret.m_surfaceNodes.end())
+            {
+                throw CiftiException("Vertices elements may not reuse a BrainStructure within a Parcel");
+            }
+            set<int64_t>& mySet = ret.m_surfaceNodes[myStructure];
+            vector<int64_t> array = readIndexArray(xml);
+            if (xml.hasError()) return ret;
+            int64_t arraySize = (int64_t)array.size();
+            for (int64_t i = 0; i < arraySize; ++i)
+            {
+                if (mySet.find(array[i]) != mySet.end())
+                {
+                    throw CiftiException("Vertices elements may not reuse indices");
+                }
+                mySet.insert(array[i]);
+            }
+        } else if (name == "VoxelIndicesIJK") {
+            if (haveVoxels)
+            {
+                throw CiftiException("VoxelIndicesIJK may only appear once in a Parcel");
+            }
+            vector<int64_t> array = readIndexArray(xml);
+            if (xml.hasError()) return ret;
+            int64_t arraySize = (int64_t)array.size();
+            if (arraySize % 3 != 0)
+            {
+                throw CiftiException("number of indices in VoxelIndicesIJK must be a multiple of 3");
+            }
+            for (int64_t index3 = 0; index3 < arraySize; index3 += 3)
+            {
+                VoxelIJK temp(array.data() + index3);
+                if (ret.m_voxelIndices.find(temp) != ret.m_voxelIndices.end())
+                {
+                    throw CiftiException("VoxelIndicesIJK elements may not reuse voxels");
+                }
+                ret.m_voxelIndices.insert(temp);
+            }
+            haveVoxels = true;
+        } else {
+            throw CiftiException("unexpected element in Parcel: " + name.toString());
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "Vertices")
+                {
+                    vector<AString> mandAttrs1(1, "BrainStructure");
+                    XmlAttributesResult myAttrs1 = XmlReader_parseAttributes(xml, mandAttrs1);
+                    bool ok = false;
+                    StructureEnum::Enum myStructure = StructureEnum::fromCiftiName(myAttrs1.mandatoryVals[0], &ok);
+                    if (!ok)
+                    {
+                        throw CiftiException("unrecognized value for BrainStructure: " + myAttrs1.mandatoryVals[0]);
+                    }
+                    if (ret.m_surfaceNodes.find(myStructure) != ret.m_surfaceNodes.end())
+                    {
+                        throw CiftiException("Vertices elements may not reuse a BrainStructure within a Parcel");
+                    }
+                    set<int64_t>& mySet = ret.m_surfaceNodes[myStructure];
+                    vector<int64_t> array = readIndexArray(xml);
+                    int64_t arraySize = (int64_t)array.size();
+                    for (int64_t i = 0; i < arraySize; ++i)
+                    {
+                        if (mySet.find(array[i]) != mySet.end())
+                        {
+                            throw CiftiException("Vertices elements may not reuse indices");
+                        }
+                        mySet.insert(array[i]);
+                    }
+                } else if (name == "VoxelIndicesIJK") {
+                    if (haveVoxels)
+                    {
+                        throw CiftiException("VoxelIndicesIJK may only appear once in a Parcel");
+                    }
+                    vector<int64_t> array = readIndexArray(xml);
+                    int64_t arraySize = (int64_t)array.size();
+                    if (arraySize % 3 != 0)
+                    {
+                        throw CiftiException("number of indices in VoxelIndicesIJK must be a multiple of 3");
+                    }
+                    for (int64_t index3 = 0; index3 < arraySize; index3 += 3)
+                    {
+                        VoxelIJK temp(array.data() + index3);
+                        if (ret.m_voxelIndices.find(temp) != ret.m_voxelIndices.end())
+                        {
+                            throw CiftiException("VoxelIndicesIJK elements may not reuse voxels");
+                        }
+                        ret.m_voxelIndices.insert(temp);
+                    }
+                    haveVoxels = true;
+                } else {
+                    throw CiftiException("unexpected element in Parcel: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "Parcel"));
+    return ret;
+}
+
+vector<int64_t> CiftiParcelsMap::readIndexArray(XmlReader& xml)
+{
+    vector<int64_t> ret;
+    AString text = XmlReader_readElementText(xml);//raises error if it encounters a start element
+#ifdef CIFTILIB_USE_QT
+    if (xml.hasError()) return ret;
+#endif
+    vector<AString> separated = AString_split_whitespace(text);
+    int64_t numElems = separated.size();
+    ret.reserve(numElems);
+    for (int64_t i = 0; i < numElems; ++i)
+    {
+        bool ok = false;
+        ret.push_back(AString_toInt(separated[i], ok));
+        if (!ok)
+        {
+            throw CiftiException("found noninteger in index array: " + separated[i]);
+        }
+        if (ret.back() < 0)
+        {
+            throw CiftiException("found negative integer in index array: " + separated[i]);
+        }
+    }
+    return ret;
+}
+
+void CiftiParcelsMap::writeXML1(XmlWriter& xml) const
+{
+    CiftiAssert(!m_ignoreVolSpace);
+    cerr << "writing nonstandard parcels mapping type in cifti-1" << endl;
+    xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_PARCELS");
+    for (map<StructureEnum::Enum, SurfaceInfo>::const_iterator iter = m_surfInfo.begin(); iter != m_surfInfo.end(); ++iter)
+    {
+        xml.writeStartElement("Surface");
+        xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(iter->first));
+        xml.writeAttribute("SurfaceNumberOfNodes", AString_number(iter->second.m_numNodes));
+        xml.writeEndElement();
+    }
+    int64_t numParcels = m_parcels.size();
+    for (int64_t i = 0; i < numParcels; ++i)
+    {
+        xml.writeStartElement("Parcel");
+        xml.writeAttribute("Name", m_parcels[i].m_name);
+        int64_t numVoxels = (int64_t)m_parcels[i].m_voxelIndices.size();
+        if (numVoxels != 0)
+        {
+            xml.writeStartElement("VoxelIndicesIJK");
+            for (set<VoxelIJK>::const_iterator iter = m_parcels[i].m_voxelIndices.begin(); iter != m_parcels[i].m_voxelIndices.end(); ++iter)
+            {
+                xml.writeCharacters(AString_number(iter->m_ijk[0]) + " " + AString_number(iter->m_ijk[1]) + " " + AString_number(iter->m_ijk[2]) + "\n");
+            }
+            xml.writeEndElement();
+        }
+        for (map<StructureEnum::Enum, set<int64_t> >::const_iterator iter = m_parcels[i].m_surfaceNodes.begin(); iter != m_parcels[i].m_surfaceNodes.end(); ++iter)
+        {
+            if (iter->second.size() != 0)//prevent writing empty elements, regardless
+            {
+                xml.writeStartElement("Nodes");
+                xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(iter->first));
+                set<int64_t>::const_iterator iter2 = iter->second.begin();//which also allows us to write the first one outside the loop, to not add whitespace on the front or back
+                xml.writeCharacters(AString_number(*iter2));
+                ++iter2;
+                for (; iter2 != iter->second.end(); ++iter2)
+                {
+                    xml.writeCharacters(" " + AString_number(*iter2));
+                }
+                xml.writeEndElement();
+            }
+        }
+        xml.writeEndElement();
+    }
+}
+
+void CiftiParcelsMap::writeXML2(XmlWriter& xml) const
+{
+    CiftiAssert(!m_ignoreVolSpace);
+    xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_PARCELS");
+    if (hasVolumeData())//could be m_haveVolumeSpace if we want to be able to write a volspace without having voxels, but that seems silly
+    {
+        m_volSpace.writeCiftiXML2(xml);
+    }
+    for (map<StructureEnum::Enum, SurfaceInfo>::const_iterator iter = m_surfInfo.begin(); iter != m_surfInfo.end(); ++iter)
+    {
+        xml.writeStartElement("Surface");
+        xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(iter->first));
+        xml.writeAttribute("SurfaceNumberOfVertices", AString_number(iter->second.m_numNodes));
+        xml.writeEndElement();
+    }
+    int64_t numParcels = m_parcels.size();
+    for (int64_t i = 0; i < numParcels; ++i)
+    {
+        xml.writeStartElement("Parcel");
+        xml.writeAttribute("Name", m_parcels[i].m_name);
+        int64_t numVoxels = (int64_t)m_parcels[i].m_voxelIndices.size();
+        if (numVoxels != 0)
+        {
+            xml.writeStartElement("VoxelIndicesIJK");
+            for (set<VoxelIJK>::const_iterator iter = m_parcels[i].m_voxelIndices.begin(); iter != m_parcels[i].m_voxelIndices.end(); ++iter)
+            {
+                xml.writeCharacters(AString_number(iter->m_ijk[0]) + " " + AString_number(iter->m_ijk[1]) + " " + AString_number(iter->m_ijk[2]) + "\n");
+            }
+            xml.writeEndElement();
+        }
+        for (map<StructureEnum::Enum, set<int64_t> >::const_iterator iter = m_parcels[i].m_surfaceNodes.begin(); iter != m_parcels[i].m_surfaceNodes.end(); ++iter)
+        {
+            if (iter->second.size() != 0)//prevent writing empty elements, regardless
+            {
+                xml.writeStartElement("Vertices");
+                xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(iter->first));
+                set<int64_t>::const_iterator iter2 = iter->second.begin();//which also allows us to write the first one outside the loop, to not add whitespace on the front or back
+                xml.writeCharacters(AString_number(*iter2));
+                ++iter2;
+                for (; iter2 != iter->second.end(); ++iter2)
+                {
+                    xml.writeCharacters(" " + AString_number(*iter2));
+                }
+                xml.writeEndElement();
+            }
+        }
+        xml.writeEndElement();
+    }
+}
diff --git a/src/Cifti/CiftiParcelsMap.h b/src/Cifti/CiftiParcelsMap.h
new file mode 100644
index 0000000..ee8f63f
--- /dev/null
+++ b/src/Cifti/CiftiParcelsMap.h
@@ -0,0 +1,99 @@
+#ifndef __CIFTI_PARCELS_MAP_H__
+#define __CIFTI_PARCELS_MAP_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiMappingType.h"
+
+#include "Common/Compact3DLookup.h"
+#include "StructureEnum.h"
+#include "VolumeSpace.h"
+#include "Common/VoxelIJK.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+namespace cifti
+{
+    class CiftiParcelsMap : public CiftiMappingType
+    {
+    public:
+        struct Parcel
+        {
+            std::map<StructureEnum::Enum, std::set<int64_t> > m_surfaceNodes;
+            std::set<VoxelIJK> m_voxelIndices;
+            AString m_name;
+            bool operator==(const Parcel& rhs) const;
+            bool operator!=(const Parcel& rhs) const { return !((*this) == rhs); }
+            bool approximateMatch(const Parcel& rhs, AString* explanation = NULL) const;
+        };
+        bool hasVolumeData() const;
+        bool hasSurface(const StructureEnum::Enum& structure) const;//only checks whether surface has been added/read
+        bool hasSurfaceData(const StructureEnum::Enum& structure) const;
+        const VolumeSpace& getVolumeSpace() const;
+        int64_t getSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const;
+        int64_t getIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const;
+        int64_t getIndexForVoxel(const int64_t* ijk) const;
+        int64_t getIndexForVoxel(const int64_t& i, const int64_t& j, const int64_t& k) const;
+        std::vector<StructureEnum::Enum> getParcelSurfaceStructures() const;
+        const std::vector<Parcel>& getParcels() const { return m_parcels; }
+        
+        CiftiParcelsMap() { m_haveVolumeSpace = false; m_ignoreVolSpace = false; }
+        void addSurface(const int64_t& numberOfNodes, const StructureEnum::Enum& structure);
+        void setVolumeSpace(const VolumeSpace& space);
+        void addParcel(const Parcel& parcel);
+        void clear();
+        
+        CiftiMappingType* clone() const { return new CiftiParcelsMap(*this); }
+        MappingType getType() const { return PARCELS; }
+        int64_t getLength() const { return m_parcels.size(); }
+        bool operator==(const CiftiMappingType& rhs) const;
+        bool approximateMatch(const CiftiMappingType& rhs, AString* explanation = NULL) const;
+        void readXML1(XmlReader& xml);
+        void readXML2(XmlReader& xml);
+        void writeXML1(XmlWriter& xml) const;
+        void writeXML2(XmlWriter& xml) const;
+    private:
+        std::vector<Parcel> m_parcels;
+        VolumeSpace m_volSpace;
+        bool m_haveVolumeSpace, m_ignoreVolSpace;//second is needed for parsing cifti-1;
+        struct SurfaceInfo
+        {
+            int64_t m_numNodes;
+            std::vector<int64_t> m_lookup;
+        };
+        Compact3DLookup<int64_t> m_volLookup;
+        std::map<StructureEnum::Enum, SurfaceInfo> m_surfInfo;
+        static Parcel readParcel1(XmlReader& xml);
+        static Parcel readParcel2(XmlReader& xml);
+        static std::vector<int64_t> readIndexArray(XmlReader& xml);
+    };
+}
+
+#endif //__CIFTI_PARCELS_MAP_H__
diff --git a/src/Cifti/CiftiScalarsMap.cxx b/src/Cifti/CiftiScalarsMap.cxx
new file mode 100644
index 0000000..2892dda
--- /dev/null
+++ b/src/Cifti/CiftiScalarsMap.cxx
@@ -0,0 +1,412 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiScalarsMap.h"
+
+#include "Common/CiftiAssert.h"
+#include "Common/CiftiException.h"
+
+#include <iostream>
+
+using namespace std;
+using namespace cifti;
+
+void CiftiScalarsMap::clear()
+{
+    m_maps.clear();
+}
+
+const MetaData& CiftiScalarsMap::getMapMetadata(const int64_t& index) const
+{
+    CiftiAssertVectorIndex(m_maps, index);
+    return m_maps[index].m_metaData;
+}
+
+const AString& CiftiScalarsMap::getMapName(const int64_t& index) const
+{
+    CiftiAssertVectorIndex(m_maps, index);
+    return m_maps[index].m_name;
+}
+
+void CiftiScalarsMap::setMapMetadata(const int64_t& index, const MetaData& mdIn)
+{
+    CiftiAssertVectorIndex(m_maps, index);
+    m_maps[index].m_metaData = mdIn;
+}
+
+void CiftiScalarsMap::setMapName(const int64_t& index, const AString& mapName)
+{
+    CiftiAssertVectorIndex(m_maps, index);
+    m_maps[index].m_name = mapName;
+}
+
+void CiftiScalarsMap::setLength(const int64_t& length)
+{
+    CiftiAssert(length > 0);
+    m_maps.resize(length);
+}
+
+bool CiftiScalarsMap::approximateMatch(const CiftiMappingType& rhs, AString* explanation) const
+{
+    switch (rhs.getType())
+    {
+        case SCALARS:
+        case SERIES://maybe?
+        case LABELS:
+            if (getLength() != rhs.getLength())
+            {
+                if (explanation != NULL) *explanation = "mappings have different length";
+                return false;
+            } else return true;
+        default:
+            if (explanation != NULL) *explanation = CiftiMappingType::mappingTypeToName(rhs.getType()) + " mapping never matches " + CiftiMappingType::mappingTypeToName(getType());
+            return false;
+    }
+}
+
+bool CiftiScalarsMap::operator==(const CiftiMappingType& rhs) const
+{
+    if (rhs.getType() != getType()) return false;
+    const CiftiScalarsMap& myrhs = dynamic_cast<const CiftiScalarsMap&>(rhs);
+    return (m_maps == myrhs.m_maps);
+}
+
+bool CiftiScalarsMap::ScalarMap::operator==(const CiftiScalarsMap::ScalarMap& rhs) const
+{
+    if (m_name != rhs.m_name) return false;
+    return (m_metaData == rhs.m_metaData);
+}
+
+void CiftiScalarsMap::readXML1(XmlReader& xml)
+{
+    cerr << "parsing nonstandard scalars mapping type in cifti-1" << endl;
+    clear();
+#ifdef CIFTILIB_USE_QT
+    for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext())
+    {
+        switch (xml.tokenType())
+        {
+            case QXmlStreamReader::StartElement:
+            {
+                if (xml.name() != "NamedMap")
+                {
+                    throw CiftiException("unexpected element in scalars map: " + xml.name().toString());
+                }
+                m_maps.push_back(ScalarMap());
+                m_maps.back().readXML1(xml);
+                if (xml.hasError()) return;
+                break;
+            }
+            default:
+                break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "NamedMap")
+                {
+                    m_maps.push_back(ScalarMap());
+                    m_maps.back().readXML1(xml);
+                } else {
+                    throw CiftiException("unexpected element in scalars map: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+}
+
+void CiftiScalarsMap::readXML2(XmlReader& xml)
+{
+    clear();
+#ifdef CIFTILIB_USE_QT
+    for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext())
+    {
+        switch (xml.tokenType())
+        {
+            case QXmlStreamReader::StartElement:
+            {
+                if (xml.name() != "NamedMap")
+                {
+                    throw CiftiException("unexpected element in scalars map: " + xml.name().toString());
+                }
+                m_maps.push_back(ScalarMap());
+                m_maps.back().readXML2(xml);
+                if (xml.hasError()) return;
+                break;
+            }
+            default:
+                break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "NamedMap")
+                {
+                    m_maps.push_back(ScalarMap());
+                    m_maps.back().readXML2(xml);
+                } else {
+                    throw CiftiException("unexpected element in scalars map: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+}
+
+void CiftiScalarsMap::ScalarMap::readXML1(XmlReader& xml)
+{
+    bool haveName = false, haveMetaData = false;
+#ifdef CIFTILIB_USE_QT
+    for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext())
+    {
+        switch (xml.tokenType())
+        {
+            case QXmlStreamReader::StartElement:
+            {
+                QStringRef name = xml.name();
+                if (name == "MetaData")
+                {
+                    if (haveMetaData)
+                    {
+                        throw CiftiException("MetaData specified multiple times in one NamedMap");
+                    }
+                    m_metaData.readCiftiXML1(xml);
+                    if (xml.hasError()) return;
+                    haveMetaData = true;
+                } else if (name == "MapName") {
+                    if (haveName)
+                    {
+                        throw CiftiException("MapName specified multiple times in one NamedMap");
+                    }
+                    m_name = xml.readElementText();//raises error if element encountered
+                    if (xml.hasError()) return;
+                    haveName = true;
+                } else {
+                    throw CiftiException("unexpected element in NamedMap: " + name.toString());
+                }
+                break;
+            }
+            default:
+                break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "MetaData")
+                {
+                    if (haveMetaData)
+                    {
+                        throw CiftiException("MetaData specified multiple times in one NamedMap");
+                    }
+                    m_metaData.readCiftiXML1(xml);
+                    haveMetaData = true;
+                } else if (name == "MapName") {
+                    if (haveName)
+                    {
+                        throw CiftiException("MapName specified multiple times in one NamedMap");
+                    }
+                    m_name = XmlReader_readElementText(xml);//throws if element encountered
+                    haveName = true;
+                } else {
+                    throw CiftiException("unexpected element in NamedMap: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "NamedMap"));
+    if (!haveName)
+    {
+        throw CiftiException("NamedMap missing required child element MapName");
+    }
+}
+
+void CiftiScalarsMap::ScalarMap::readXML2(XmlReader& xml)
+{
+    bool haveName = false, haveMetaData = false;
+#ifdef CIFTILIB_USE_QT
+    for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext())
+    {
+        switch (xml.tokenType())
+        {
+            case QXmlStreamReader::StartElement:
+            {
+                QStringRef name = xml.name();
+                if (name == "MetaData")
+                {
+                    if (haveMetaData)
+                    {
+                        throw CiftiException("MetaData specified multiple times in one NamedMap");
+                    }
+                    m_metaData.readCiftiXML2(xml);
+                    if (xml.hasError()) return;
+                    haveMetaData = true;
+                } else if (name == "MapName") {
+                    if (haveName)
+                    {
+                        throw CiftiException("MapName specified multiple times in one NamedMap");
+                    }
+                    m_name = xml.readElementText();//raises error if element encountered
+                    if (xml.hasError()) return;
+                    haveName = true;
+                } else {
+                    throw CiftiException("unexpected element in NamedMap: " + name.toString());
+                }
+                break;
+            }
+            default:
+                break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "MetaData")
+                {
+                    if (haveMetaData)
+                    {
+                        throw CiftiException("MetaData specified multiple times in one NamedMap");
+                    }
+                    m_metaData.readCiftiXML2(xml);
+                    haveMetaData = true;
+                } else if (name == "MapName") {
+                    if (haveName)
+                    {
+                        throw CiftiException("MapName specified multiple times in one NamedMap");
+                    }
+                    m_name = XmlReader_readElementText(xml);//throws if element encountered
+                    haveName = true;
+                } else {
+                    throw CiftiException("unexpected element in NamedMap: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "NamedMap"));
+    if (!haveName)
+    {
+        throw CiftiException("NamedMap missing required child element MapName");
+    }
+}
+
+void CiftiScalarsMap::writeXML1(XmlWriter& xml) const
+{
+    cerr << "writing nonstandard scalars mapping type in cifti-1" << endl;
+    xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_SCALARS");
+    int64_t numMaps = (int64_t)m_maps.size();
+    for (int64_t i = 0; i < numMaps; ++i)
+    {
+        xml.writeStartElement("NamedMap");
+        xml.writeTextElement("MapName", m_maps[i].m_name);
+        m_maps[i].m_metaData.writeCiftiXML1(xml);
+        xml.writeEndElement();
+    }
+}
+
+void CiftiScalarsMap::writeXML2(XmlWriter& xml) const
+{
+    int64_t numMaps = (int64_t)m_maps.size();
+    xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_SCALARS");
+    for (int64_t i = 0; i < numMaps; ++i)
+    {
+        xml.writeStartElement("NamedMap");
+        xml.writeTextElement("MapName", m_maps[i].m_name);
+        m_maps[i].m_metaData.writeCiftiXML1(xml);
+        xml.writeEndElement();
+    }
+}
diff --git a/src/Cifti/CiftiScalarsMap.h b/src/Cifti/CiftiScalarsMap.h
new file mode 100644
index 0000000..080ed28
--- /dev/null
+++ b/src/Cifti/CiftiScalarsMap.h
@@ -0,0 +1,78 @@
+#ifndef __CIFTI_SCALARS_MAP_H__
+#define __CIFTI_SCALARS_MAP_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiMappingType.h"
+
+#include "MetaData.h"
+
+#include <map>
+#include <vector>
+
+namespace cifti
+{
+    class CiftiScalarsMap : public CiftiMappingType
+    {
+    public:
+        CiftiScalarsMap() { }
+        CiftiScalarsMap(const CiftiScalarsMap& rhs) { m_maps = rhs.m_maps; }
+        CiftiScalarsMap& operator=(const CiftiScalarsMap& rhs) { m_maps = rhs.m_maps; return *this; }
+        explicit CiftiScalarsMap(const int64_t& length) { setLength(length); }
+        
+        const MetaData& getMapMetadata(const int64_t& index) const;
+        const AString& getMapName(const int64_t& index) const;
+        
+        void setMapMetadata(const int64_t& index, const MetaData& mdIn);
+        void setMapName(const int64_t& index, const AString& mapName);
+        void setLength(const int64_t& length);
+        void clear();
+        
+        CiftiMappingType* clone() const { return new CiftiScalarsMap(*this); }
+        MappingType getType() const { return SCALARS; }
+        int64_t getLength() const { return m_maps.size(); }
+        bool operator==(const CiftiMappingType& rhs) const;
+        bool approximateMatch(const CiftiMappingType& rhs, AString* explanation = NULL) const;
+        void readXML1(XmlReader& xml);
+        void readXML2(XmlReader& xml);
+        void writeXML1(XmlWriter& xml) const;
+        void writeXML2(XmlWriter& xml) const;
+    private:
+        struct ScalarMap
+        {
+            AString m_name;
+            MetaData m_metaData;
+            bool operator==(const ScalarMap& rhs) const;
+            void readXML1(XmlReader& xml);
+            void readXML2(XmlReader& xml);
+        };
+        std::vector<ScalarMap> m_maps;
+    };
+}
+
+#endif //__CIFTI_SCALARS_MAP_H__
diff --git a/src/Cifti/CiftiSeriesMap.cxx b/src/Cifti/CiftiSeriesMap.cxx
new file mode 100644
index 0000000..1f37d2a
--- /dev/null
+++ b/src/Cifti/CiftiSeriesMap.cxx
@@ -0,0 +1,303 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiSeriesMap.h"
+
+#include "Common/CiftiAssert.h"
+#include "Common/CiftiException.h"
+
+#include <cmath>
+#include <iostream>
+
+using namespace cifti;
+using namespace std;
+
+void CiftiSeriesMap::setLength(const int64_t& length)
+{
+    CiftiAssert(length > 0);
+    m_length = length;
+}
+
+CiftiSeriesMap::Unit CiftiSeriesMap::stringToUnit(const AString& string, bool& ok)
+{
+    ok = true;
+    if (string == "SECOND")
+    {
+        return SECOND;
+    } else if (string == "HERTZ") {
+        return HERTZ;
+    } else if (string == "METER") {
+        return METER;
+    } else if (string == "RADIAN") {
+        return RADIAN;
+    }
+    ok = false;
+    return SECOND;
+}
+
+AString CiftiSeriesMap::unitToString(const CiftiSeriesMap::Unit& theUnit)
+{
+    switch (theUnit)
+    {
+        case SECOND:
+            return "SECOND";
+        case HERTZ:
+            return "HERTZ";
+        case METER:
+            return "METER";
+        case RADIAN:
+            return "RADIAN";
+    }
+    CiftiAssert(false);
+    return "UNKNOWN";
+}
+
+vector<CiftiSeriesMap::Unit> CiftiSeriesMap::getAllUnits()
+{
+    vector<Unit> ret;
+    ret.push_back(SECOND);
+    ret.push_back(HERTZ);
+    ret.push_back(METER);
+    ret.push_back(RADIAN);
+    return ret;
+}
+
+void CiftiSeriesMap::readXML1(XmlReader& xml)
+{
+    vector<AString> mandAttrs(2), optAttrs(1, "TimeStart");
+    mandAttrs[0] = "TimeStep";
+    mandAttrs[1] = "TimeStepUnits";
+    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs, optAttrs);
+    float newStart = 0.0f, newStep = -1.0f, mult = 0.0f;
+    bool ok = false;
+    if (myAttrs.mandatoryVals[1] == "NIFTI_UNITS_SEC")
+    {
+        mult = 1.0f;
+    } else if (myAttrs.mandatoryVals[1] == "NIFTI_UNITS_MSEC") {
+        mult = 0.001f;
+    } else if (myAttrs.mandatoryVals[1] == "NIFTI_UNITS_USEC") {
+        mult = 0.000001f;
+    } else {
+        throw CiftiException("unrecognized value for TimeStepUnits: " + myAttrs.mandatoryVals[1]);
+    }
+    if (myAttrs.optionalVals[0].present)//optional and nonstandard
+    {
+        newStart = mult * AString_toFloat(myAttrs.optionalVals[0].value, ok);
+        if (!ok)
+        {
+            throw CiftiException("unrecognized value for TimeStart: " + myAttrs.optionalVals[0].value);
+        }
+    }
+    newStep = mult * AString_toFloat(myAttrs.mandatoryVals[0], ok);
+    if (!ok)
+    {
+        throw CiftiException("unrecognized value for TimeStep: " + myAttrs.mandatoryVals[0]);
+    }
+#ifdef CIFTILIB_USE_QT
+    if (xml.readNextStartElement())
+    {
+        throw CiftiException("unexpected element in timepoints map: " + xml.name().toString());
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                throw CiftiException("unexpected element in timepoints map: " + name);
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+    m_length = -1;//cifti-1 doesn't know length in xml, must be set by checking the matrix
+    m_start = newStart;
+    m_step = newStep;
+    m_unit = SECOND;
+}
+
+void CiftiSeriesMap::readXML2(XmlReader& xml)
+{
+    vector<AString> mandAttrs(5);
+    mandAttrs[0] = "SeriesStep";
+    mandAttrs[1] = "SeriesUnit";
+    mandAttrs[2] = "SeriesExponent";
+    mandAttrs[3] = "SeriesStart";
+    mandAttrs[4] = "NumberOfSeriesPoints";
+    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs);
+    float newStart = 0.0f, newStep = -1.0f, mult = 0.0f;
+    int64_t newLength = -1;
+    Unit newUnit;
+    bool ok = false;
+    if (myAttrs.mandatoryVals[1] == "HERTZ")
+    {
+        newUnit = HERTZ;
+    } else if (myAttrs.mandatoryVals[1] == "METER") {
+        newUnit = METER;
+    } else if (myAttrs.mandatoryVals[1] == "RADIAN") {
+        newUnit = RADIAN;
+    } else if (myAttrs.mandatoryVals[1] == "SECOND") {
+        newUnit = SECOND;
+    } else {
+        throw CiftiException("unrecognized value for SeriesUnit: " + myAttrs.mandatoryVals[1]);
+    }
+    int exponent = AString_toInt(myAttrs.mandatoryVals[2], ok);
+    if (!ok)
+    {
+        throw CiftiException("unrecognized value for SeriesExponent: " + myAttrs.mandatoryVals[2]);
+    }
+    mult = pow(10.0f, exponent);
+    newStart = mult * AString_toFloat(myAttrs.mandatoryVals[3], ok);
+    if (!ok)
+    {
+        throw CiftiException("unrecognized value for SeriesStart: " + myAttrs.mandatoryVals[3]);
+    }
+    newStep = mult * AString_toFloat(myAttrs.mandatoryVals[0], ok);
+    if (!ok)
+    {
+        throw CiftiException("unrecognized value for SeriesStep: " + myAttrs.mandatoryVals[0]);
+    }
+    newLength = AString_toInt(myAttrs.mandatoryVals[4], ok);
+    if (!ok)
+    {
+        throw CiftiException("unrecognized value for NumberOfSeriesPoints: " + myAttrs.mandatoryVals[4]);
+    }
+    if (newLength < 1)
+    {
+        throw CiftiException("NumberOfSeriesPoints must be positive");
+    }
+#ifdef CIFTILIB_USE_QT
+    if (xml.readNextStartElement())
+    {
+        throw CiftiException("unexpected element in series map: " + xml.name().toString());
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                throw CiftiException("unexpected element in series map: " + name);
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+    m_length = newLength;
+    m_start = newStart;
+    m_step = newStep;
+    m_unit = newUnit;
+}
+
+void CiftiSeriesMap::writeXML1(XmlWriter& xml) const
+{
+    CiftiAssert(m_length != -1);
+    if (m_unit != SECOND)
+    {
+        cerr << "WARNING: changing series units to seconds for CIFTI-1 XML" << endl;
+    }
+    xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_TIME_POINTS");
+    float mult = 1.0f;
+    AString unitString = "NIFTI_UNITS_SEC";
+    float test = m_step;
+    if (test == 0.0f) test = m_start;
+    if (test != 0.0f)
+    {
+        if (abs(test) < 0.00005f)
+        {
+            mult = 1000000.0f;
+            unitString = "NIFTI_UNITS_USEC";
+        } else if (abs(test) < 0.05f) {
+            mult = 1000.0f;
+            unitString = "NIFTI_UNITS_MSEC";
+        }
+    }
+    xml.writeAttribute("TimeStepUnits", unitString);
+    xml.writeAttribute("TimeStart", AString_number_fixed(mult * m_start, 7));//even though it is nonstandard, write it, always
+    xml.writeAttribute("TimeStep", AString_number_fixed(mult * m_step, 7));
+}
+
+void CiftiSeriesMap::writeXML2(XmlWriter& xml) const
+{
+    CiftiAssert(m_length != -1);
+    xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_SERIES");
+    int exponent = 0;
+    float test = m_step;
+    if (test == 0.0f) test = m_start;
+    if (test != 0.0f)
+    {
+        exponent = 3 * (int)floor((log10(test) - log10(0.05f)) / 3.0f);//some magic to get the exponent that is a multiple of 3 that puts the test value in [0.05, 50]
+    }
+    float mult = pow(10.0f, -exponent);
+    AString unitString;
+    switch (m_unit)
+    {
+        case HERTZ:
+            unitString = "HERTZ";
+            break;
+        case METER:
+            unitString = "METER";
+            break;
+        case RADIAN:
+            unitString = "RADIAN";
+            break;
+        case SECOND:
+            unitString = "SECOND";
+            break;
+    }
+    xml.writeAttribute("NumberOfSeriesPoints", AString_number(m_length));
+    xml.writeAttribute("SeriesExponent", AString_number(exponent));
+    xml.writeAttribute("SeriesStart", AString_number_fixed(mult * m_start, 7));
+    xml.writeAttribute("SeriesStep", AString_number_fixed(mult * m_step, 7));
+    xml.writeAttribute("SeriesUnit", unitString);
+}
diff --git a/src/Cifti/CiftiSeriesMap.h b/src/Cifti/CiftiSeriesMap.h
new file mode 100644
index 0000000..0c28098
--- /dev/null
+++ b/src/Cifti/CiftiSeriesMap.h
@@ -0,0 +1,112 @@
+#ifndef __CIFTI_SERIES_MAP_H__
+#define __CIFTI_SERIES_MAP_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiMappingType.h"
+
+namespace cifti
+{
+    class CiftiSeriesMap : public CiftiMappingType
+    {
+    public:
+        enum Unit
+        {
+            HERTZ,
+            METER,
+            RADIAN,
+            SECOND
+        };//should this go somewhere else?
+        float getStart() const { return m_start; }//using getter/setter as style choice to match other mapping types
+        float getStep() const { return m_step; }//getter for number of series points is getLength(), specified by CiftiIndexMap
+        Unit getUnit() const { return m_unit; }
+        
+        CiftiSeriesMap()
+        {
+            m_start = 0.0f;
+            m_step = 1.0f;
+            m_unit = SECOND;
+            m_length = -1;//to make it clear an improperly initialized series map object was used
+        }
+        CiftiSeriesMap(const int64_t& length, const float& start = 0.0f, const float& step = 1.0f, const Unit& unit = SECOND)
+        {
+            m_start = start;
+            m_step = step;
+            m_unit = unit;
+            m_length = length;
+        }
+        void setStart(const float& start) { m_start = start; }
+        void setStep(const float& step) { m_step = step; }
+        void setUnit(const Unit& unit) { m_unit = unit; }
+        void setLength(const int64_t& length);
+        
+        static Unit stringToUnit(const AString& string, bool& ok);
+        static AString unitToString(const Unit& theUnit);
+        static std::vector<Unit> getAllUnits();
+        
+        CiftiMappingType* clone() const { return new CiftiSeriesMap(*this); }
+        MappingType getType() const { return SERIES; }
+        int64_t getLength() const { return m_length; }
+        bool operator==(const CiftiMappingType& rhs) const
+        {
+            if (rhs.getType() != getType()) return false;
+            const CiftiSeriesMap& temp = dynamic_cast<const CiftiSeriesMap&>(rhs);
+            return (temp.m_length == m_length &&
+                    temp.m_unit == m_unit &&
+                    temp.m_start == m_start &&
+                    temp.m_step == m_step);
+        }
+        bool approximateMatch(const CiftiMappingType& rhs, AString* explanation = NULL) const
+        {
+            switch (rhs.getType())
+            {
+                case SCALARS://maybe?
+                case SERIES:
+                case LABELS://maybe?
+                    if (getLength() != rhs.getLength())
+                    {
+                        if (explanation != NULL) *explanation = "mappings have different length";
+                        return false;
+                    } else return true;
+                default:
+                    if (explanation != NULL) *explanation = CiftiMappingType::mappingTypeToName(rhs.getType()) + " mapping never matches " + CiftiMappingType::mappingTypeToName(getType());
+                    return false;
+            }
+        }
+        void readXML1(XmlReader& xml);
+        void readXML2(XmlReader& xml);
+        void writeXML1(XmlWriter& xml) const;
+        void writeXML2(XmlWriter& xml) const;
+    private:
+        int64_t m_length;
+        float m_start, m_step;//exponent gets applied to these on reading
+        Unit m_unit;
+    };
+}
+
+#endif //__CIFTI_SERIES_MAP_H__
diff --git a/src/Cifti/CiftiVersion.cxx b/src/Cifti/CiftiVersion.cxx
new file mode 100644
index 0000000..f88e647
--- /dev/null
+++ b/src/Cifti/CiftiVersion.cxx
@@ -0,0 +1,135 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiVersion.h"
+
+#include "Common/CiftiException.h"
+
+using namespace std;
+using namespace cifti;
+
+CiftiVersion::CiftiVersion()
+{
+    m_major = 2;
+    m_minor = 0;
+}
+
+CiftiVersion::CiftiVersion(const int16_t& major, const int16_t& minor)
+{
+    m_major = major;
+    m_minor = minor;
+}
+
+CiftiVersion::CiftiVersion(const AString& versionString)
+{
+    bool ok = false;
+#ifdef CIFTILIB_USE_QT
+    int result = versionString.indexOf('.');
+    if (result < 0)
+    {
+        m_minor = 0;
+        m_major = versionString.toShort(&ok);
+        if (!ok) throw CiftiException("improperly formatted CIFTI version string: '" + versionString + "'");
+    } else {
+        if (result == 0) throw CiftiException("improperly formatted CIFTI version string: '" + versionString + "'");
+        m_major = versionString.mid(0, result).toShort(&ok);
+        if (!ok) throw CiftiException("improperly formatted CIFTI version string: '" + versionString + "'");
+        m_minor = versionString.mid(result + 1).toShort(&ok);
+        if (!ok) throw CiftiException("improperly formatted CIFTI version string: '" + versionString + "'");
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    size_t result = versionString.find('.');
+    if (result == AString::npos)
+    {
+        m_minor = 0;
+        m_major = (int16_t)AString_toInt(versionString, ok);
+        if (!ok) throw CiftiException("improperly formatted version string: " + versionString);
+    } else {
+        if (result == 0) throw CiftiException("improperly formatted version string: " + versionString);
+        m_major = (int16_t)AString_toInt(versionString.substr(0, result), ok);
+        if (!ok) throw CiftiException("improperly formatted version string: " + versionString);
+        m_minor = (int16_t)AString_toInt(versionString.substr(result + 1), ok);
+        if (!ok) throw CiftiException("improperly formatted version string: " + versionString);
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+}
+
+bool CiftiVersion::hasReversedFirstDims() const
+{
+    if (m_major == 1 && m_minor == 0) return true;
+    return false;
+}
+
+bool CiftiVersion::operator<(const CiftiVersion& rhs) const
+{
+    if (m_major < rhs.m_major) return true;
+    if (m_major == rhs.m_major && m_minor < rhs.m_minor) return true;
+    return false;
+}
+
+bool CiftiVersion::operator<=(const CiftiVersion& rhs) const
+{
+    if (m_major < rhs.m_major) return true;
+    if (m_major == rhs.m_major && m_minor <= rhs.m_minor) return true;
+    return false;
+}
+
+bool CiftiVersion::operator==(const CiftiVersion& rhs) const
+{
+    if (m_major == rhs.m_major && m_minor == rhs.m_minor) return true;
+    return false;
+}
+
+bool CiftiVersion::operator!=(const CiftiVersion& rhs) const
+{
+    return !(*this == rhs);
+}
+
+bool CiftiVersion::operator>(const cifti::CiftiVersion& rhs) const
+{
+    if (m_major > rhs.m_major) return true;
+    if (m_major == rhs.m_major && m_minor > rhs.m_minor) return true;
+    return false;
+}
+
+bool CiftiVersion::operator>=(const cifti::CiftiVersion& rhs) const
+{
+    if (m_major > rhs.m_major) return true;
+    if (m_major == rhs.m_major && m_minor >= rhs.m_minor) return true;
+    return false;
+}
+
+AString CiftiVersion::toString() const
+{
+    AString ret = AString_number(m_major);
+    if (m_minor != 0) ret += "." + AString_number(m_minor);
+    return ret;
+}
diff --git a/src/Cifti/CiftiVersion.h b/src/Cifti/CiftiVersion.h
new file mode 100644
index 0000000..b9d0b62
--- /dev/null
+++ b/src/Cifti/CiftiVersion.h
@@ -0,0 +1,59 @@
+#ifndef __CIFTI_VERSION_H__
+#define __CIFTI_VERSION_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Common/AString.h"
+
+#include "stdint.h"
+
+namespace cifti
+{
+    class CiftiVersion
+    {
+        int16_t m_major, m_minor;
+    public:
+        int16_t getMajor() const { return m_major; }
+        int16_t getMinor() const { return m_minor; }
+        
+        CiftiVersion();
+        CiftiVersion(const int16_t& major, const int16_t& minor);
+        CiftiVersion(const AString& versionString);
+        AString toString() const;
+        bool operator<(const CiftiVersion& rhs) const;
+        bool operator>(const CiftiVersion& rhs) const;
+        bool operator==(const CiftiVersion& rhs) const;
+        bool operator!=(const CiftiVersion& rhs) const;
+        bool operator<=(const CiftiVersion& rhs) const;
+        bool operator>=(const CiftiVersion& rhs) const;
+        ///quirk tests
+        bool hasReversedFirstDims() const;
+    };
+}
+
+#endif //__CIFTI_VERSION_H__
diff --git a/src/Cifti/CiftiXML.cxx b/src/Cifti/CiftiXML.cxx
new file mode 100644
index 0000000..4e0cf59
--- /dev/null
+++ b/src/Cifti/CiftiXML.cxx
@@ -0,0 +1,1045 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiXML.h"
+
+#include "Common/CiftiException.h"
+#include "Common/XmlAdapter.h"
+
+#include <cassert>
+#include <iostream>
+#include <set>
+
+using namespace std;
+using namespace boost;
+using namespace cifti;
+
+CiftiXML::CiftiXML(const CiftiXML& rhs)
+{
+    copyHelper(rhs);
+}
+
+CiftiXML& CiftiXML::operator=(const CiftiXML& rhs)
+{
+    if (this != &rhs) copyHelper(rhs);
+    return *this;
+}
+
+void CiftiXML::copyHelper(const CiftiXML& rhs)
+{
+    int numDims = (int)rhs.m_indexMaps.size();
+    m_indexMaps.resize(numDims);
+    for (int i = 0; i < numDims; ++i)
+    {
+        m_indexMaps[i] = boost::shared_ptr<CiftiMappingType>(rhs.m_indexMaps[i]->clone());
+    }
+    m_parsedVersion = rhs.m_parsedVersion;
+    m_fileMetaData = rhs.m_fileMetaData;
+}
+
+bool CiftiXML::operator==(const CiftiXML& rhs) const
+{
+    int numDims = getNumberOfDimensions();
+    if (rhs.getNumberOfDimensions() != numDims) return false;
+    if (m_fileMetaData != rhs.m_fileMetaData) return false;
+    for (int i = 0; i < numDims; ++i)
+    {
+        const CiftiMappingType* left = getMap(i), *right = rhs.getMap(i);
+        if (left == NULL && right == NULL) continue;
+        if (left == NULL || right == NULL) return false;//only one NULL, due to above test
+        if ((*left) != (*right)) return false;//finally can dereference them
+    }
+    return true;
+}
+
+bool CiftiXML::approximateMatch(const CiftiXML& rhs) const
+{
+    int numDims = getNumberOfDimensions();
+    if (rhs.getNumberOfDimensions() != numDims) return false;
+    for (int i = 0; i < numDims; ++i)
+    {
+        const CiftiMappingType* left = getMap(i), *right = rhs.getMap(i);
+        if (left == NULL && right == NULL) continue;
+        if (left == NULL || right == NULL) return false;//only one NULL, due to above test
+        if (!left->approximateMatch(*right)) return false;//finally can dereference them
+    }
+    return true;
+}
+
+const CiftiMappingType* CiftiXML::getMap(const int& direction) const
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    return m_indexMaps[direction].get();
+}
+
+CiftiMappingType* CiftiXML::getMap(const int& direction)
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    return m_indexMaps[direction].get();
+}
+
+const MetaData& CiftiXML::getFileMetaData() const
+{
+    return m_fileMetaData;
+}
+
+const CiftiBrainModelsMap& CiftiXML::getBrainModelsMap(const int& direction) const
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    CiftiAssert(getMappingType(direction) == CiftiMappingType::BRAIN_MODELS);//assert so we catch it in debug
+    return dynamic_cast<const CiftiBrainModelsMap&>(*getMap(direction));//let release throw bad_cast or segfault
+}
+
+CiftiBrainModelsMap& CiftiXML::getBrainModelsMap(const int& direction)
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    CiftiAssert(getMappingType(direction) == CiftiMappingType::BRAIN_MODELS);//assert so we catch it in debug
+    return dynamic_cast<CiftiBrainModelsMap&>(*getMap(direction));//let release throw bad_cast or segfault
+}
+
+const CiftiLabelsMap& CiftiXML::getLabelsMap(const int& direction) const
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    CiftiAssert(getMappingType(direction) == CiftiMappingType::LABELS);//assert so we catch it in debug
+    return dynamic_cast<const CiftiLabelsMap&>(*getMap(direction));//let release throw bad_cast or segfault
+}
+
+CiftiLabelsMap& CiftiXML::getLabelsMap(const int& direction)
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    CiftiAssert(getMappingType(direction) == CiftiMappingType::LABELS);//assert so we catch it in debug
+    return dynamic_cast<CiftiLabelsMap&>(*getMap(direction));//let release throw bad_cast or segfault
+}
+
+const CiftiParcelsMap& CiftiXML::getParcelsMap(const int& direction) const
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    CiftiAssert(getMappingType(direction) == CiftiMappingType::PARCELS);//assert so we catch it in debug
+    return dynamic_cast<const CiftiParcelsMap&>(*getMap(direction));//let release throw bad_cast or segfault
+}
+
+CiftiParcelsMap& CiftiXML::getParcelsMap(const int& direction)
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    CiftiAssert(getMappingType(direction) == CiftiMappingType::PARCELS);//assert so we catch it in debug
+    return dynamic_cast<CiftiParcelsMap&>(*getMap(direction));//let release throw bad_cast or segfault
+}
+
+const CiftiScalarsMap& CiftiXML::getScalarsMap(const int& direction) const
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    CiftiAssert(getMappingType(direction) == CiftiMappingType::SCALARS);//assert so we catch it in debug
+    return dynamic_cast<const CiftiScalarsMap&>(*getMap(direction));//let release throw bad_cast or segfault
+}
+
+CiftiScalarsMap& CiftiXML::getScalarsMap(const int& direction)
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    CiftiAssert(getMappingType(direction) == CiftiMappingType::SCALARS);//assert so we catch it in debug
+    return dynamic_cast<CiftiScalarsMap&>(*getMap(direction));//let release throw bad_cast or segfault
+}
+
+const CiftiSeriesMap& CiftiXML::getSeriesMap(const int& direction) const
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    CiftiAssert(getMappingType(direction) == CiftiMappingType::SERIES);//assert so we catch it in debug
+    return dynamic_cast<const CiftiSeriesMap&>(*getMap(direction));//let release throw bad_cast or segfault
+}
+
+CiftiSeriesMap& CiftiXML::getSeriesMap(const int& direction)
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    CiftiAssert(getMappingType(direction) == CiftiMappingType::SERIES);//assert so we catch it in debug
+    return dynamic_cast<CiftiSeriesMap&>(*getMap(direction));//let release throw bad_cast or segfault
+}
+
+int64_t CiftiXML::getDimensionLength(const int& direction) const
+{
+    const CiftiMappingType* tempMap = getMap(direction);
+    CiftiAssert(tempMap != NULL);
+    return tempMap->getLength();
+}
+
+vector<int64_t> CiftiXML::getDimensions() const
+{
+    vector<int64_t> ret(getNumberOfDimensions());
+    for (int i = 0; i < (int)ret.size(); ++i)
+    {
+        ret[i] = getDimensionLength(i);
+    }
+    return ret;
+}
+
+CiftiMappingType::MappingType CiftiXML::getMappingType(const int& direction) const
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    CiftiAssert(m_indexMaps[direction] != NULL);
+    return m_indexMaps[direction]->getType();
+}
+
+void CiftiXML::setMap(const int& direction, const CiftiMappingType& mapIn)
+{
+    CiftiAssertVectorIndex(m_indexMaps, direction);
+    if (mapIn.getType() == CiftiMappingType::LABELS)
+    {
+        for (int i = 0; i < getNumberOfDimensions(); ++i)
+        {
+            if (i != direction && m_indexMaps[i] != NULL && m_indexMaps[i]->getType() == CiftiMappingType::LABELS)
+            {
+                throw CiftiException("Cifti XML cannot contain more than one label mapping");
+            }
+        }
+    }
+    m_indexMaps[direction] = boost::shared_ptr<CiftiMappingType>(mapIn.clone());
+}
+
+void CiftiXML::setNumberOfDimensions(const int& num)
+{
+    m_indexMaps.resize(num);
+}
+
+void CiftiXML::clear()
+{
+    setNumberOfDimensions(0);
+    m_fileMetaData.clear();
+    m_parsedVersion = CiftiVersion();
+}
+
+void CiftiXML::readXML(const vector<char>& text)
+{
+#ifdef CIFTILIB_USE_QT
+    vector<char> text2 = text;
+    text2.push_back('\0');//make sure it has a null terminator
+    XmlReader xml(text2.data());//so it works as a C string
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    vector<char>::const_iterator end = find(text.begin(), text.end(), '\0');//find the null terminator, if it exists, to prevent "extra content at end of document" errors
+    XmlReader xml((unsigned char*)text.data(), end - text.begin());//get the number of bytes
+#else
+#error "not implemented"
+#endif
+#endif
+    readXML(xml);
+}
+
+int32_t CiftiXML::getIntentInfo(const CiftiVersion& writingVersion, char intentNameOut[16]) const
+{
+    int32_t ret;
+    const char* name = NULL;
+    if (writingVersion == CiftiVersion(1, 0))//cifti-1: unknown didn't exist, and "ConnDense" was default in workbench
+    {
+        ret = 3001;//default
+        name = "ConnDense";
+        if (getNumberOfDimensions() > 0 && getMappingType(0) == CiftiMappingType::SERIES) { ret = 3002; name = "ConnDenseTime"; }//same logic as was used in workbench
+        if (getNumberOfDimensions() > 1 && getMappingType(1) == CiftiMappingType::SERIES) { ret = 3002; name = "ConnDenseTime"; }//NOTE: name for this code is different than cifti-2
+    } else if (writingVersion == CiftiVersion(1, 1) || writingVersion == CiftiVersion(2, 0)) {//cifti-2
+        ret = 3000;//default
+        name = "ConnUnknown";
+        switch (getNumberOfDimensions())
+        {
+            case 2:
+            {
+                CiftiMappingType::MappingType first = getMappingType(0), second = getMappingType(1);
+                if (first == CiftiMappingType::BRAIN_MODELS && second == CiftiMappingType::BRAIN_MODELS) { ret = 3001; name = "ConnDense"; }
+                if (first == CiftiMappingType::SERIES && second == CiftiMappingType::BRAIN_MODELS) { ret = 3002; name = "ConnDenseSeries"; }
+                if (first == CiftiMappingType::PARCELS && second == CiftiMappingType::PARCELS) { ret = 3003; name = "ConnParcels"; }
+                if (first == CiftiMappingType::SERIES && second == CiftiMappingType::PARCELS) { ret = 3004; name = "ConnParcelSries"; }//NOTE: 3005 is reserved but not used
+                if (first == CiftiMappingType::SCALARS && second == CiftiMappingType::BRAIN_MODELS) { ret = 3006; name = "ConnDenseScalar"; }
+                if (first == CiftiMappingType::LABELS && second == CiftiMappingType::BRAIN_MODELS) { ret = 3007; name = "ConnDenseLabel"; }
+                if (first == CiftiMappingType::SCALARS && second == CiftiMappingType::PARCELS) { ret = 3008; name = "ConnParcelScalr"; }
+                if (first == CiftiMappingType::BRAIN_MODELS && second == CiftiMappingType::PARCELS) { ret = 3009; name = "ConnParcelDense"; }
+                if (first == CiftiMappingType::PARCELS && second == CiftiMappingType::BRAIN_MODELS) { ret = 3010; name = "ConnDenseParcel"; }
+                break;
+            }
+            case 3:
+            {
+                CiftiMappingType::MappingType first = getMappingType(0), second = getMappingType(1), third = getMappingType(2);
+                if (first == CiftiMappingType::PARCELS && second == CiftiMappingType::PARCELS && third == CiftiMappingType::SERIES) { ret = 3011; name = "ConnPPSr"; }
+                if (first == CiftiMappingType::PARCELS && second == CiftiMappingType::PARCELS && third == CiftiMappingType::SCALARS) { ret = 3012; name = "ConnPPSc"; }
+                break;
+            }
+            default:
+                break;
+        }
+    } else {
+        throw CiftiException("unknown cifti version: " + writingVersion.toString());
+    }
+    int i;
+    for (i = 0; i < 16 && name[i] != '\0'; ++i) intentNameOut[i] = name[i];
+    for (; i < 16; ++i) intentNameOut[i] = '\0';
+    return ret;
+}
+
+void CiftiXML::readXML(XmlReader& xml)
+{
+    clear();
+    bool haveCifti = false;
+    try
+    {
+#ifdef CIFTILIB_USE_QT
+        for (; !xml.atEnd(); xml.readNext())
+        {
+            if (xml.isStartElement())
+            {
+                QStringRef name = xml.name();
+                if (name == "CIFTI")
+                {
+                    if (haveCifti)
+                    {
+                        throw CiftiException("CIFTI element may only be specified once");
+                    }
+                    QXmlStreamAttributes attributes = xml.attributes();
+                    if(attributes.hasAttribute("Version"))
+                    {
+                        m_parsedVersion = CiftiVersion(attributes.value("Version").toString());
+                    } else {
+                        throw CiftiException("Cifti XML missing Version attribute.");
+                    }
+                    if (m_parsedVersion == CiftiVersion(1, 0))//switch/case on major/minor would be much harder to read
+                    {
+                        parseCIFTI1(xml);
+                        if (xml.hasError()) break;
+                    } else if (m_parsedVersion == CiftiVersion(2, 0)) {
+                        parseCIFTI2(xml);
+                        if (xml.hasError()) break;
+                    } else {
+                        throw CiftiException("unknown Cifti Version: '" + m_parsedVersion.toString());
+                    }
+                    haveCifti = true;
+                } else {
+                    throw CiftiException("unexpected root element in Cifti XML: " + name.toString());
+                }
+            }
+        }
+        if (!xml.hasError() && !haveCifti)
+        {
+            throw CiftiException("CIFTI element not found");
+        }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+        while (true)//don't want to skip the first element, but there is no obvious function for "at end"
+        {
+            switch (xml.get_node_type())
+            {
+                case XmlReader::Element:
+                {
+                    AString name = xml.get_local_name();
+                    if (name == "CIFTI")
+                    {
+                        if (haveCifti)
+                        {
+                            throw CiftiException("CIFTI element may only be specified once");
+                        }
+                        vector<AString> mandAttrs(1, "Version");
+                        XmlAttributesResult attrVals = XmlReader_parseAttributes(xml, mandAttrs);
+                        m_parsedVersion = CiftiVersion(attrVals.mandatoryVals[0]);
+                        if (m_parsedVersion == CiftiVersion(1, 0))//switch/case on major/minor would be much harder to read
+                        {
+                            parseCIFTI1(xml);
+                        } else if (m_parsedVersion == CiftiVersion(2, 0)) {
+                            parseCIFTI2(xml);
+                        } else {
+                            throw CiftiException("unknown Cifti Version: '" + m_parsedVersion.toString());
+                        }
+                        haveCifti = true;
+                    } else {
+                        throw CiftiException("unexpected root element in Cifti XML: " + name);
+                    }
+                }
+                default:
+                    break;
+            }
+            if (!xml.read()) break;
+        }
+#else
+#error "not implemented"
+#endif
+#endif
+    } catch (CiftiException& e) {
+        throw CiftiException("Cifti XML error: " + e.whatString());//so we can throw on error instead of doing a bunch of dancing with xml.raiseError and xml.hasError
+    } catch (std::exception& e) {//libxml++ throws things that inherit from std::exception, catch them too
+        throw CiftiException("Cifti XML error: " + AString(e.what()));
+    }
+#ifdef CIFTILIB_USE_QT
+    if(xml.hasError())
+    {
+        throw CiftiException("Cifti XML error: " + xml.errorString());
+    }
+#endif
+}
+
+void CiftiXML::parseCIFTI1(XmlReader& xml)
+{
+    bool haveMatrix = false;
+#ifdef CIFTILIB_USE_QT
+    QXmlStreamAttributes attributes = xml.attributes();
+    if (attributes.hasAttribute("NumberOfMatrices"))
+    {
+        if (attributes.value("NumberOfMatrices") != "1")
+        {
+            throw CiftiException("attribute NumberOfMatrices in CIFTI is required to be 1 for CIFTI-1");
+        }
+    } else {
+        throw CiftiException("missing attribute NumberOfMatrices in CIFTI");
+    }
+    while (!xml.atEnd())
+    {
+        xml.readNext();
+        if (xml.isStartElement())
+        {
+            QStringRef name = xml.name();
+            if (name == "Matrix")
+            {
+                if (haveMatrix)
+                {
+                    throw CiftiException("Matrix element may only be specified once");
+                }
+                parseMatrix1(xml);
+                if (xml.hasError()) return;
+                haveMatrix = true;
+            } else {
+                throw CiftiException("unexpected element in CIFTI: " + name.toString());
+            }
+        } else if (xml.isEndElement()) {
+            break;
+        }
+    }
+    if (xml.hasError()) return;
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    vector<AString> mandAttrs(1, "NumberOfMatrices");
+    XmlAttributesResult attrVals = XmlReader_parseAttributes(xml, mandAttrs);
+    if (attrVals.mandatoryVals[0] != "1")
+    {
+        throw CiftiException("attribute NumberOfMatrices in CIFTI is required to be 1 for CIFTI-1");
+    }
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "Matrix")
+                {
+                    if (haveMatrix)
+                    {
+                        throw CiftiException("Matrix element may only be specified once");
+                    }
+                    parseMatrix1(xml);
+                    haveMatrix = true;
+                } else {
+                    throw CiftiException("unexpected element in CIFTI: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "CIFTI"));
+    if (!haveMatrix)
+    {
+        throw CiftiException("Matrix element not found in CIFTI");
+    }
+}
+
+void CiftiXML::parseCIFTI2(XmlReader& xml)//yes, these will often have largely similar code, but it seems cleaner than having only some functions split, or constantly rechecking the version
+{//also, helps keep changes to cifti-2 away from code that parses cifti-1
+    bool haveMatrix = false;
+#ifdef CIFTILIB_USE_QT
+    while (!xml.atEnd())
+    {
+        xml.readNext();
+        if (xml.hasError()) return;
+        if (xml.isStartElement())
+        {
+            QStringRef name = xml.name();
+            if (name == "Matrix")
+            {
+                if (haveMatrix)
+                {
+                    throw CiftiException("Matrix element may only be specified once");
+                }
+                parseMatrix2(xml);
+                if (xml.hasError()) return;
+                haveMatrix = true;
+            } else {
+                throw CiftiException("unexpected element in CIFTI: " + name.toString());
+            }
+        } else if (xml.isEndElement()) {
+            break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "Matrix")
+                {
+                    if (haveMatrix)
+                    {
+                        throw CiftiException("Matrix element may only be specified once");
+                    }
+                    parseMatrix2(xml);
+                    haveMatrix = true;
+                } else {
+                    throw CiftiException("unexpected element in CIFTI: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "CIFTI"));
+    if (!haveMatrix)
+    {
+        throw CiftiException("Matrix element not found in CIFTI");
+    }
+}
+
+void CiftiXML::parseMatrix1(XmlReader& xml)
+{
+    VolumeSpace fileVolSpace;
+    bool haveVolSpace = false, haveMetadata = false;
+#ifdef CIFTILIB_USE_QT
+    while (!xml.atEnd())
+    {
+        xml.readNext();
+        if (xml.hasError()) return;
+        if (xml.isStartElement())
+        {
+            QStringRef name = xml.name();
+            if (name == "MetaData")
+            {
+                if (haveMetadata)
+                {
+                    throw CiftiException("MetaData may only be specified once in Matrix");
+                }
+                m_fileMetaData.readCiftiXML1(xml);
+                if (xml.hasError()) return;
+                haveMetadata = true;
+            } else if (name == "MatrixIndicesMap") {
+                parseMatrixIndicesMap1(xml);
+                if (xml.hasError()) return;
+            } else if (name == "Volume") {
+                if (haveVolSpace)
+                {
+                    throw CiftiException("Volume may only be specified once in Matrix");
+                }
+                fileVolSpace.readCiftiXML1(xml);
+                if (xml.hasError()) return;
+                haveVolSpace = true;
+            } else if (name == "LabelTable") {
+                xml.readElementText(XmlReader::SkipChildElements);
+            } else {
+                throw CiftiException("unexpected element in Matrix: " + name.toString());
+            }
+        } else if (xml.isEndElement()) {
+            break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool skipread = false, done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done)
+    {
+        if (!skipread)
+        {
+            if (!xml.read()) break;
+        } else {
+            skipread = false;
+        }
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "MetaData")
+                {
+                    if (haveMetadata)
+                    {
+                        throw CiftiException("MetaData may only be specified once in Matrix");
+                    }
+                    m_fileMetaData.readCiftiXML1(xml);
+                    haveMetadata = true;
+                } else if (name == "MatrixIndicesMap") {
+                    parseMatrixIndicesMap1(xml);
+                } else if (name == "Volume") {
+                    if (haveVolSpace)
+                    {
+                        throw CiftiException("Volume may only be specified once in Matrix");
+                    }
+                    fileVolSpace.readCiftiXML1(xml);
+                    haveVolSpace = true;
+                } else if (name == "LabelTable") {
+                    xml.next();//TODO: test if this does this actually does what we want
+                    skipread = true;
+                } else {
+                    throw CiftiException("unexpected element in Matrix: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "Matrix"));
+    for (int i = 0; i < (int)m_indexMaps.size(); ++i)
+    {
+        if (m_indexMaps[i] == NULL)
+        {
+            int displaynum = i;
+            if (displaynum < 2) displaynum = 1 - displaynum;//re-invert so that it shows the same number as the XML is missing
+            throw CiftiException("missing mapping for dimension '" + AString_number(displaynum) + "'");
+        }
+        switch (m_indexMaps[i]->getType())
+        {
+            case CiftiMappingType::BRAIN_MODELS:
+            {
+                CiftiBrainModelsMap& myMap = dynamic_cast<CiftiBrainModelsMap&>(*(m_indexMaps[i]));
+                if (myMap.hasVolumeData())
+                {
+                    if (haveVolSpace)
+                    {
+                        myMap.setVolumeSpace(fileVolSpace);//also does the needed checking of voxel indices
+                    } else {
+                        throw CiftiException("BrainModels map uses voxels, but no Volume element exists");
+                    }
+                }
+                break;
+            }
+            case CiftiMappingType::PARCELS:
+            {
+                CiftiParcelsMap& myMap = dynamic_cast<CiftiParcelsMap&>(*(m_indexMaps[i]));
+                if (myMap.hasVolumeData())
+                {
+                    if (haveVolSpace)
+                    {
+                        myMap.setVolumeSpace(fileVolSpace);//ditto
+                    } else {
+                        throw CiftiException("Parcels map uses voxels, but no Volume element exists");
+                    }
+                }
+                break;
+            }
+            default:
+                break;
+        }
+    }
+}
+
+void CiftiXML::parseMatrix2(XmlReader& xml)
+{
+    bool haveMetadata = false;
+#ifdef CIFTILIB_USE_QT
+    while (!xml.atEnd())
+    {
+        xml.readNext();
+        if (xml.hasError()) return;
+        if (xml.isStartElement())
+        {
+            QStringRef name = xml.name();
+            if (name == "MetaData")
+            {
+                if (haveMetadata)
+                {
+                    throw CiftiException("MetaData may only be specified once in Matrix");
+                }
+                m_fileMetaData.readCiftiXML2(xml);
+                if (xml.hasError()) return;
+                haveMetadata = true;
+            } else if (name == "MatrixIndicesMap") {
+                parseMatrixIndicesMap2(xml);
+                if (xml.hasError()) return;
+            } else {
+                throw CiftiException("unexpected element in Matrix: " + name.toString());
+            }
+        } else if (xml.isEndElement()) {
+            break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "MetaData")
+                {
+                    if (haveMetadata)
+                    {
+                        throw CiftiException("MetaData may only be specified once in Matrix");
+                    }
+                    m_fileMetaData.readCiftiXML2(xml);
+                    haveMetadata = true;
+                } else if (name == "MatrixIndicesMap") {
+                    parseMatrixIndicesMap2(xml);
+                } else {
+                    throw CiftiException("unexpected element in Matrix: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "Matrix"));
+    for (int i = 0; i < (int)m_indexMaps.size(); ++i)
+    {
+        if (m_indexMaps[i] == NULL)
+        {
+            throw CiftiException("missing mapping for dimension '" + AString_number(i) + "'");
+        }
+    }
+}
+
+void CiftiXML::parseMatrixIndicesMap1(XmlReader& xml)
+{
+    vector<AString> mandAttrs(2);
+    mandAttrs[0] = "AppliesToMatrixDimension";
+    mandAttrs[1] = "IndicesMapToDataType";
+    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs);
+    vector<AString> values = AString_split(myAttrs.mandatoryVals[0], ',');
+    bool ok = false;
+    set<int> used;
+    for(int i = 0; i < (int)values.size(); i++)
+    {
+        int parsed = AString_toInt(values[i], ok);
+        if (!ok || parsed < 0)
+        {
+            throw CiftiException("bad value in AppliesToMatrixDimension list: " + values[i]);
+        }
+        if (parsed < 2) parsed = 1 - parsed;//in other words, 0 becomes 1 and 1 becomes 0, since cifti-1 had them reversed
+        if (used.find(parsed) != used.end())
+        {
+            throw CiftiException("AppliesToMatrixDimension contains repeated value: " + values[i]);
+        }
+        used.insert(parsed);
+    }
+    boost::shared_ptr<CiftiMappingType> toRead;
+    AString type = myAttrs.mandatoryVals[1];
+    if (type == "CIFTI_INDEX_TYPE_BRAIN_MODELS")
+    {
+        toRead = boost::shared_ptr<CiftiBrainModelsMap>(new CiftiBrainModelsMap());
+    } else if (type == "CIFTI_INDEX_TYPE_TIME_POINTS") {
+        toRead = boost::shared_ptr<CiftiSeriesMap>(new CiftiSeriesMap());
+    } else if (type == "CIFTI_INDEX_TYPE_LABELS") {//this and below are nonstandard
+        toRead = boost::shared_ptr<CiftiLabelsMap>(new CiftiLabelsMap());
+    } else if (type == "CIFTI_INDEX_TYPE_PARCELS") {
+        toRead = boost::shared_ptr<CiftiParcelsMap>(new CiftiParcelsMap());
+    } else if (type == "CIFTI_INDEX_TYPE_SCALARS") {
+        toRead = boost::shared_ptr<CiftiScalarsMap>(new CiftiScalarsMap());
+    } else {
+        throw CiftiException("invalid value for IndicesMapToDataType in CIFTI-1: " + type);
+    }
+    toRead->readXML1(xml);//this will warn if it is nonstandard
+#ifdef CIFTILIB_USE_QT
+    if (xml.hasError()) return;
+#endif
+    if (toRead->getLength() < 1 && !(type == "CIFTI_INDEX_TYPE_TIME_POINTS" && toRead->getLength() == -1)) throw CiftiException("cifti mapping type with zero length found");
+    bool first = true;//NOTE: series maps didn't encode length in cifti-1, -1 is used as a stand-in, CiftiFile fills in the length from the matrix
+    for (set<int>::iterator iter = used.begin(); iter != used.end(); ++iter)
+    {
+        if (*iter >= (int)m_indexMaps.size()) m_indexMaps.resize(*iter + 1);
+        if (first)
+        {
+            m_indexMaps[*iter] = toRead;
+            first = false;
+        } else {
+            m_indexMaps[*iter] = boost::shared_ptr<CiftiMappingType>(toRead->clone());//make in-memory information independent per-dimension, rather than dealing with deduplication everywhere
+        }
+    }
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+}
+
+void CiftiXML::parseMatrixIndicesMap2(XmlReader& xml)
+{
+    vector<AString> mandAttrs(2);
+    mandAttrs[0] = "AppliesToMatrixDimension";
+    mandAttrs[1] = "IndicesMapToDataType";
+    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs);
+    vector<AString> values = AString_split(myAttrs.mandatoryVals[0], ',');
+    bool ok = false;
+    set<int> used;
+    for(int i = 0; i < (int)values.size(); i++)
+    {
+        int parsed = AString_toInt(values[i], ok);
+        if (!ok || parsed < 0)
+        {
+            throw CiftiException("bad value in AppliesToMatrixDimension list: " + values[i]);
+        }
+        if (used.find(parsed) != used.end())
+        {
+            throw CiftiException("AppliesToMatrixDimension contains repeated value: " + values[i]);
+        }
+        used.insert(parsed);
+    }
+    boost::shared_ptr<CiftiMappingType> toRead;
+    AString type = myAttrs.mandatoryVals[1];
+    if (type == "CIFTI_INDEX_TYPE_BRAIN_MODELS")
+    {
+        toRead = boost::shared_ptr<CiftiBrainModelsMap>(new CiftiBrainModelsMap());
+    } else if (type == "CIFTI_INDEX_TYPE_LABELS") {
+        toRead = boost::shared_ptr<CiftiLabelsMap>(new CiftiLabelsMap());
+    } else if (type == "CIFTI_INDEX_TYPE_PARCELS") {
+        toRead = boost::shared_ptr<CiftiParcelsMap>(new CiftiParcelsMap());
+    } else if (type == "CIFTI_INDEX_TYPE_SCALARS") {
+        toRead = boost::shared_ptr<CiftiScalarsMap>(new CiftiScalarsMap());
+    } else if (type == "CIFTI_INDEX_TYPE_SERIES") {
+        toRead = boost::shared_ptr<CiftiSeriesMap>(new CiftiSeriesMap());
+    } else {
+        throw CiftiException("invalid value for IndicesMapToDataType in CIFTI-1: " + type);
+    }
+    toRead->readXML2(xml);
+#ifdef CIFTILIB_USE_QT
+    if (xml.hasError()) return;
+#endif
+    if (toRead->getLength() < 1) throw CiftiException("cifti mapping type with zero length found");
+    bool first = true;
+    for (set<int>::iterator iter = used.begin(); iter != used.end(); ++iter)
+    {
+        if (*iter >= (int)m_indexMaps.size()) m_indexMaps.resize(*iter + 1);
+        if (first)
+        {
+            m_indexMaps[*iter] = toRead;
+            first = false;
+        } else {
+            m_indexMaps[*iter] = boost::shared_ptr<CiftiMappingType>(toRead->clone());//make in-memory information independent per-dimension, rather than dealing with deduplication everywhere
+        }
+    }
+    CiftiAssert(XmlReader_checkEndElement(xml, "MatrixIndicesMap"));
+}
+
+vector<char> CiftiXML::writeXMLToVector(const CiftiVersion& writingVersion) const
+{
+#ifdef CIFTILIB_USE_QT
+    QByteArray tempArray;
+    XmlWriter xml(&tempArray);
+    xml.setAutoFormatting(true);
+    xml.writeStartDocument();
+    writeXML(xml, writingVersion);
+    xml.writeEndDocument();
+    int numBytes = tempArray.size();//QByteArray is limited to 2GB
+    vector<char> ret(numBytes + 1);//include room for null terminator
+    for (int i = 0; i < numBytes; ++i)
+    {
+        ret[i] = tempArray[i];
+    }
+    ret[numBytes] = '\0';
+    return ret;
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    XmlWriter xml;
+    xml.writeStartDocument();
+    writeXML(xml, writingVersion);
+    xml.writeEndDocument();
+    return xml.getXmlData();
+#else
+#error "not implemented"
+#endif
+#endif
+}
+
+void CiftiXML::writeXML(XmlWriter& xml, const CiftiVersion& writingVersion) const
+{
+    xml.writeStartElement("CIFTI");
+    xml.writeAttribute("Version", writingVersion.toString());
+    if (writingVersion == CiftiVersion(1, 0))//switch/case on major/minor would be much harder to read
+    {
+        xml.writeAttribute("NumberOfMatrices", "1");
+        writeMatrix1(xml);
+    } else if (writingVersion == CiftiVersion(2, 0)) {
+        writeMatrix2(xml);
+    } else {
+        throw CiftiException("unknown Cifti writing version: '" + writingVersion.toString() + "'");
+    }
+    xml.writeEndElement();
+}
+
+void CiftiXML::writeMatrix1(XmlWriter& xml) const
+{
+    int numDims = (int)m_indexMaps.size();
+    bool haveVolData = false;
+    VolumeSpace volSpace;
+    for (int i = 0; i < numDims; ++i)
+    {
+        if (m_indexMaps[i] == NULL) throw CiftiException("dimension " + AString_number(i) + " was not given a mapping");
+        switch (m_indexMaps[i]->getType())
+        {
+            case CiftiMappingType::BRAIN_MODELS:
+            {
+                const CiftiBrainModelsMap& myMap = dynamic_cast<const CiftiBrainModelsMap&>(*(m_indexMaps[i]));
+                if (myMap.hasVolumeData())
+                {
+                    if (haveVolData)
+                    {
+                        if (myMap.getVolumeSpace() != volSpace)
+                        {
+                            throw CiftiException("cannot write different volume spaces for different dimensions in CIFTI-1");
+                        }
+                    } else {
+                        haveVolData = true;
+                        volSpace = myMap.getVolumeSpace();
+                    }
+                }
+                break;
+            }
+            case CiftiMappingType::PARCELS:
+            {
+                const CiftiParcelsMap& myMap = dynamic_cast<const CiftiParcelsMap&>(*(m_indexMaps[i]));
+                if (myMap.hasVolumeData())
+                {
+                    if (haveVolData)
+                    {
+                        if (myMap.getVolumeSpace() != volSpace)
+                        {
+                            throw CiftiException("cannot write different volume spaces for different dimensions in CIFTI-1");
+                        }
+                    } else {
+                        haveVolData = true;
+                        volSpace = myMap.getVolumeSpace();
+                    }
+                }
+                break;
+            }
+            default:
+                break;
+        }
+    }
+    xml.writeStartElement("Matrix");
+    m_fileMetaData.writeCiftiXML1(xml);
+    if (haveVolData)
+    {
+        volSpace.writeCiftiXML1(xml);
+    }
+    vector<bool> used(numDims, false);
+    for (int i = 0; i < numDims; ++i)
+    {
+        if (!used[i])
+        {
+            used[i] = true;
+            int outputNum = i;
+            if (outputNum < 2) outputNum = 1 - outputNum;//ie, swap 0 and 1
+            AString appliesTo = AString_number(outputNum);//initialize containing just the current dimension
+            for (int j = i + 1; j < numDims; ++j)//compare to all later unused dimensions for deduplication
+            {//technically, shouldn't need to check for previously used as long as equality is exact, but means maybe fewer comparisons, and to prevent a bug in == from getting stranger behavior
+                if (!used[j])
+                {
+                    if ((*m_indexMaps[i]) == (*m_indexMaps[j]))
+                    {
+                        outputNum = j;
+                        if (outputNum < 2) outputNum = 1 - outputNum;
+                        appliesTo += "," + AString_number(outputNum);
+                        used[j] = true;
+                    }
+                }
+            }
+            xml.writeStartElement("MatrixIndicesMap");//should the CiftiIndexMap do this instead, and we pass appliesTo to it as string?  probably not important, we won't use them in any other xml
+            xml.writeAttribute("AppliesToMatrixDimension", appliesTo);
+            m_indexMaps[i]->writeXML1(xml);
+            xml.writeEndElement();
+        }
+    }
+    xml.writeEndElement();
+}
+
+void CiftiXML::writeMatrix2(XmlWriter& xml) const
+{
+    int numDims = (int)m_indexMaps.size();
+    for (int i = 0; i < numDims; ++i)
+    {
+        if (m_indexMaps[i] == NULL) throw CiftiException("dimension " + AString_number(i) + " was not given a mapping");
+    }
+    xml.writeStartElement("Matrix");
+    m_fileMetaData.writeCiftiXML2(xml);
+    vector<bool> used(numDims, false);
+    for (int i = 0; i < numDims; ++i)
+    {
+        if (!used[i])
+        {
+            used[i] = true;
+            AString appliesTo = AString_number(i);//initialize containing just the current dimension
+            for (int j = i + 1; j < numDims; ++j)//compare to all later unused dimensions for deduplication
+            {//technically, shouldn't need to check for previously used as long as equality is exact, but means maybe fewer comparisons, and to prevent a bug in == from getting stranger behavior
+                if (!used[j])
+                {
+                    if ((*m_indexMaps[i]) == (*m_indexMaps[j]))
+                    {
+                        appliesTo += "," + AString_number(j);
+                        used[j] = true;
+                    }
+                }
+            }
+            xml.writeStartElement("MatrixIndicesMap");//should the CiftiIndexMap do this instead, and we pass appliesTo to it as string?  probably not important, we won't use them in any other xml
+            xml.writeAttribute("AppliesToMatrixDimension", appliesTo);
+            m_indexMaps[i]->writeXML2(xml);
+            xml.writeEndElement();
+        }
+    }
+    xml.writeEndElement();
+}
diff --git a/src/Cifti/CiftiXML.h b/src/Cifti/CiftiXML.h
new file mode 100644
index 0000000..07a80eb
--- /dev/null
+++ b/src/Cifti/CiftiXML.h
@@ -0,0 +1,120 @@
+#ifndef __CIFTI_XML_H__
+#define __CIFTI_XML_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiMappingType.h"
+#include "CiftiVersion.h"
+#include "MetaData.h"
+
+#include "CiftiBrainModelsMap.h"
+#include "CiftiLabelsMap.h"
+#include "CiftiParcelsMap.h"
+#include "CiftiScalarsMap.h"
+#include "CiftiSeriesMap.h"
+
+#include "boost/shared_ptr.hpp"
+
+#include <vector>
+
+namespace cifti
+{
+    ///class for retrieving and setting mapping information of cifti files
+    class CiftiXML
+    {
+    public:
+        enum
+        {
+            ALONG_ROW = 0,
+            ALONG_COLUMN = 1,
+            ALONG_STACK = 2//better name for this?
+        };
+        int getNumberOfDimensions() const { return m_indexMaps.size(); }
+        const CiftiVersion& getParsedVersion() const { return m_parsedVersion; }
+        
+        ///can return null in unfilled XML object
+        const CiftiMappingType* getMap(const int& direction) const;
+        
+        ///can return null in unfilled XML object
+        CiftiMappingType* getMap(const int& direction);
+        const MetaData& getFileMetaData() const;
+        
+        CiftiMappingType::MappingType getMappingType(const int& direction) const;//convenience functions
+        const CiftiBrainModelsMap& getBrainModelsMap(const int& direction) const;
+        CiftiBrainModelsMap& getBrainModelsMap(const int& direction);
+        const CiftiLabelsMap& getLabelsMap(const int& direction) const;
+        CiftiLabelsMap& getLabelsMap(const int& direction);
+        const CiftiParcelsMap& getParcelsMap(const int& direction) const;
+        CiftiParcelsMap& getParcelsMap(const int& direction);
+        const CiftiScalarsMap& getScalarsMap(const int& direction) const;
+        CiftiScalarsMap& getScalarsMap(const int& direction);
+        const CiftiSeriesMap& getSeriesMap(const int& direction) const;
+        CiftiSeriesMap& getSeriesMap(const int& direction);
+        int64_t getDimensionLength(const int& direction) const;
+        std::vector<int64_t> getDimensions() const;
+        
+        void setNumberOfDimensions(const int& num);
+        void setMap(const int& direction, const CiftiMappingType& mapIn);
+        void setFileMetaData(const MetaData& mdIn) { m_fileMetaData = mdIn; }
+        void clear();
+        
+        void readXML(XmlReader& xml);
+        void readXML(const std::vector<char>& text);
+        
+        std::vector<char> writeXMLToVector(const CiftiVersion& writingVersion = CiftiVersion()) const;
+        void writeXML(XmlWriter& xml, const CiftiVersion& writingVersion = CiftiVersion()) const;
+        
+        ///uses the mapping types to figure out what the intent info should be
+        int32_t getIntentInfo(const CiftiVersion& writingVersion, char intentNameOut[16]) const;
+        
+        CiftiXML() { }
+        CiftiXML(const CiftiXML& rhs);
+        CiftiXML& operator=(const CiftiXML& rhs);
+        bool operator==(const CiftiXML& rhs) const;
+        bool operator!=(const CiftiXML& rhs) const { return !((*this) == rhs); }
+        bool approximateMatch(const CiftiXML& rhs) const;
+    private:
+        std::vector<boost::shared_ptr<CiftiMappingType> > m_indexMaps;
+        CiftiVersion m_parsedVersion;
+        MetaData m_fileMetaData;
+        
+        void copyHelper(const CiftiXML& rhs);
+        //parsing functions
+        void parseCIFTI1(XmlReader& xml);
+        void parseMatrix1(XmlReader& xml);
+        void parseCIFTI2(XmlReader& xml);
+        void parseMatrix2(XmlReader& xml);
+        void parseMatrixIndicesMap1(XmlReader& xml);
+        void parseMatrixIndicesMap2(XmlReader& xml);
+        //writing functions
+        void writeMatrix1(XmlWriter& xml) const;
+        void writeMatrix2(XmlWriter& xml) const;
+    };
+}
+
+#endif //__CIFTI_XML_H__
diff --git a/src/Cifti/Label.cxx b/src/Cifti/Label.cxx
new file mode 100644
index 0000000..685e237
--- /dev/null
+++ b/src/Cifti/Label.cxx
@@ -0,0 +1,503 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sstream>
+
+#include "Label.h"
+
+#include <limits>
+
+using namespace cifti;
+
+const int32_t Label::s_invalidLabelKey =  std::numeric_limits<int32_t>::min(); 
+
+/**
+ * Constructor.
+ *
+ * @param key - key of the label.
+ * @param name - name of label.
+ *
+ */
+Label::Label(
+                   const int32_t key,
+                   const AString& name)
+{
+    this->initializeMembersLabel();
+    this->key = key;
+    this->name = name;
+}
+
+/**
+ * Constructor.
+ *
+ * @param key - Key of the label.
+ * @param name - name of label.
+ * @param red - red color component, zero to one.
+ * @param green - green color component, zero to one.
+ * @param blue - blue color component, zero to one.
+ * @param alpha - alpha color component, zero to one.
+ *
+ */
+Label::Label(
+                   const int32_t key,
+                   const AString& name,
+                   const float red,
+                   const float green,
+                   const float blue,
+                   const float alpha)
+{
+    this->initializeMembersLabel();
+    this->key = key;
+    this->name = name;
+    this->red = red;
+    this->green = green;
+    this->blue = blue;
+    this->alpha = alpha;
+}
+
+/**
+ * Constructor.
+ *
+ * @param key - Key of the label.
+ * @param name - name of label.
+ * @param red - red color component, zero to one.
+ * @param green - green color component, zero to one.
+ * @param blue - blue color component, zero to one.
+ * @param alpha - alpha color component, zero to one.
+ *
+ */
+Label::Label(
+                       const int32_t key,
+                       const AString& name,
+                       const double red,
+                       const double green,
+                       const double blue,
+                       const double alpha)
+{
+    this->initializeMembersLabel();
+    this->key = key;
+    this->name = name;
+    this->red = red;
+    this->green = green;
+    this->blue = blue;
+    this->alpha = alpha;
+}
+
+/**
+ * Constructor.
+ *
+ * @param key - Key of the label.
+ * @param name - name of label.
+ * @param rgba - red, green, blue, alpha color componenents, zero to one.
+ *
+ */
+Label::Label(
+                   const int32_t key,
+                   const AString& name,
+                   const float rgba[])
+{
+    this->initializeMembersLabel();
+    this->key = key;
+    this->name = name;
+    this->red = rgba[0];
+    this->green = rgba[1];
+    this->blue = rgba[2];
+    this->alpha = rgba[3];
+}
+
+/**
+ * Constructor.
+ *
+ * @param key - Key of the label.
+ * @param name - name of label.
+ * @param red - red color component, zero to two-fifty-five.
+ * @param green - green color component, zero to two-fifty-five.
+ * @param blue - blue color component, zero to two-fifty-five.
+ * @param alpha - alpha color component, zero to two-fifty-five.
+ *
+ */
+Label::Label(
+                   const int32_t key,
+                   const AString& name,
+                   const int32_t red,
+                   const int32_t green,
+                   const int32_t blue,
+                   const int32_t alpha)
+{
+    this->initializeMembersLabel();
+    this->key = key;
+    this->name = name;
+    this->red = red / 255.0;
+    this->green = green / 255.0;
+    this->blue = blue / 255.0;
+    this->alpha = alpha / 255.0;
+}
+
+/**
+ * Constructor.
+ *
+ * @param key - Key of the label.
+ * @param name - name of label.
+ * @param rgba - red, green, blue, alpha color componenents, zero to 255.
+ *
+ */
+Label::Label(
+                   const int32_t key,
+                   const AString& name,
+                   const int32_t rgba[])
+{
+    this->initializeMembersLabel();
+    this->key = key;
+    this->name = name;
+    this->red = rgba[0] / 255.0;
+    this->green = rgba[1] / 255.0;
+    this->blue = rgba[2] / 255.0;
+    this->alpha = rgba[3] / 255.0;
+}
+
+/**
+ * Constructor.
+ *
+ * @param key - Key of the label.
+ *
+ */
+Label::Label(
+                   const int32_t key)
+{
+    this->initializeMembersLabel();
+    this->key = key;
+    if (this->key == 0) {
+        this->name = "???";    
+    }
+    else {
+        std::stringstream str;
+        str << "???" << this->key;
+        this->name = str.str().c_str();
+    }
+}
+
+Label::~Label()
+{
+}
+
+Label::Label(const Label& o)
+{
+    this->initializeMembersLabel();
+    this->copyHelper(o);
+}
+
+Label&
+Label::operator=(const Label& o)
+{
+    if (this != &o) {
+        this->copyHelper(o);
+    };
+    return *this;
+}
+
+/**
+ * Helps with copy constructor and assignment operator.
+ */
+void
+Label::copyHelper(const Label& gl)
+{
+    this->initializeMembersLabel();
+    this->name = gl.name;
+    this->key = gl.key;
+    this->selected = gl.selected;
+    this->red = gl.red;
+    this->green = gl.green;    
+    this->blue = gl.blue;    
+    this->alpha = gl.alpha;
+}
+
+/**
+ * Initialize data members.
+ */
+void
+Label::initializeMembersLabel() 
+{
+    this->name = "";
+    this->key = s_invalidLabelKey;
+    this->selected = true;
+    this->red = 1.0;
+    this->green = 1.0;    
+    this->blue = 1.0;    
+    this->alpha = 1.0;
+}
+
+/**
+ * Determine if two labels are equal.  Two Labels are equal if they 
+ * have the same "key".
+ * @param gl label for comparison.
+ * @return true if equal, else false.
+ *
+ */
+bool
+Label::equals(const Label& gl)
+{
+    return (this->key == gl.key);
+}
+
+/**
+ * Compare this label to another label using the indices of the labels.
+ * @param gl - Compare to this Label.
+ * @return negative if "this" is less, positive if "this" is greater,
+ * else zero.
+ *
+ */
+int32_t
+Label::operator<(const Label& gl)
+{
+    return (this->key < gl.key);
+}
+
+/**
+ * Get the key of this label.
+ * @return key of the label.
+ *
+ */
+int32_t
+Label::getKey() const
+{
+    return this->key;
+}
+
+/**
+ * Set the key of this label.  DO NOT call this method on a label
+ * retrieved from the label table.
+ * 
+ * @param key - New key for this label.
+ *
+ */
+void
+Label::setKey(const int32_t key)
+{
+    this->key = key;
+}
+
+/**
+ * Get the name.
+ * @return Name of label.
+ *
+ */
+AString
+Label::getName() const
+{
+    return this->name;
+}
+
+/**
+ * Set the name.
+ * @param name - new name for label.
+ *
+ */
+void
+Label::setName(const AString& name)
+{
+    this->name = name;
+}
+
+/**
+ * Is this label selected (for display)?
+ *
+ * @return  true if label selected for display, else false.
+ *
+ */
+bool
+Label::isSelected() const
+{
+    return this->selected;
+}
+
+/**
+ * Set the label selected (for display).
+ *
+ * @param selected - new selection status.
+ *
+ */
+void
+Label::setSelected(const bool selected)
+{
+    this->selected = selected;
+}
+
+/**
+ * Get the color components.
+ *
+ * @return  A four-dimensional array of floats containing the red, green,
+ * blue, and alpha components with values ranging from 0.0 to 1.0.
+ * User MUST delete[] returned array.
+ *
+ */
+float*
+Label::getColor() const
+{
+    float* rgba = new float[4];
+    rgba[0] = this->red;
+    rgba[1] = this->green;
+    rgba[2] = this->blue;
+    rgba[3] = this->alpha;
+    return rgba;
+}
+
+/**
+ * Get the color components.
+ * @param rgbaOut four dimensional array into which are loaded,
+ * red, green, blue, and alpha components ranging 0.0. to 1.0.
+ *
+ */
+void
+Label::getColor(float rgbaOut[]) const
+{
+    rgbaOut[0] = this->red;
+    rgbaOut[1] = this->green;
+    rgbaOut[2] = this->blue;
+    rgbaOut[3] = this->alpha;
+}
+
+/**
+ * Set the color components.
+ *
+ * @param rgba - A four-dimensional array of floats containing the red,
+ * green, blue, and alpha components with values ranging from 0.0 to 1.0.
+ *
+ */
+void
+Label::setColor(const float rgba[])
+{
+    this->red = rgba[0];
+    this->green = rgba[1];
+    this->blue = rgba[2];
+    this->alpha = rgba[3];
+}
+
+/**
+ * Get the colors as integers ranging 0 to 255.
+ * @return  Four-dimensional array containing color components.
+ * User must delete[] the returned array.
+ *
+ */
+int32_t*
+Label::getColorInt() const
+{
+    int32_t* rgbaOut = new int32_t[4];
+    rgbaOut[0] = static_cast<int32_t>(this->red * 255);
+    rgbaOut[1] = static_cast<int32_t>(this->green * 255);
+    rgbaOut[2] = static_cast<int32_t>(this->blue * 255);
+    rgbaOut[3] = static_cast<int32_t>(this->alpha * 255);
+    return rgbaOut;
+}
+
+/**
+ * Set the colors with integers ranging 0 to 255.
+ * @param rgba - four-dimensional array with colors ranging 0 to 255.
+ *
+ */
+void
+Label::setColorInt(const int32_t rgba[])
+{
+    this->red = rgba[0] / 255.0;
+    this->green = rgba[1] / 255.0;
+    this->blue = rgba[2] / 255.0;
+    this->alpha = rgba[3] / 255.0;
+}
+
+/**
+ * Get the default color.
+ *
+ * @param rgbaOut ouput, a four-dimensional array of floats
+ * containing the red, green, blue, and alpha components with values
+ * ranging from 0.0 to 1.0.
+ */
+void
+Label::getDefaultColor(float rgbaOut[4])
+{
+    rgbaOut[0] = 1.0;
+    rgbaOut[1] = 1.0;
+    rgbaOut[2] = 1.0;
+    rgbaOut[3] = 1.0;
+}
+
+/**
+ * Get the red color component for this label.
+ * @return red color component.
+ *
+ */
+float
+Label::getRed() const
+{
+    return this->red;
+}
+
+/**
+ * Get the green color component for this label.
+ * @return green color component.
+ *
+ */
+float
+Label::getGreen() const
+{
+    return this->green;
+}
+
+/**
+ * Get the blue color component for this label.
+ * @return blue color component.
+ *
+ */
+float
+Label::getBlue() const
+{
+    return this->blue;
+}
+
+/**
+ * Get the alpha color component for this label.
+ * @return alpha color component.
+ *
+ */
+float
+Label::getAlpha() const
+{
+    return this->alpha;
+}
+
+bool
+Label::matches(const Label& rhs, const bool checkColor) const
+{
+    if (key != rhs.key) return false;
+    if (name != rhs.name) return false;
+    if (checkColor)
+    {
+        if (red != rhs.red) return false;
+        if (green != rhs.green) return false;
+        if (blue != rhs.blue) return false;
+        if (alpha != rhs.alpha) return false;
+    }
+    return true;
+}
diff --git a/src/Cifti/Label.h b/src/Cifti/Label.h
new file mode 100644
index 0000000..cd00a3e
--- /dev/null
+++ b/src/Cifti/Label.h
@@ -0,0 +1,161 @@
+#ifndef __LABEL_H__
+#define __LABEL_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Common/AString.h"
+
+#include <stdint.h>
+
+namespace cifti {
+    
+    class GroupAndNameHierarchyItem;
+    
+    class Label {
+        
+    public:
+        Label(
+                   const int32_t key,
+                   const AString& name);
+        
+        explicit Label(
+                            const int32_t key,
+                            const AString& name,
+                            const float red,
+                            const float green,
+                            const float blue,
+                            const float alpha);
+        
+        explicit Label(
+                            const int32_t key,
+                            const AString& name,
+                            const double red,
+                            const double green,
+                            const double blue,
+                            const double alpha);
+        
+        Label(
+                   const int32_t key,
+                   const AString& name,
+                   const float rgba[]);
+        
+        explicit Label(
+                            const int32_t key,
+                            const AString& name,
+                            const int32_t red,
+                            const int32_t green,
+                            const int32_t blue,
+                            const int32_t alpha);
+        
+        Label(
+                   const int32_t key,
+                   const AString& name,
+                   const int32_t rgba[]);
+        
+        Label(const int32_t key);
+        
+        Label(const Label& gl);
+        
+    public:
+        Label& operator=(const Label& gl);
+        
+        virtual ~Label();
+        
+    private:
+        void copyHelper(const Label& o);
+        
+        void initializeMembersLabel();
+        
+    public:
+        int32_t hashCode();
+        
+        bool equals(const Label&);
+        
+        int32_t operator<(const Label& gl);
+        
+        int32_t getKey() const;
+        
+        void setKey(const int32_t key);
+        
+        AString getName() const;
+        
+        void setName(const AString& name);
+        
+        bool isSelected() const;
+        
+        void setSelected(const bool selected);
+        
+        float* getColor() const;
+        
+        void getColor(float rgbaOut[]) const;
+        
+        void setColor(const float rgba[]);
+        
+        int32_t* getColorInt() const;
+        
+        void setColorInt(const int32_t rgba[]);
+        
+        static void getDefaultColor(float rgbaOut[4]);
+        
+        float getRed() const;
+        
+        float getGreen() const;
+        
+        float getBlue() const;
+        
+        float getAlpha() const;
+        
+        bool matches(const Label& rhs, const bool checkColor = false) const;
+        
+        /**
+         * @return The invalid label key.
+         */
+        static inline int32_t getInvalidLabelKey() { return s_invalidLabelKey; }
+        
+    private:
+        AString name;
+        
+        int32_t key;
+        
+        bool selected;
+        
+        float red;
+        
+        float green;
+        
+        float blue;
+        
+        float alpha;
+        
+        /** The invalid label key */
+        const static int32_t s_invalidLabelKey;
+    };
+    
+} // namespace
+
+#endif // __LABEL_H__
diff --git a/src/Cifti/LabelTable.cxx b/src/Cifti/LabelTable.cxx
new file mode 100644
index 0000000..3475277
--- /dev/null
+++ b/src/Cifti/LabelTable.cxx
@@ -0,0 +1,928 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <algorithm>
+#include <sstream>
+#include <iostream>
+
+#include "Common/CiftiAssert.h"
+#include "Label.h"
+#include "LabelTable.h"
+
+using namespace std;
+using namespace cifti;
+
+LabelTable::LabelTable()
+{
+    clear();//actually adds the 0: ??? label
+}
+
+LabelTable::~LabelTable()
+{
+    for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin();
+         iter != labelsMap.end();
+         iter++) {
+        delete iter->second;
+    }
+    this->labelsMap.clear();
+}
+
+LabelTable::LabelTable(const LabelTable& glt)
+{
+    this->copyHelper(glt);
+}
+
+LabelTable&
+LabelTable::operator=(const LabelTable& glt)
+{
+    if (this != &glt) {
+        this->copyHelper(glt);
+    };
+    return *this;
+}
+
+/**
+ * Helps with copy constructor and assignment operator.
+ */
+void
+LabelTable::copyHelper(const LabelTable& glt)
+{
+    this->clear();
+    
+    for (LABELS_MAP_CONST_ITERATOR iter = glt.labelsMap.begin();
+         iter != glt.labelsMap.end();
+         iter++) {
+        Label* myLabel = this->getLabel(iter->second->getKey());
+        if (myLabel != NULL)
+        {
+            *myLabel = *(iter->second);
+        } else {
+            addLabel(iter->second);
+        }
+    }
+}
+
+/**
+ * Clear the labelTable.
+ *
+ */
+void
+LabelTable::clear()
+{
+    for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin();
+         iter != labelsMap.end();
+         iter++) {
+        delete iter->second;
+    }
+    this->labelsMap.clear();
+    
+    Label gl(0, "???", 1.0, 1.0, 1.0, 0.0);
+    this->addLabel(&gl);
+}
+
+/**
+ * Append a label table to this label table.  Since labels may be
+ * duplicated, the map returned that converts the keys of
+ * the appended LabelTable to keys for "this" label table.
+ *
+ * @param glt  Label table that is to be appended.
+ *
+ * @return  A map where the keys are the keys in the label table
+ *    that is passed as a parameter and the values are the keys
+ *    into "this" label table.
+ *
+ */
+std::map<int32_t,int32_t>
+LabelTable::append(const LabelTable& glt)
+{
+    std::map<int32_t,int32_t> keyConverterMap;
+    
+    for (LABELS_MAP_CONST_ITERATOR iter = glt.labelsMap.begin();
+         iter != glt.labelsMap.end();
+         iter++) {
+        int32_t key = iter->first;
+        int32_t newKey = this->addLabel(iter->second);
+        
+        keyConverterMap.insert(std::make_pair(key, newKey));
+    }
+    return keyConverterMap;
+}
+
+/**
+ * Add a label.  If a label with the name exists, its colors
+ * are replaced with these color components.
+ * @param labelName Name of label.
+ * @param red  Red color component ranging 0.0 to 1.0.
+ * @param green Green color component ranging 0.0 to 1.0.
+ * @param blue Blue color component ranging 0.0 to 1.0.
+ * @param alpha Alpha color component ranging 0.0 to 1.0.
+ * @return  Index of the existing label, or, if no label 
+ * exists with name, index of new label.
+ *
+ */
+int32_t
+LabelTable::addLabel(
+                   const AString& labelName,
+                   const float red,
+                   const float green,
+                   const float blue,
+                   const float alpha)
+{
+    const Label gl(Label::getInvalidLabelKey(), labelName, red, green, blue, alpha);
+    return this->addLabel(&gl);
+}
+
+/**
+ * Add a label.  If a label with the name exists, its colors
+ * are replaced with these color components.
+ * @param labelName Name of label.
+ * @param red  Red color component ranging 0.0 to 1.0.
+ * @param green Green color component ranging 0.0 to 1.0.
+ * @param blue Blue color component ranging 0.0 to 1.0.
+ * @return  Index of the existing label, or, if no label 
+ * exists with name, index of new label.
+ *
+ */
+int32_t
+LabelTable::addLabel(
+                   const AString& labelName,
+                   const float red,
+                   const float green,
+                   const float blue)
+{
+    return this->addLabel(labelName, red, green, blue, 1.0f);
+}
+
+/**
+ * Add a label.  If a label with the name exists, its colors
+ * are replaced with these color components.
+ * @param labelName Name of label.
+ * @param red  Red color component ranging 0 to 255.
+ * @param green Green color component ranging 0 to 255.
+ * @param blue Blue color component ranging 0 to 255.
+ * @param alpha Alpha color component ranging 0 to 255.
+ * @return  Index of the existing label, or, if no label 
+ * exists with name, index of new label.
+ *
+ */
+int32_t
+LabelTable::addLabel(
+                   const AString& labelName,
+                   const int32_t red,
+                   const int32_t green,
+                   const int32_t blue,
+                   const int32_t alpha)
+{
+    const Label gl(Label::getInvalidLabelKey(), labelName, red, green, blue, alpha);
+    return this->addLabel(&gl);
+}
+
+/**
+ * Add a label.  If a label with the name exists, its colors
+ * are replaced with these color components.
+ * @param labelName Name of label.
+ * @param red  Red color component ranging 0 to 255.
+ * @param green Green color component ranging 0 to 255.
+ * @param blue Blue color component ranging 0 to 255.
+ * @return  Index of the existing label, or, if no label 
+ * exists with name, index of new label.
+ *
+ */
+int32_t
+LabelTable::addLabel(
+                   const AString& labelName,
+                   const int32_t red,
+                   const int32_t green,
+                   const int32_t blue)
+{
+    return this->addLabel(labelName, red, green, blue, 255);
+}
+
+/**
+ * Add a label to the label table.  If the label's key is already in
+ * the label table, a new key is created.  If a label of the same
+ * name already exists, the key of the existing label is returned
+ * and its color is overridden.
+ * @param glIn - Label to add.
+ * @return  Key of the label, possibly different than its original key.
+ *
+ */
+int32_t
+LabelTable::addLabel(const Label* glIn)
+{
+    /*
+     * First see if a label with the same name already exists
+     */
+    int32_t key = this->getLabelKeyFromName(glIn->getName());
+    
+    /*
+     * If no label with the name exists, get the key
+     * (which may be invalid) from the input label,
+     * and check that nothing uses that key
+     */
+    if (key == Label::getInvalidLabelKey()) {
+        int32_t tempkey = glIn->getKey();
+        LABELS_MAP_ITERATOR iter = this->labelsMap.find(tempkey);
+        if (iter == labelsMap.end())
+        {
+            key = tempkey;
+        }
+    }
+    
+    /*
+     * Still need a key, find an unused key
+     */
+    if (key == Label::getInvalidLabelKey()) {
+        key = this->generateUnusedKey();
+        
+        Label* gl = new Label(*glIn);
+        gl->setKey(key);
+        this->labelsMap.insert(std::make_pair(key, gl));
+        return key;
+    }
+    
+    LABELS_MAP_ITERATOR iter = this->labelsMap.find(key);
+    if (iter != this->labelsMap.end()) {
+        /*
+         * Update existing label
+         */
+        Label* gl = iter->second;
+        gl->setName(glIn->getName());
+        float rgba[4];
+        glIn->getColor(rgba);
+        gl->setColor(rgba);
+        key = iter->first;
+    }
+    else {
+        /*
+         * Insert a new label
+         */
+        this->labelsMap.insert(std::make_pair(key, new Label(*glIn)));
+    }
+    return key;
+}
+
+/**
+ * Generate an unused key.
+ * @return An unused key.
+ */ 
+int32_t 
+LabelTable::generateUnusedKey() const
+{
+    const int32_t numKeys = labelsMap.size();
+    LABELS_MAP::const_reverse_iterator rbegin = labelsMap.rbegin();//reverse begin is largest key
+    if (numKeys > 0 && rbegin->first > 0)//there is at least one positive key
+    {
+        if (rbegin->first < numKeys)
+        {
+            CiftiAssert(labelsMap.find(rbegin->first + 1) == labelsMap.end());
+            return rbegin->first + 1;//keys are compact unless negatives exist, in which case consider it "compact enough" if positive holes equal number of negative keys
+        } else {
+            LABELS_MAP::const_iterator begin = labelsMap.begin();
+            if (begin->first == 1 && rbegin->first == numKeys)
+            {
+                CiftiAssert(labelsMap.find(rbegin->first + 1) == labelsMap.end());
+                return rbegin->first + 1;//keys are compact but missing 0, do not return 0, so return next
+            } else {//there aren't enough negatives to make up for the missing, search for a hole in the positives
+                LABELS_MAP::const_iterator iter = labelsMap.upper_bound(0);//start with first positive
+                int32_t curVal = 0;//if it isn't one, we can stop early
+                while (iter != labelsMap.end() && iter->first == curVal + 1)//it should NEVER hit end(), due to above checks, but if it did, it would return rbegin->first + 1
+                {
+                    curVal = iter->first;
+                    ++iter;
+                }
+                CiftiAssert(labelsMap.find(curVal + 1) == labelsMap.end());
+                return curVal + 1;
+            }
+        }
+    } else {
+        CiftiAssert(labelsMap.find(1) == labelsMap.end());
+        return 1;//otherwise, no keys exist or all keys are non-positive, return 1
+    }
+}
+
+/**
+ * Remove the label with the specified key.
+ * @param key - key of label.
+ *
+ */
+void
+LabelTable::deleteLabel(const int32_t key)
+{
+   if (key == 0)
+   {//key 0 is reserved (sort of)
+      cerr << "Label 0 DELETED!" << endl;
+   }
+    LABELS_MAP_ITERATOR iter = this->labelsMap.find(key);
+    if (iter != this->labelsMap.end()) {
+        this->labelsMap.erase(iter);
+        delete iter->second;
+    }
+}
+
+/**
+ * Remove a label from the label table.
+ * This method WILL DELETE the label passed
+ * in so the caller should never use the parameter
+ * passed after this call.
+ * @param label - label to remove.
+ *
+ */
+void
+LabelTable::deleteLabel(const Label* label)
+{
+   if (label->getKey() == 0)
+   {//key 0 is reserved (sort of)
+      cerr << "Label 0 DELETED!" << endl;
+   }
+    for (LABELS_MAP_ITERATOR iter = this->labelsMap.begin();
+         iter != this->labelsMap.end();
+         iter++) {
+        if (iter->second == label) {
+            this->labelsMap.erase(iter);
+            break;
+        }
+    }
+    delete label;
+}
+
+/**
+ * Remove unused labels from the label table.  Note that the unassigned
+ * label is not removed, even if it is unused.
+ *
+ * @param usedLabelKeys - Color keys that are in use.
+ *
+ */
+void
+LabelTable::deleteUnusedLabels(const std::set<int32_t>& usedLabelKeys)
+{
+    LABELS_MAP newMap;
+    int32_t unassignedKey = getUnassignedLabelKey();
+    for (LABELS_MAP_ITERATOR iter = this->labelsMap.begin();
+         iter != this->labelsMap.end();
+         iter++) {
+        int32_t key = iter->first;
+        Label* gl = iter->second;
+        if (key == unassignedKey || usedLabelKeys.find(key) != usedLabelKeys.end()) {//unassigned key gets a free pass
+            newMap.insert(std::make_pair(key, gl));
+        }
+        else {
+            delete gl;
+        }
+    }
+    
+    this->labelsMap = newMap;
+}
+
+/**
+ * Insert the label using the labels key.
+ * @param labelIn - Label to insert (replaces an existing label
+ *    with the same key).
+ *
+ */
+void
+LabelTable::insertLabel(const Label* labelIn)
+{
+    Label* label = new Label(*labelIn);
+    int32_t key = label->getKey();
+    if (key == Label::getInvalidLabelKey()) {
+        key = this->generateUnusedKey();
+        label->setKey(key);
+    }
+    /*
+     * Note: A map DOES NOT replace an existing key, so it
+     * must be deleted and then added.
+     */
+    LABELS_MAP_ITERATOR keyPos = this->labelsMap.find(label->getKey());
+    if (keyPos != this->labelsMap.end()) {
+        Label* gl = keyPos->second;
+        this->labelsMap.erase(keyPos);
+        delete gl;
+    }
+        
+    this->labelsMap.insert(std::make_pair(label->getKey(), label));
+}
+
+/**
+ * Get the key of a lable from its name.
+ * @param name   Name to search for.
+ * @return       Key of Name or Label::getInvalidLabelKey() if not found.
+ *
+ */
+int32_t
+LabelTable::getLabelKeyFromName(const AString& name) const
+{
+    LABELS_MAP newMap;
+    for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin();
+         iter != this->labelsMap.end();
+         iter++) {
+        int32_t key = iter->first;
+        Label* gl = iter->second;
+        if (gl->getName() == name) {
+            return key;
+        }
+    }
+    return Label::getInvalidLabelKey();
+}
+
+/**
+ * Get a Label from its name.
+ * @param labelName - Name of label that is sought.
+ * @return  Reference to label with name or null if no matching label.
+ *
+ */
+const Label*
+LabelTable::getLabel(const AString& labelName) const
+{
+    LABELS_MAP newMap;
+    for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin();
+         iter != this->labelsMap.end();
+         iter++) {
+        Label* gl = iter->second;
+        if (gl->getName() == labelName) {
+            return gl;
+        }
+    }
+    return NULL;
+}
+
+/**
+ * Get a Label from its name.
+ * @param labelName - Name of label that is sought.
+ * @return  Reference to label with name or null if no matching label.
+ *
+ */
+Label*
+LabelTable::getLabel(const AString& labelName)
+{
+    LABELS_MAP newMap;
+    for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin();
+         iter != this->labelsMap.end();
+         iter++) {
+        Label* gl = iter->second;
+        if (gl->getName() == labelName) {
+            return gl;
+        }
+    }
+    return NULL;
+}
+
+/**
+ * Get the Label at the specified key.
+ *
+ * @param  key - Key of Label entry.
+ * @return       The Label at the specified key or null if the
+ *    there is not a label at the specified key.
+ *
+ */
+const Label*
+LabelTable::getLabel(const int32_t key) const
+{
+    LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.find(key);
+    if (iter != this->labelsMap.end()) {
+        return iter->second;
+    }
+    return NULL;
+}
+
+/**
+ * Get the Label at the specified key.
+ *
+ * @param  key - Key of Label entry.
+ * @return       The Label at the specified key or null if the
+ *    there is not a label at the specified key.
+ */
+Label*
+LabelTable::getLabel(const int32_t key)
+{
+    LABELS_MAP_ITERATOR iter = this->labelsMap.find(key);
+    if (iter != this->labelsMap.end()) {
+        return iter->second;
+    }
+    return NULL;
+}
+
+/**
+ * Get the key for the unassigned label.
+ * @return  Index of key for unassigned label.
+ *          A valid key will always be returned.
+ *
+ */
+int32_t
+LabelTable::getUnassignedLabelKey() const
+{
+    const Label* gl = this->getLabel("???");
+    if (gl != NULL) {
+        return gl->getKey();
+    }
+
+    /*
+     * Remove 'constness' from this object so that the 
+     * label can be added.
+     */
+    LabelTable* glt = (LabelTable*)this;
+    const int32_t key = glt->addLabel("???", 0.0f, 0.0f, 0.0f, 0.0f);
+    return key;
+}
+
+/**
+ * Get the number of labels.  This value is one greater than the last
+ * label key.  Note that not every key may have a label.  If there
+ * are no labels this returns 0.
+ * @return  Number of labels.
+ *
+ */
+int32_t
+LabelTable::getNumberOfLabels() const
+{
+    return this->labelsMap.size();
+}
+
+/**
+ * Get the name of the label at the key.  If there is no label at the
+ * key an empty string is returned.
+ * @param key - key of label.
+ * @return  Name of label at inkeydex.
+ *
+ */
+AString
+LabelTable::getLabelName(const int32_t key) const
+{
+    LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.find(key);
+    if (iter != this->labelsMap.end()) {
+        const AString name = iter->second->getName();
+        return name;
+    }
+    return "";
+}
+
+/**
+ * Set the name of a label.
+ * @param key - key of label.
+ * @param name - new name of label.
+ *
+ */
+void
+LabelTable::setLabelName(
+                   const int32_t key,
+                   const AString& name)
+{
+    LABELS_MAP_ITERATOR iter = this->labelsMap.find(key);
+    if (iter != this->labelsMap.end()) {
+        iter->second->setName(name);
+    }
+}
+
+/**
+ * Set a label.  If a label with the specified key exists,
+ * it is replaced.
+ * 
+ * @param key    Key for label.
+ * @param name   Name of label.
+ * @param red    Red color component.
+ * @param green  Green color component.
+ * @param blue   Blue color component.
+ * @param alpha  Alpha color component.
+ *
+ */
+void
+LabelTable::setLabel(
+                   const int32_t key,
+                   const AString& name,
+                   const float red,
+                   const float green,
+                   const float blue,
+                   const float alpha)
+{
+    LABELS_MAP_ITERATOR iter = this->labelsMap.find(key);
+    if (iter != this->labelsMap.end()) {
+        Label* gl = iter->second;
+        gl->setName(name);
+        float rgba[4] = { red, green, blue, alpha };
+        gl->setColor(rgba);
+    }
+    else {
+        Label gl(key, name, red, green, blue, alpha);
+        this->addLabel(&gl);
+    }
+}
+
+/**
+ * Get the selection status of the label at the specified key.  If there
+ * is no label at the key, false is returned.
+ * @param key - key of label
+ * @return  selection status of label.
+ *
+ */
+bool
+LabelTable::isLabelSelected(const int32_t key) const
+{
+    LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.find(key);
+    if (iter != this->labelsMap.end()) {
+        return iter->second->isSelected();
+    }
+    return false;
+}
+
+/**
+ * Set the selection status of a label.
+ * @param key - key of label.
+ * @param sel - new selection status.
+ *
+ */
+void
+LabelTable::setLabelSelected(
+                   const int32_t key,
+                   const bool sel)
+{
+    LABELS_MAP_ITERATOR iter = this->labelsMap.find(key);
+    if (iter != this->labelsMap.end()) {
+        iter->second->setSelected(sel);
+    }
+}
+
+/**
+ * Set the selection status for all labels.
+ * @param newStatus  New selection status.
+ *
+ */
+void
+LabelTable::setSelectionStatusForAllLabels(const bool newStatus)
+{
+    for (LABELS_MAP_ITERATOR iter = this->labelsMap.begin();
+         iter != this->labelsMap.end();
+         iter++) {
+        Label* gl = iter->second;
+        gl->setSelected(newStatus);
+    }
+}
+
+/**
+ * Get the alpha color component for a label.  If the key is not a
+ * valid label, an alpha of zero is returned.
+ * @param key - Key of label.
+ * @return  Alpha for label or zero if invalid key.
+ *
+ */
+float
+LabelTable::getLabelAlpha(const int32_t key) const
+{
+    const Label* gl = this->getLabel(key);
+    if (gl != NULL) {
+        return gl->getAlpha();
+    }
+    return 0.0;
+}
+
+/**
+ * Get the color for a label.
+ * @param key - key of label.
+ * @param rgbaOut - output, its color components
+ *
+ */
+void
+LabelTable::getLabelColor(const int32_t key, float rgbaOut[4]) const
+{
+    const Label* gl = this->getLabel(key);
+    if (gl != NULL) {
+        gl->getColor(rgbaOut);
+    }
+}
+
+/**
+ * Set the color of a label.
+ * @param key - key of label.
+ * @param color - new color of label.
+ *
+ */
+void
+LabelTable::setLabelColor(
+                   const int32_t key,
+                   const float color[])
+{
+    LABELS_MAP_ITERATOR iter = this->labelsMap.find(key);
+    if (iter != this->labelsMap.end()) {
+        Label* gl = iter->second;
+        gl->setColor(color);
+    }
+}
+
+void
+LabelTable::writeXML(XmlWriter& xmlWriter) const
+{
+    //
+    // Write the label tag
+    //
+    xmlWriter.writeStartElement("LabelTable");
+    
+    //
+    // Write the labels
+    //
+    std::set<int32_t> keys = this->getKeys();
+    for (std::set<int32_t>::const_iterator iter = keys.begin();
+            iter != keys.end();
+            iter++) {
+        int key = *iter;
+        const Label* label = this->getLabel(key);
+        if (label != NULL) {
+            xmlWriter.writeStartElement("Label");
+            xmlWriter.writeAttribute("Key", AString_number(key));
+            float* rgba = label->getColor();
+            xmlWriter.writeAttribute("Red", AString_number(rgba[0]));
+            xmlWriter.writeAttribute("Green", AString_number(rgba[1]));
+            xmlWriter.writeAttribute("Blue", AString_number(rgba[2]));
+            xmlWriter.writeAttribute("Alpha", AString_number(rgba[3]));
+            xmlWriter.writeCharacters(label->getName());
+            xmlWriter.writeEndElement();
+            delete[] rgba;
+        }
+    }
+    
+    //
+    // Write the closing label tag
+    //
+    xmlWriter.writeEndElement();
+}
+
+void LabelTable::readXml(XmlReader& xml)
+{
+    clear();
+#ifdef CIFTILIB_USE_QT
+    if (!xml.isStartElement() || xml.name() != "LabelTable")
+    {
+        throw CiftiException("tried to read LabelTable when current element is not LabelTable");
+    }
+    while (xml.readNextStartElement() && !xml.atEnd())
+    {
+        if (xml.name() != "Label")
+        {
+            throw CiftiException("unexpected element '" + xml.name().toString() + "' encountered in Label");
+        }
+        int key;
+        float rgba[4];
+        QXmlStreamAttributes myAttrs = xml.attributes();
+        bool ok = false;
+        AString temp = myAttrs.value("Key").toString();
+        key = temp.toInt(&ok);
+        if (!ok) throw CiftiException("Key attribute of Label missing or noninteger");
+        temp = myAttrs.value("Red").toString();
+        rgba[0] = temp.toFloat(&ok);
+        if (!ok) throw CiftiException("Red attribute of Label missing or not a number");
+        temp = myAttrs.value("Green").toString();
+        rgba[1] = temp.toFloat(&ok);
+        if (!ok) throw CiftiException("Green attribute of Label missing or not a number");
+        temp = myAttrs.value("Blue").toString();
+        rgba[2] = temp.toFloat(&ok);
+        if (!ok) throw CiftiException("Blue attribute of Label missing or not a number");
+        temp = myAttrs.value("Alpha").toString();
+        if (temp == "")
+        {
+            rgba[3] = 1.0f;
+        } else {
+            rgba[3] = temp.toFloat(&ok);
+            if (!ok) throw CiftiException("Alpha attribute of Label is not a number");
+        }
+        temp = xml.readElementText();
+        if (xml.hasError()) return;
+        setLabel(key, temp, rgba[0], rgba[1], rgba[2], rgba[3]);
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    vector<AString> mandAttrs(4), optAttrs(1, "Alpha");
+    mandAttrs[0] = "Key";
+    mandAttrs[1] = "Red";
+    mandAttrs[2] = "Green";
+    mandAttrs[3] = "Blue";
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "Label")
+                {
+                    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs, optAttrs);
+                    int key;
+                    float rgba[4];
+                    bool ok = false;
+                    key = AString_toInt(myAttrs.mandatoryVals[0], ok);
+                    if (!ok) throw CiftiException("Key attribute of Label is not an integer");
+                    rgba[0] = AString_toFloat(myAttrs.mandatoryVals[1], ok);
+                    if (!ok) throw CiftiException("Red attribute of Label is not a number");
+                    rgba[1] = AString_toFloat(myAttrs.mandatoryVals[2], ok);
+                    if (!ok) throw CiftiException("Green attribute of Label is not a number");
+                    rgba[2] = AString_toFloat(myAttrs.mandatoryVals[3], ok);
+                    if (!ok) throw CiftiException("Blue attribute of Label is not a number");
+                    if (myAttrs.optionalVals[0].present)
+                    {
+                        rgba[3] = AString_toFloat(myAttrs.optionalVals[0].value, ok);
+                        if (!ok) throw CiftiException("Alpha attribute of Label is not a number");
+                    } else {
+                        rgba[3] = 1.0f;
+                    }
+                    AString name = XmlReader_readElementText(xml);
+                    setLabel(key, name, rgba[0], rgba[1], rgba[2], rgba[3]);
+                } else {
+                    throw CiftiException("unexpected element in LabelTable: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "LabelTable"));
+}
+
+/**
+ * Get the valid keys of the labels in ascending order.
+ * @return  A Set containing the valid keys of the label in
+ *    ascending order.
+ *
+ */
+std::set<int32_t>
+LabelTable::getKeys() const
+{
+    std::set<int32_t> keys;
+    for (std::map<int32_t,Label*>::const_iterator iter = this->labelsMap.begin();
+         iter != this->labelsMap.end();
+         iter++) {
+        keys.insert(iter->first);
+    }
+    return keys;
+}
+
+void LabelTable::getKeys(std::vector<int32_t>& keysOut) const
+{
+    keysOut.reserve(labelsMap.size());
+    for (std::map<int32_t,Label*>::const_iterator iter = this->labelsMap.begin();
+         iter != this->labelsMap.end();
+         iter++) {
+        keysOut.push_back(iter->first);
+    }
+}
+
+/**
+ * Get all keys and names.
+ * 
+ * @param keysAndNamesOut
+ *     Map containing the pairs of corresponding keys and names.
+ */
+void
+LabelTable::getKeysAndNames(std::map<int32_t, AString>& keysAndNamesOut) const
+{
+    keysAndNamesOut.clear();
+    
+    for (std::map<int32_t,Label*>::const_iterator iter = this->labelsMap.begin();
+         iter != this->labelsMap.end();
+         iter++) {
+        const Label* gl = iter->second;
+        keysAndNamesOut.insert(std::make_pair(iter->first,
+                                              gl->getName()));
+    }
+}
+
+bool LabelTable::matches(const LabelTable& rhs, const bool checkColors) const
+{
+    if (labelsMap.size() != rhs.labelsMap.size()) return false;
+    for (LABELS_MAP::const_iterator iter = labelsMap.begin(); iter != labelsMap.end(); ++iter)
+    {
+        LABELS_MAP::const_iterator riter = rhs.labelsMap.find(iter->first);
+        if (riter == rhs.labelsMap.end()) return false;
+        if (!iter->second->matches(*(riter->second), checkColors)) return false;
+    }
+    return true;
+}
diff --git a/src/Cifti/LabelTable.h b/src/Cifti/LabelTable.h
new file mode 100644
index 0000000..3b0a964
--- /dev/null
+++ b/src/Cifti/LabelTable.h
@@ -0,0 +1,173 @@
+#ifndef __LABELTABLE_H__
+#define __LABELTABLE_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Common/AString.h"
+
+#include <map>
+#include <set>
+#include <vector>
+#include <stdint.h>
+
+#include "Common/XmlAdapter.h"
+
+namespace cifti {
+
+class Label;
+    
+class LabelTable {
+
+public:
+    LabelTable();
+
+    LabelTable(const LabelTable& glt);
+
+    LabelTable& operator=(const LabelTable& glt);
+    
+    bool matches(const LabelTable& rhs, const bool checkColors = false) const;
+    
+    bool operator==(const LabelTable& rhs) const { return matches(rhs, true); }
+    
+    bool operator!=(const LabelTable& rhs) const { return !((*this) == rhs); }
+    
+    virtual ~LabelTable();
+
+private:
+    void copyHelper(const LabelTable& glt);
+
+public:
+    void clear();
+
+    std::map<int32_t,int32_t> append(const LabelTable& glt);
+
+    int32_t addLabel(
+                    const AString& labelName,
+                    const float red,
+                    const float green,
+                    const float blue,
+                    const float alpha);
+
+    int32_t addLabel(
+                    const AString& labelName,
+                    const float red,
+                    const float green,
+                    const float blue);
+
+    int32_t addLabel(
+                    const AString& labelName,
+                    const int32_t red,
+                    const int32_t green,
+                    const int32_t blue,
+                    const int32_t alpha);
+
+    int32_t addLabel(
+                    const AString& labelName,
+                    const int32_t red,
+                    const int32_t green,
+                    const int32_t blue);
+
+    int32_t addLabel(const Label* glt);
+
+    void deleteLabel(const int32_t key);
+
+    void deleteLabel(const Label* label);
+
+    void deleteUnusedLabels(const std::set<int32_t>& usedLabelKeys);
+
+    void insertLabel(const Label* label);
+
+    int32_t getLabelKeyFromName(const AString& name) const;
+
+    const Label* getLabel(const AString& labelName) const;
+
+    Label* getLabel(const AString& labelName);
+    
+    const Label* getLabel(const int32_t key) const;
+
+    Label* getLabel(const int32_t key);
+    
+    int32_t getUnassignedLabelKey() const;
+
+    int32_t getNumberOfLabels() const;
+
+    AString getLabelName(const int32_t key) const;
+
+    void setLabelName(
+                    const int32_t key,
+                    const AString& name);
+
+    void setLabel(const int32_t key,
+                    const AString& name,
+                    const float red,
+                    const float green,
+                    const float blue,
+                    const float alpha);
+    
+    bool isLabelSelected(const int32_t key) const;
+
+    void setLabelSelected(
+                    const int32_t key,
+                    const bool sel);
+
+    void setSelectionStatusForAllLabels(const bool newStatus);
+
+    float getLabelAlpha(const int32_t key) const;
+
+    void getLabelColor(const int32_t key, float rgbaOut[4]) const;
+
+    void setLabelColor(
+                    const int32_t key,
+                    const float color[4]);
+
+    void createLabelsForKeys(const std::set<int32_t>& newKeys);
+
+    void writeXML(XmlWriter& xmlWriter) const;
+
+    void readXml(XmlReader& xml);
+
+    std::set<int32_t> getKeys() const;
+
+    void getKeys(std::vector<int32_t>& keysOut) const;
+
+    void getKeysAndNames(std::map<int32_t, AString>& keysAndNamesOut) const;
+    
+    int32_t generateUnusedKey() const;
+    
+private:
+    typedef std::map<int32_t, Label*> LABELS_MAP;
+    typedef std::map<int32_t, Label*>::iterator LABELS_MAP_ITERATOR;
+    typedef std::map<int32_t, Label*>::const_iterator LABELS_MAP_CONST_ITERATOR;
+
+    LABELS_MAP labelsMap;
+
+};
+
+} // namespace
+
+#endif // __LABELTABLE_H__
diff --git a/src/Cifti/MetaData.cxx b/src/Cifti/MetaData.cxx
new file mode 100644
index 0000000..e16328d
--- /dev/null
+++ b/src/Cifti/MetaData.cxx
@@ -0,0 +1,478 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <algorithm>
+
+#include "Common/CiftiAssert.h"
+#include "Common/CiftiException.h"
+#include "MetaData.h"
+
+using namespace cifti;
+
+MetaData::MetaData()
+{
+    this->initializeMembersMetaData();
+}
+
+MetaData::~MetaData()
+{
+}
+
+MetaData::MetaData(const MetaData& o)
+{
+    this->initializeMembersMetaData();
+    this->copyHelper(o);
+}
+
+MetaData&
+MetaData::operator=(const MetaData& o)
+{
+    if (this != &o) {
+        this->copyHelper(o);
+    };
+    return *this;
+}
+
+bool MetaData::operator==(const MetaData& rhs) const
+{
+    return (metadata == rhs.metadata);
+}
+
+/**
+ * Helps with copy constructor and assignment operator.
+ */
+void
+MetaData::copyHelper(const MetaData& o)
+{
+    this->metadata = o.metadata;
+}
+
+void
+MetaData::initializeMembersMetaData()
+{
+}
+
+/**
+ * Clear the metadata.
+ *
+ */
+void
+MetaData::clear()
+{
+    metadata.clear();
+}
+
+/**
+ * Append the metadata to this metadata.  A comment is always appended.
+ * Other metadata are added only if the name is not in "this" metadata.
+ *
+ * @param smd   Metadata that is to be appended to "this".
+ *
+ */
+void
+MetaData::append(const MetaData& smd)
+{
+    for (MetaDataConstIterator iter = smd.metadata.begin();
+         iter != smd.metadata.end();
+         iter++) {
+            this->set(iter->first, iter->second);
+    }
+}
+
+/**
+ * Clears this metadata and then copies all metadata from "smd"
+ *
+ * @param smd   Metadata that is to be copied to "this".
+ *
+ */
+void
+MetaData::replace(const MetaData& smd)
+{
+    this->metadata = smd.metadata;
+}
+
+/**
+ * Sets metadata.  If a metadata entry named "name" already
+ * exists, it is replaced.  
+ *
+ * @param name   Name of metadata entry.
+ * @param value  Value for metadata entry.
+ *
+ */
+void
+MetaData::set(const AString& name,
+                   const AString& value)
+{
+    MetaDataIterator namePos = this->metadata.find(name);
+    if (namePos != this->metadata.end()) {
+        if (namePos->second != value) {
+            namePos->second = value;
+        }
+    }
+    else {
+        this->metadata.insert(std::make_pair(name, value));
+    }
+}
+
+/**
+ * Set metadata with an integer value.
+ * @param name - name of metadata.
+ * @param value - value of metadata.
+ *
+ */
+void
+MetaData::setInt(
+                   const AString& name,
+                   const int32_t value)
+{
+    AString s = AString_number(value);
+    this->set(name, s);
+}
+
+/**
+ * Set metadata with an float value.
+ * @param name - name of metadata.
+ * @param value - value of metadata.
+ *
+ */
+void
+MetaData::setFloat(
+                   const AString& name,
+                   const float value)
+{
+    AString s = AString_number(value);
+    this->set(name, s);
+}
+
+/**
+ * Replace ALL of the metadata with the data in the given map.
+ *
+ * @param map
+ *     New metadata that replaces all existing metadata.
+ */
+void
+MetaData::replaceWithMap(const std::map<AString, AString>& map)
+{
+    this->metadata = map;
+}
+
+/**
+ * @return ALL of the metadata in map.
+ */
+std::map<AString, AString>
+MetaData::getAsMap() const
+{
+    return this->metadata;
+}
+
+
+/**
+ * Remove a metadata entry.
+ *
+ * @param name   Name of metadata entry that is to be removed.
+ *
+ */
+void
+MetaData::remove(const AString& name)
+{
+    this->metadata.erase(name);
+}
+
+/**
+ * See if a metadata entry "name" exists.
+ *
+ * @param  name  Name of metadata entry.
+ * @return   Returns true if the metadata entry "name" exists, else false.
+ *
+ */
+bool
+MetaData::exists(const AString& name) const
+{
+    if (this->metadata.find(name) != this->metadata.end()) {
+        return true;
+    }
+    return false;
+}
+
+/**
+ * Get a value for metadata entry.  
+ *
+ * @param  name  Name of metadata entry.
+ * @return       The value of the metadata entry "name".  If the
+ *               metadata entry "name" does not exist an empty
+ *               string is returned.
+ *
+ */
+AString
+MetaData::get(const AString& name) const
+{
+    MetaDataConstIterator iter = this->metadata.find(name);
+    if (iter != this->metadata.end()) {
+        return iter->second;
+    }
+    return "";
+}
+
+/**
+ * Get the metadata as an integer value.  If the metadata does not exist
+ * or its string representation is not a number, zero is returned.
+ * @param name - name of metadata.
+ * @param ok - is set to false if key not found, or value not integer
+ * @return  Integer value associated with the metadata.
+ *
+ */
+int32_t
+MetaData::getInt(const AString& name, bool& ok) const
+{
+    ok = false;
+    AString s = this->get(name);
+    if (s.length() > 0) {
+        int32_t i = AString_toInt(s, ok);
+        return i;
+    }
+    return 0;
+}
+
+/**
+ * Get the metadata as an float value.  If the metadata does not exist
+ * or its string representation is not a number, zero is returned.
+ * @param name - name of metadata.
+ * @param ok - is set to false if key not found, or value not numeric
+ * @return  Float value associated with the metadata.
+ *
+ */
+float
+MetaData::getFloat(const AString& name, bool& ok) const
+{
+    ok = false;
+    AString s = this->get(name);
+    if (s.length() > 0) {
+        float f = AString_toFloat(s, ok);
+        return f;
+    }
+    return 0.0f;
+}
+
+/**
+ * Get names of all metadata.
+ *
+ * @return List of all metadata names.
+ *
+ */
+std::vector<AString>
+MetaData::getAllMetaDataNames() const
+{
+    std::vector<AString> names;
+    
+    for (MetaDataConstIterator iter = this->metadata.begin();
+         iter != this->metadata.end();
+         iter++) {
+        names.push_back(iter->first);   
+    }
+    return names;
+}
+
+/**
+ * Replace a metadata name.
+ * @param oldName - old name of metadata.
+ * @param newName - new name of metadata.
+ *
+ */
+void
+MetaData::replaceName(
+                   const AString& oldName,
+                   const AString& newName)
+{
+    MetaDataIterator iter = this->metadata.find(oldName);
+    if (iter != this->metadata.end()) {
+        AString value = iter->second;
+        this->remove(oldName);
+        this->set(newName, value);
+    }
+}
+
+void MetaData::writeCiftiXML1(XmlWriter& xmlWriter) const
+{
+    if (metadata.empty()) return;//don't write an empty tag if we have no metadata
+    xmlWriter.writeStartElement("MetaData");
+    for (MetaDataConstIterator iter = metadata.begin(); iter != metadata.end(); ++iter)
+    {
+        xmlWriter.writeStartElement("MD");
+        xmlWriter.writeTextElement("Name", iter->first);
+        xmlWriter.writeTextElement("Value", iter->second);
+        xmlWriter.writeEndElement();
+    }
+    xmlWriter.writeEndElement();
+}
+
+void MetaData::writeCiftiXML2(XmlWriter& xmlWriter) const
+{
+    writeCiftiXML1(xmlWriter);
+}
+
+void MetaData::readCiftiXML1(XmlReader& xml)
+{
+    clear();
+#ifdef CIFTILIB_USE_QT
+    while (!xml.atEnd())//don't check the current element's name
+    {
+        xml.readNext();
+        if (xml.isStartElement())
+        {
+            QStringRef name = xml.name();
+            if (name == "MD")
+            {
+                readEntry(xml);
+            } else {
+                throw CiftiException("unexpected tag name in MetaData: " + name.toString());
+            }
+        } else if (xml.isEndElement()) {
+            break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: because libxml++ will NOT give a separate close element for <MetaData/>!!!
+    while (!done && xml.read())//false means no node was available to read, it will throw on malformed xml
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "MD")
+                {
+                    readEntry(xml);
+                } else {
+                    throw CiftiException("unexpected tag name in MetaData: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    CiftiAssert(XmlReader_checkEndElement(xml, "MetaData"));
+}
+
+void MetaData::readCiftiXML2(XmlReader& xml)
+{
+    readCiftiXML1(xml);
+}
+
+void MetaData::readEntry(XmlReader& xml)
+{
+    AString key, value;
+    bool haveKey = false, haveValue = false;
+#ifdef CIFTILIB_USE_QT
+    while (!xml.atEnd())//don't check the current element's name
+    {
+        xml.readNext();
+        if (xml.isStartElement())
+        {
+            QStringRef name = xml.name();
+            if (name == "Name")
+            {
+                if (haveKey) throw CiftiException("MD element has multiple Name elements");
+                key = xml.readElementText();
+                haveKey = true;
+            } else if (name == "Value") {
+                if (haveValue) throw CiftiException("MD element has multiple Value elements");
+                value = xml.readElementText();
+                haveValue = true;
+            } else {
+                throw CiftiException("unexpected element name in MD: " + name.toString());
+            }
+        } else if (xml.isEndElement()) {
+            break;
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while (!done && xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+            {
+                AString name = xml.get_local_name();
+                if (name == "Name")
+                {
+                    if (haveKey) throw CiftiException("MD element has multiple Name elements");
+                    key = XmlReader_readElementText(xml);
+                    haveKey = true;
+                } else if (name == "Value") {
+                    if (haveValue) throw CiftiException("MD element has multiple Value elements");
+                    name = XmlReader_readElementText(xml);
+                    haveValue = true;
+                } else {
+                    throw CiftiException("unexpected element name in MD: " + name);
+                }
+                break;
+            }
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    if (haveKey && haveValue)
+    {
+        if (exists(key))
+        {
+            throw CiftiException("key '" + key + "' used more than once in MetaData");
+        } else {
+            set(key, value);
+        }
+    } else {
+        if (haveKey)
+        {
+            throw CiftiException("MD element has no Value element");
+        } else {
+            if (haveValue)
+            {
+                throw CiftiException("MD element has no Name element");
+            } else {
+                throw CiftiException("MD element has no Name or Value element");
+            }
+        }
+    }
+    CiftiAssert(XmlReader_checkEndElement(xml, "MD"));
+}
diff --git a/src/Cifti/MetaData.h b/src/Cifti/MetaData.h
new file mode 100644
index 0000000..158ab1f
--- /dev/null
+++ b/src/Cifti/MetaData.h
@@ -0,0 +1,117 @@
+#ifndef __METADATA_H__
+#define __METADATA_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "Common/AString.h"
+
+#include "Common/XmlAdapter.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <stdexcept>
+#include <vector>
+
+namespace cifti {
+
+class MetaData {
+
+public:
+    MetaData();
+
+public:
+    MetaData(const MetaData& o);
+
+    MetaData& operator=(const MetaData& o);
+    
+    bool operator==(const MetaData& rhs) const;
+    
+    bool operator!=(const MetaData& rhs) const { return !((*this) == rhs); }
+
+    virtual ~MetaData();
+
+private:
+    void copyHelper(const MetaData& o);
+
+    void initializeMembersMetaData();
+    
+public:
+    void clear();
+
+    void append(const MetaData& smd);
+
+    void replace(const MetaData& smd);
+
+    void set(const AString& name, const AString& value);
+
+    void setInt(const AString& name, const int32_t value);
+
+    void setFloat(const AString& name, const float value);
+
+    void replaceWithMap(const std::map<AString, AString>& map);
+    
+    std::map<AString, AString> getAsMap() const;
+    
+    void remove(const AString& name);
+
+    bool exists(const AString& name) const;
+
+    AString get(const AString& name) const;
+
+    int32_t getInt(const AString& name, bool& ok) const;
+
+    float getFloat(const AString& name, bool& ok) const;
+
+    std::vector<AString> getAllMetaDataNames() const;
+
+    void writeCiftiXML1(XmlWriter& xmlWriter) const;
+    void writeCiftiXML2(XmlWriter& xmlWriter) const;//for style, and in case it changes
+    void readCiftiXML1(XmlReader& xml);
+    void readCiftiXML2(XmlReader& xml);
+    
+private:
+    void readEntry(XmlReader& xml);
+    
+    void replaceName(const AString& oldName,
+                     const AString& newName);
+
+public:
+
+private:
+    /**the metadata storage. */
+    std::map<AString, AString> metadata;
+    typedef std::map<AString, AString>::iterator MetaDataIterator;
+    typedef std::map<AString, AString>::const_iterator MetaDataConstIterator;
+    
+};
+
+} // namespace
+
+#endif // __METADATA_H__
diff --git a/src/Cifti/StructureEnum.cxx b/src/Cifti/StructureEnum.cxx
new file mode 100644
index 0000000..7019451
--- /dev/null
+++ b/src/Cifti/StructureEnum.cxx
@@ -0,0 +1,669 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "StructureEnum.h"
+
+#include "Common/CiftiAssert.h"
+#include "Common/CiftiException.h"
+
+using namespace cifti;
+
+std::vector<StructureEnum> StructureEnum::enumData;
+bool StructureEnum::initializedFlag = false;
+
+/**
+ * Constructor.
+ *
+ * @param enumValue
+ *    An enumerated value.
+ * @param integerCode
+ *    Integer code for this enumerated value.
+ *
+ * @param name
+ *    Name of enumerated value.
+ *
+ * @param guiName
+ *    User-friendly name for use in user-interface.
+ */
+StructureEnum::StructureEnum(const Enum enumValue,
+                           const AString& name,
+                           const AString& guiName)
+{
+    this->enumValue = enumValue;
+    this->name = name;
+    this->guiName = guiName;
+}
+
+/**
+ * Destructor.
+ */
+StructureEnum::~StructureEnum()
+{
+}
+
+/**
+ * Initialize the enumerated metadata.
+ */
+void StructureEnum::initialize()
+{
+    if (initializedFlag) {
+        return;
+    }
+    initializedFlag = true;
+
+    enumData.push_back(StructureEnum(CORTEX_LEFT,
+                                     "CORTEX_LEFT",
+                                     "CortexLeft"));
+    
+    enumData.push_back(StructureEnum(CORTEX_RIGHT,
+                                     "CORTEX_RIGHT",
+                                     "CortexRight"));
+    
+    enumData.push_back(StructureEnum(CEREBELLUM,
+                                     "CEREBELLUM",
+                                     "Cerebellum"));
+    
+    enumData.push_back(StructureEnum(ACCUMBENS_LEFT,
+                                     "ACCUMBENS_LEFT",
+                                     "AccumbensLeft"));
+    
+    enumData.push_back(StructureEnum(ACCUMBENS_RIGHT,
+                                     "ACCUMBENS_RIGHT",
+                                     "AccumbensRight"));
+    
+    enumData.push_back(StructureEnum(ALL,
+                                     "ALL",
+                                     "All"));
+    
+    enumData.push_back(StructureEnum(ALL_GREY_MATTER,
+                                     "ALL_GREY_MATTER",
+                                     "AllGreyMatter"));
+    
+    enumData.push_back(StructureEnum(ALL_WHITE_MATTER,
+                                     "ALL_WHITE_MATTER",
+                                     "AllWhiteMatter"));
+    
+    enumData.push_back(StructureEnum(AMYGDALA_LEFT,
+                                     "AMYGDALA_LEFT",
+                                     "AmygdalaLeft"));
+    
+    enumData.push_back(StructureEnum(AMYGDALA_RIGHT,
+                                     "AMYGDALA_RIGHT",
+                                     "AmygdalaRight"));
+    
+    enumData.push_back(StructureEnum(BRAIN_STEM,
+                                     "BRAIN_STEM",
+                                     "BrainStem"));
+    
+    enumData.push_back(StructureEnum(CAUDATE_LEFT,
+                                     "CAUDATE_LEFT",
+                                     "CaudateLeft"));
+    
+    enumData.push_back(StructureEnum(CAUDATE_RIGHT,
+                                     "CAUDATE_RIGHT",
+                                     "CaudateRight"));
+    
+    enumData.push_back(StructureEnum(CEREBELLAR_WHITE_MATTER_LEFT,
+                                     "CEREBELLAR_WHITE_MATTER_LEFT",
+                                     "CerebellarWhiteMatterLeft"));
+    
+    enumData.push_back(StructureEnum(CEREBELLAR_WHITE_MATTER_RIGHT,
+                                     "CEREBELLAR_WHITE_MATTER_RIGHT",
+                                     "CerebellarWhiteMatterRight"));
+    
+    enumData.push_back(StructureEnum(CEREBELLUM_LEFT,
+                                     "CEREBELLUM_LEFT", 
+                                     "CerebellumLeft"));
+    
+    enumData.push_back(StructureEnum(CEREBELLUM_RIGHT, 
+                                     "CEREBELLUM_RIGHT", 
+                                     "CerebellumRight"));
+    
+    enumData.push_back(StructureEnum(CEREBRAL_WHITE_MATTER_LEFT,
+                                     "CEREBRAL_WHITE_MATTER_LEFT",
+                                     "CerebralWhiteMatterLeft"));
+    
+    enumData.push_back(StructureEnum(CEREBRAL_WHITE_MATTER_RIGHT,
+                                     "CEREBRAL_WHITE_MATTER_RIGHT",
+                                     "CerebralWhiteMatterRight"));
+    
+    enumData.push_back(StructureEnum(CORTEX,
+                                     "CORTEX",
+                                     "Cortex"));
+    
+    enumData.push_back(StructureEnum(DIENCEPHALON_VENTRAL_LEFT, 
+                                     "DIENCEPHALON_VENTRAL_LEFT", 
+                                     "DiencephalonVentralLeft"));
+    
+    enumData.push_back(StructureEnum(DIENCEPHALON_VENTRAL_RIGHT, 
+                                     "DIENCEPHALON_VENTRAL_RIGHT", 
+                                     "DiencephalonVentralRight"));
+    
+    enumData.push_back(StructureEnum(HIPPOCAMPUS_LEFT, 
+                                     "HIPPOCAMPUS_LEFT", 
+                                     "HippocampusLeft"));
+    
+    enumData.push_back(StructureEnum(HIPPOCAMPUS_RIGHT, 
+                                     "HIPPOCAMPUS_RIGHT", 
+                                     "HippocampusRight"));
+    
+    enumData.push_back(StructureEnum(INVALID,
+                                     "INVALID",
+                                     "Invalid"));
+    
+    enumData.push_back(StructureEnum(OTHER,
+                                     "OTHER",
+                                     "Other"));
+    
+    enumData.push_back(StructureEnum(OTHER_GREY_MATTER,
+                                     "OTHER_GREY_MATTER",
+                                     "OtherGreyMatter"));
+    
+    enumData.push_back(StructureEnum(OTHER_WHITE_MATTER,
+                                     "OTHER_WHITE_MATTER",
+                                     "OtherWhiteMatter"));
+    
+    enumData.push_back(StructureEnum(PALLIDUM_LEFT,
+                                     "PALLIDUM_LEFT", 
+                                     "PallidumLeft"));
+    
+    enumData.push_back(StructureEnum(PALLIDUM_RIGHT, 
+                                     "PALLIDUM_RIGHT", 
+                                     "PallidumRight"));
+    
+    enumData.push_back(StructureEnum(PUTAMEN_LEFT, 
+                                     "PUTAMEN_LEFT", 
+                                     "PutamenLeft"));
+    
+    enumData.push_back(StructureEnum(PUTAMEN_RIGHT, 
+                                     "PUTAMEN_RIGHT", 
+                                     "PutamenRight"));
+    
+    enumData.push_back(StructureEnum(THALAMUS_LEFT, 
+                                     "THALAMUS_LEFT", 
+                                     "ThalamusLeft"));
+    
+    enumData.push_back(StructureEnum(THALAMUS_RIGHT, 
+                                     "THALAMUS_RIGHT", 
+                                     "ThalamusRight"));
+}
+
+/**
+ * Find the data for and enumerated value.
+ * @param enumValue
+ *     The enumerated value.
+ * @return Pointer to data for this enumerated type
+ * or NULL if no data for type or if type is invalid.
+ */
+const StructureEnum* StructureEnum::findData(const Enum enumValue)
+{
+    if (initializedFlag == false) initialize();
+
+    size_t num = enumData.size();
+    for (size_t i = 0; i < num; i++) {
+        const StructureEnum* d = &enumData[i];
+        if (d->enumValue == enumValue) {
+            return d;
+        }
+    }
+
+    throw CiftiException("unable to find enumeration value " + AString_number(enumValue));
+}
+
+/**
+ * Get a string representation of the enumerated type.
+ * @param enumValue 
+ *     Enumerated value.
+ * @return 
+ *     String representing enumerated value.
+ */
+AString StructureEnum::toName(Enum enumValue) {
+    if (initializedFlag == false) initialize();
+    
+    const StructureEnum* enumInstance = findData(enumValue);
+    return enumInstance->name;
+}
+
+/**
+ * Get an enumerated value corresponding to its name.
+ * @param name 
+ *     Name of enumerated value.
+ * @param isValidOut 
+ *     If not NULL, it is set indicating that a
+ *     enum value exists for the input name.
+ * @return 
+ *     Enumerated value.
+ */
+StructureEnum::Enum StructureEnum::fromName(const AString& name, bool* isValidOut)
+{
+    if (initializedFlag == false) initialize();
+    
+    bool validFlag = false;
+    Enum enumValue = INVALID;
+    
+    for (std::vector<StructureEnum>::iterator iter = enumData.begin();
+         iter != enumData.end();
+         iter++) {
+        const StructureEnum& d = *iter;
+        if (d.name == name) {
+            enumValue = d.enumValue;
+            validFlag = true;
+            break;
+        }
+    }
+    
+    if (isValidOut != 0) {
+        *isValidOut = validFlag;
+    }
+    else if (validFlag == false) {
+        throw CiftiException("Name " + name + "failed to match enumerated value for type StructureEnum");
+    }
+    return enumValue;
+}
+
+/**
+ * Get a GUI string representation of the enumerated type.
+ * @param enumValue 
+ *     Enumerated value.
+ * @return 
+ *     String representing enumerated value.
+ */
+AString StructureEnum::toGuiName(Enum enumValue) {
+    if (initializedFlag == false) initialize();
+    
+    const StructureEnum* enumInstance = findData(enumValue);
+    return enumInstance->guiName;
+}
+
+/**
+ * Get an enumerated value corresponding to its GUI name.
+ * @param guiName
+ *     Name of enumerated value.
+ * @param isValidOut 
+ *     If not NULL, it is set indicating that a
+ *     enum value exists for the input name.
+ * @return 
+ *     Enumerated value.
+ */
+StructureEnum::Enum StructureEnum::fromGuiName(const AString& guiName, bool* isValidOut)
+{
+    if (initializedFlag == false) initialize();
+    
+    bool validFlag = false;
+    Enum enumValue = INVALID;
+    
+    for (std::vector<StructureEnum>::iterator iter = enumData.begin();
+         iter != enumData.end();
+         iter++) {
+        const StructureEnum& d = *iter;
+        if (d.guiName == guiName) {
+            enumValue = d.enumValue;
+            validFlag = true;
+            break;
+        }
+    }
+    
+    if (isValidOut != 0) {
+        *isValidOut = validFlag;
+    }
+    else if (validFlag == false) {
+        throw CiftiException("guiName " + guiName + "failed to match enumerated value for type StructureEnum");
+    }
+    return enumValue;
+}
+
+/**
+ * Get a GUI string representation of the enumerated type.
+ * @param enumValue
+ *     Enumerated value.
+ * @return
+ *     String representing enumerated value.
+ */
+AString StructureEnum::toCiftiName(Enum enumValue) {
+    if (initializedFlag == false) initialize();
+
+    const StructureEnum* enumInstance = findData(enumValue);
+    return "CIFTI_STRUCTURE_" + enumInstance->name;
+}
+
+/**
+ * Get an enumerated value corresponding to its GUI name.
+ * @param ciftiName
+ *     Name of enumerated value.
+ * @param isValidOut
+ *     If not NULL, it is set indicating that a
+ *     enum value exists for the input name.
+ * @return
+ *     Enumerated value.
+ */
+StructureEnum::Enum StructureEnum::fromCiftiName(const AString& ciftiName, bool* isValidOut)
+{
+    if (initializedFlag == false) initialize();
+
+    bool validFlag = false;
+    Enum enumValue = INVALID;
+    if (AString_substr(ciftiName, 0, 16) == "CIFTI_STRUCTURE_")
+    {
+        AString toMatch = AString_substr(ciftiName, 16);
+        for (std::vector<StructureEnum>::iterator iter = enumData.begin();
+            iter != enumData.end();
+            iter++) {
+            const StructureEnum& d = *iter;
+            if (toMatch == d.name) {
+                enumValue = d.enumValue;
+                validFlag = true;
+                break;
+            }
+        }
+    }
+
+    if (isValidOut != 0) {
+        *isValidOut = validFlag;
+    }
+    else if (validFlag == false) {
+        throw CiftiException("ciftiName " + ciftiName + "failed to match enumerated value for type StructureEnum");
+    }
+    return enumValue;
+}
+
+/**
+ * Get all of the enumerated type values.  The values can be used
+ * as parameters to toXXX() methods to get associated metadata.
+ *
+ * @param allEnums
+ *     A vector that is OUTPUT containing all of the enumerated values
+ *     except ALL.
+ */
+void StructureEnum::getAllEnums(std::vector<StructureEnum::Enum>& allEnums)
+{
+    if (initializedFlag == false) initialize();
+    
+    allEnums.clear();
+    
+    for (std::vector<StructureEnum>::iterator iter = enumData.begin();
+         iter != enumData.end();
+         iter++) {
+        StructureEnum::Enum value =iter->enumValue;
+        if (value == ALL) {
+            // nothing
+        }
+        else {
+            allEnums.push_back(iter->enumValue);
+        }
+    }
+}
+
+/**
+ * Is this 'right' structure?
+ * @param enumValue
+ *   The enumerated type.
+ * @return 
+ *   true if the enumerated value represents a 'right' structure, else false.
+ */
+bool StructureEnum::isRight(const Enum enumValue)
+{
+    switch (enumValue)
+    {
+        case ACCUMBENS_RIGHT:
+        case AMYGDALA_RIGHT:
+        case CAUDATE_RIGHT:
+        case CEREBELLAR_WHITE_MATTER_RIGHT:
+        case CEREBELLUM_RIGHT:
+        case CEREBRAL_WHITE_MATTER_RIGHT:
+        case CORTEX_RIGHT:
+        case DIENCEPHALON_VENTRAL_RIGHT:
+        case HIPPOCAMPUS_RIGHT:
+        case PALLIDUM_RIGHT:
+        case PUTAMEN_RIGHT:
+        case THALAMUS_RIGHT:
+            return true;
+        case ALL://avoid default so smart compilers can warn when a new structure isn't added here
+        case ALL_GREY_MATTER:
+        case ALL_WHITE_MATTER:
+        case BRAIN_STEM:
+        case CEREBELLUM:
+        case CORTEX:
+        case INVALID:
+        case OTHER:
+        case OTHER_GREY_MATTER:
+        case OTHER_WHITE_MATTER:
+            return false;//visually separate none/both from opposite cases
+        case ACCUMBENS_LEFT:
+        case AMYGDALA_LEFT:
+        case CAUDATE_LEFT:
+        case CEREBELLAR_WHITE_MATTER_LEFT:
+        case CEREBELLUM_LEFT:
+        case CEREBRAL_WHITE_MATTER_LEFT:
+        case CORTEX_LEFT:
+        case DIENCEPHALON_VENTRAL_LEFT:
+        case HIPPOCAMPUS_LEFT:
+        case PALLIDUM_LEFT:
+        case PUTAMEN_LEFT:
+        case THALAMUS_LEFT:
+            return false;
+    }
+    CiftiAssert(false);
+    return false;
+}
+
+/**
+ * Is this 'left' structure?
+ * @param enumValue
+ *   The enumerated type.
+ * @return 
+ *   true if the enumerated value represents a 'left' structure, else false.
+ */
+bool StructureEnum::isLeft(const Enum enumValue)
+{
+    switch (enumValue)
+    {
+        case ACCUMBENS_LEFT:
+        case AMYGDALA_LEFT:
+        case CAUDATE_LEFT:
+        case CEREBELLAR_WHITE_MATTER_LEFT:
+        case CEREBELLUM_LEFT:
+        case CEREBRAL_WHITE_MATTER_LEFT:
+        case CORTEX_LEFT:
+        case DIENCEPHALON_VENTRAL_LEFT:
+        case HIPPOCAMPUS_LEFT:
+        case PALLIDUM_LEFT:
+        case PUTAMEN_LEFT:
+        case THALAMUS_LEFT:
+            return true;
+        case ALL://avoid default so smart compilers can warn when a new structure isn't added here
+        case ALL_GREY_MATTER:
+        case ALL_WHITE_MATTER:
+        case BRAIN_STEM:
+        case CEREBELLUM:
+        case CORTEX:
+        case INVALID:
+        case OTHER:
+        case OTHER_GREY_MATTER:
+        case OTHER_WHITE_MATTER:
+            return false;//visually separate none/both from opposite cases
+        case ACCUMBENS_RIGHT:
+        case AMYGDALA_RIGHT:
+        case CAUDATE_RIGHT:
+        case CEREBELLAR_WHITE_MATTER_RIGHT:
+        case CEREBELLUM_RIGHT:
+        case CEREBRAL_WHITE_MATTER_RIGHT:
+        case CORTEX_RIGHT:
+        case DIENCEPHALON_VENTRAL_RIGHT:
+        case HIPPOCAMPUS_RIGHT:
+        case PALLIDUM_RIGHT:
+        case PUTAMEN_RIGHT:
+        case THALAMUS_RIGHT:
+            return false;
+    }
+    CiftiAssert(false);
+    return false;
+}
+
+/**
+ * Are the two structure's cortices and contralateral (is one CortexLeft
+ * and one CortexRight)?
+ *
+ * @param enumValueA
+ *    First structure enumerated type.
+ * @param enumValueB
+ *    Second structure enumerated type.
+ * @return
+ *    True if one is CORTEX_LEFT and one is CORTEX_LEFT.
+ */
+bool StructureEnum::isCortexContralateral(const Enum enumValueA,
+                               const Enum enumValueB)
+{
+    if ((enumValueA == CORTEX_LEFT)
+        && (enumValueB == CORTEX_RIGHT)) {
+        return true;
+    }
+    if ((enumValueA == CORTEX_RIGHT)
+        && (enumValueB == CORTEX_LEFT)) {
+        return true;
+    }
+    
+    return false;
+}
+
+/**
+ * For the given structure return its contralateral structure.
+ * Thats is, if this is a left/right structure return its
+ * corresponding structure from the other side.
+ * 
+ * @param enumValue
+ *    Structure for which contralateral structure is desired.
+ * @return The contralateral structure or NULL if it does
+ *    not have a contralateral structure.
+ */
+StructureEnum::Enum StructureEnum::getContralateralStructure(const Enum enumValue)
+{
+    StructureEnum::Enum contralateralStructure = INVALID;
+
+    switch (enumValue) {
+        case ACCUMBENS_LEFT:
+            contralateralStructure = ACCUMBENS_RIGHT;
+            break;
+        case ACCUMBENS_RIGHT:
+            contralateralStructure = ACCUMBENS_LEFT;
+            break;
+        case ALL:
+            contralateralStructure = INVALID;
+            break;
+        case ALL_GREY_MATTER:
+            break;
+        case ALL_WHITE_MATTER:
+            break;
+        case AMYGDALA_LEFT:
+            contralateralStructure = AMYGDALA_RIGHT;
+            break;
+        case AMYGDALA_RIGHT:
+            contralateralStructure = AMYGDALA_LEFT;
+            break;
+        case BRAIN_STEM:
+            contralateralStructure = INVALID;
+            break;
+        case CAUDATE_LEFT:
+            contralateralStructure = CAUDATE_RIGHT;
+            break;
+        case CAUDATE_RIGHT:
+            contralateralStructure = CAUDATE_LEFT;
+            break;
+        case CEREBELLAR_WHITE_MATTER_LEFT:
+            contralateralStructure= CEREBELLAR_WHITE_MATTER_RIGHT;
+            break;
+        case CEREBELLAR_WHITE_MATTER_RIGHT:
+            contralateralStructure = CEREBELLAR_WHITE_MATTER_LEFT;
+            break;
+        case CEREBELLUM:
+            contralateralStructure = INVALID;
+            break;
+        case CEREBELLUM_LEFT:
+            contralateralStructure = CEREBELLUM_RIGHT;
+            break;
+        case CEREBELLUM_RIGHT:
+            contralateralStructure = CEREBELLUM_LEFT;
+            break;
+        case CEREBRAL_WHITE_MATTER_LEFT:
+            contralateralStructure = CEREBELLAR_WHITE_MATTER_RIGHT;
+            break;
+        case CEREBRAL_WHITE_MATTER_RIGHT:
+            contralateralStructure = CEREBELLAR_WHITE_MATTER_LEFT;
+            break;
+        case CORTEX:
+            break;
+        case CORTEX_LEFT:
+            contralateralStructure = CORTEX_RIGHT;
+            break;
+        case CORTEX_RIGHT:
+            contralateralStructure = CORTEX_LEFT;
+            break;
+        case DIENCEPHALON_VENTRAL_LEFT:
+            contralateralStructure = DIENCEPHALON_VENTRAL_RIGHT;
+            break;
+        case DIENCEPHALON_VENTRAL_RIGHT:
+            contralateralStructure = DIENCEPHALON_VENTRAL_LEFT;
+            break;
+        case HIPPOCAMPUS_LEFT:
+            contralateralStructure = HIPPOCAMPUS_RIGHT;
+            break;
+        case HIPPOCAMPUS_RIGHT:
+            contralateralStructure = HIPPOCAMPUS_LEFT;
+            break;
+        case INVALID:
+            contralateralStructure = INVALID;
+            break;
+        case PALLIDUM_LEFT:
+            contralateralStructure = PALLIDUM_RIGHT;
+            break;
+        case PALLIDUM_RIGHT:
+            contralateralStructure = PALLIDUM_LEFT;
+            break;
+        case OTHER:
+            contralateralStructure = INVALID;
+            break;
+        case OTHER_GREY_MATTER:
+            break;
+        case OTHER_WHITE_MATTER:
+            break;
+        case PUTAMEN_LEFT:
+            contralateralStructure = PUTAMEN_RIGHT;
+            break;
+        case PUTAMEN_RIGHT:
+            contralateralStructure = PUTAMEN_LEFT;
+            break;
+        case THALAMUS_LEFT:
+            contralateralStructure = THALAMUS_RIGHT;
+            break;
+        case THALAMUS_RIGHT:
+            contralateralStructure = THALAMUS_LEFT;
+            break;
+    }
+    
+    return contralateralStructure;
+}
+
+
diff --git a/src/Cifti/StructureEnum.h b/src/Cifti/StructureEnum.h
new file mode 100644
index 0000000..7f180e1
--- /dev/null
+++ b/src/Cifti/StructureEnum.h
@@ -0,0 +1,173 @@
+#ifndef __STRUCTURE_ENUM__H_
+#define __STRUCTURE_ENUM__H_
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Common/AString.h"
+
+#include <stdint.h>
+#include <vector>
+
+namespace cifti {
+
+/**
+ * \brief Enumerated type for a structure in a brain.
+ *
+ * Enumerated types for the individual structures in a brain.
+ */
+class StructureEnum {
+
+public:
+    /**
+     * Enumerated values.
+     */
+    enum Enum {
+        /** Invalid */
+        INVALID,
+        /** All Strucures */
+        ALL,
+        /** All white matter */
+        ALL_WHITE_MATTER,
+        /** All grey matter */
+        ALL_GREY_MATTER,
+        /** Left Nucleus Accumbens */
+        ACCUMBENS_LEFT,
+        /** Right Nucleus Accumbens */
+        ACCUMBENS_RIGHT,
+        /** Left Amygdala */
+        AMYGDALA_LEFT,
+        /** Right Amygdala */
+        AMYGDALA_RIGHT,
+        /** Brain Stem */
+        BRAIN_STEM,
+        /** Left Caudate */
+        CAUDATE_LEFT,
+        /** Right Caudate */
+        CAUDATE_RIGHT,
+        /** Cerebellar white matter left */
+        CEREBELLAR_WHITE_MATTER_LEFT,
+        /** Cerebellar white matter right */
+        CEREBELLAR_WHITE_MATTER_RIGHT,
+        /** Cerebellum */
+        CEREBELLUM,
+        /** Left Cerebellum */
+        CEREBELLUM_LEFT,
+        /** Right Cerebellum */
+        CEREBELLUM_RIGHT,
+        /** Cerebral white matter left */
+        CEREBRAL_WHITE_MATTER_LEFT,
+        /** Cerebral white matter right */
+        CEREBRAL_WHITE_MATTER_RIGHT,
+        /** Cortex not specified */
+        CORTEX,
+        /** Left Cerebral Cortex */
+        CORTEX_LEFT,
+        /** Right Cerebral Cortex*/
+        CORTEX_RIGHT,
+        /** Left Ventral Diencephalon */
+        DIENCEPHALON_VENTRAL_LEFT,
+        /** Right Ventral Diencephalon */
+        DIENCEPHALON_VENTRAL_RIGHT,
+        /** Left Hippocampus */
+        HIPPOCAMPUS_LEFT,
+        /** Right Hippocampus */
+        HIPPOCAMPUS_RIGHT,
+        /** Left Pallidum */
+        PALLIDUM_LEFT,
+        /** Right Pallidum */
+        PALLIDUM_RIGHT,
+        /** Other structure not specified */
+        OTHER,
+        /** Other grey matter */
+        OTHER_GREY_MATTER,
+        /** Other white matter */
+        OTHER_WHITE_MATTER,
+        /** Left Putamen */
+        PUTAMEN_LEFT,
+        /** Right Putamen */
+        PUTAMEN_RIGHT,
+        /** Left Thalamus */
+        THALAMUS_LEFT,
+        /** Right Thalamus */
+        THALAMUS_RIGHT
+    };
+
+
+    ~StructureEnum();
+
+    static AString toName(Enum enumValue);
+    
+    static Enum fromName(const AString& name, bool* isValidOut);
+    
+    static AString toGuiName(Enum enumValue);
+    
+    static Enum fromGuiName(const AString& guiName, bool* isValidOut);
+    
+    static AString toCiftiName(Enum enumValue);
+
+    static Enum fromCiftiName(const AString& ciftiName, bool* isValidOut);
+
+    static void getAllEnums(std::vector<Enum>& allEnums);
+
+    static bool isRight(const Enum enumValue);
+    
+    static bool isLeft(const Enum enumValue);
+    
+    static bool isCortexContralateral(const Enum enumValueA,
+                                      const Enum enumValueB);
+    
+    static Enum getContralateralStructure(const Enum enumValue);
+    
+private:
+    StructureEnum(const Enum enumValue, 
+                 const AString& name,
+                 const AString& guiName);
+
+    static const StructureEnum* findData(const Enum enumValue);
+
+    /** Holds all instance of enum values and associated metadata */
+    static std::vector<StructureEnum> enumData;
+
+    /** Initialize instances that contain the enum values and metadata */
+    static void initialize();
+
+    /** Indicates instance of enum values and metadata have been initialized */
+    static bool initializedFlag;
+    
+    /** The enumerated type value for an instance */
+    Enum enumValue;
+
+    /** The name, a text string that is identical to the enumerated value */
+    AString name;
+    
+    /** A user-friendly name that is displayed in the GUI */
+    AString guiName;
+};
+
+} // namespace
+#endif  //__STRUCTURE_ENUM__H_
diff --git a/src/Cifti/VolumeSpace.cxx b/src/Cifti/VolumeSpace.cxx
new file mode 100644
index 0000000..3152a9c
--- /dev/null
+++ b/src/Cifti/VolumeSpace.cxx
@@ -0,0 +1,536 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "VolumeSpace.h"
+
+#include "Common/CiftiAssert.h"
+#include "Common/CiftiException.h"
+#include "Common/FloatMatrix.h"
+
+#ifdef CIFTILIB_USE_QT
+    #include <QRegExp>
+    #include <QStringList>
+#else
+    #ifdef CIFTILIB_USE_XMLPP
+    #else
+        #error "not implemented"
+    #endif
+#endif
+
+#include <algorithm>
+#include <cmath>
+#include <iostream>
+
+using namespace std;
+using namespace cifti;
+
+VolumeSpace::VolumeSpace()
+{
+    m_dims[0] = 0;
+    m_dims[1] = 0;
+    m_dims[2] = 0;
+    m_sform = FloatMatrix::identity(4).getMatrix();
+    computeInverse();
+}
+
+VolumeSpace::VolumeSpace(const int64_t dims[3], const vector<vector<float> >& sform)
+{
+    setSpace(dims, sform);
+}
+
+VolumeSpace::VolumeSpace(const int64_t dims[3], const float sform[12])
+{
+    setSpace(dims, sform);
+}
+
+void VolumeSpace::setSpace(const int64_t dims[3], const vector<vector<float> >& sform)
+{
+    if (sform.size() < 2 || sform.size() > 4)
+    {
+        CiftiAssert(false);
+        throw CiftiException("VolumeSpace initialized with wrong size sform");
+    }
+    for (int i = 0; i < (int)sform.size(); ++i)
+    {
+        if (sform[i].size() != 4)
+        {
+            CiftiAssert(false);
+            throw CiftiException("VolumeSpace initialized with wrong size sform");
+        }
+    }
+    m_dims[0] = dims[0];
+    m_dims[1] = dims[1];
+    m_dims[2] = dims[2];
+    m_sform = sform;
+    m_sform.resize(4);//make sure its 4x4
+    m_sform[3].resize(4);
+    m_sform[3][0] = 0.0f;//force the fourth row to be correct
+    m_sform[3][1] = 0.0f;
+    m_sform[3][2] = 0.0f;
+    m_sform[3][3] = 1.0f;
+    computeInverse();
+}
+
+void VolumeSpace::setSpace(const int64_t dims[3], const float sform[12])
+{
+    m_sform = FloatMatrix::identity(4).getMatrix();
+    for (int i = 0; i < 3; ++i)
+    {
+        for (int j = 0; j < 4; ++j)
+        {
+            m_sform[i][j] = sform[i * 4 + j];
+        }
+    }
+    m_dims[0] = dims[0];
+    m_dims[1] = dims[1];
+    m_dims[2] = dims[2];
+    computeInverse();
+}
+
+void VolumeSpace::computeInverse()
+{
+    m_inverse = FloatMatrix(m_sform).inverse().getMatrix();
+}
+
+void VolumeSpace::spaceToIndex(const float& coordIn1, const float& coordIn2, const float& coordIn3, float& indexOut1, float& indexOut2, float& indexOut3) const
+{
+    indexOut1 = coordIn1 * m_inverse[0][0] + coordIn2 * m_inverse[0][1] + coordIn3 * m_inverse[0][2] + m_inverse[0][3];
+    indexOut2 = coordIn1 * m_inverse[1][0] + coordIn2 * m_inverse[1][1] + coordIn3 * m_inverse[1][2] + m_inverse[1][3];
+    indexOut3 = coordIn1 * m_inverse[2][0] + coordIn2 * m_inverse[2][1] + coordIn3 * m_inverse[2][2] + m_inverse[2][3];
+}
+
+void VolumeSpace::enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const
+{
+    float tempInd1, tempInd2, tempInd3;
+    spaceToIndex(coordIn1, coordIn2, coordIn3, tempInd1, tempInd2, tempInd3);
+    indexOut1 = (int64_t)floor(0.5f + tempInd1);
+    indexOut2 = (int64_t)floor(0.5f + tempInd2);
+    indexOut3 = (int64_t)floor(0.5f + tempInd3);
+}
+
+bool VolumeSpace::matchesVolumeSpace(const VolumeSpace& right) const
+{
+    for (int i = 0; i < 3; ++i)
+    {
+        if (m_dims[i] != right.m_dims[i])
+        {
+            return false;
+        }
+    }
+    const float TOLER_RATIO = 0.999f;//ratio a spacing element can mismatch by
+    for (int i = 0; i < 3; ++i)
+    {
+        for (int j = 0; j < 4; ++j)
+        {
+            float leftelem = m_sform[i][j];
+            float rightelem = right.m_sform[i][j];
+            if ((leftelem != rightelem) && (leftelem == 0.0f || rightelem == 0.0f || (leftelem / rightelem < TOLER_RATIO || rightelem / leftelem < TOLER_RATIO)))
+            {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+bool VolumeSpace::operator==(const VolumeSpace& right) const
+{
+    for (int i = 0; i < 3; ++i)
+    {
+        if (m_dims[i] != right.m_dims[i])
+        {
+            return false;
+        }
+    }
+    for (int i = 0; i < 3; ++i)
+    {
+        for (int j = 0; j < 4; ++j)
+        {
+            if (m_sform[i][j] != right.m_sform[i][j])
+            {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+void VolumeSpace::getSpacingVectors(Vector3D& iStep, Vector3D& jStep, Vector3D& kStep, Vector3D& origin) const
+{
+    FloatMatrix(m_sform).getAffineVectors(iStep, jStep, kStep, origin);
+}
+
+void VolumeSpace::getOrientAndSpacingForPlumb(OrientTypes* orientOut, float* spacingOut, float* originOut) const
+{
+    CiftiAssert(isPlumb());
+    if (!isPlumb())
+    {
+        throw CiftiException("orientation and spacing asked for on non-plumb volume space");//this will fail MISERABLY on non-plumb volumes, so throw otherwise
+    }
+    for (int i = 0; i < 3; ++i)
+    {
+        for (int j = 0; j < 3; ++j)
+        {
+            if (m_sform[i][j] != 0.0f)
+            {
+                spacingOut[j] = m_sform[i][j];
+                originOut[j] = m_sform[i][3];
+                bool negative = (m_sform[i][j] < 0.0f);
+                switch (i)
+                {
+                case 0:
+                    //left/right
+                    orientOut[j] = (negative ? RIGHT_TO_LEFT : LEFT_TO_RIGHT);
+                    break;
+                case 1:
+                    //forward/back
+                    orientOut[j] = (negative ? ANTERIOR_TO_POSTERIOR : POSTERIOR_TO_ANTERIOR);
+                    break;
+                case 2:
+                    //up/down
+                    orientOut[j] = (negative ? SUPERIOR_TO_INFERIOR : INFERIOR_TO_SUPERIOR);
+                    break;
+                default:
+                    //will never get called
+                    break;
+                };
+            }
+        }
+    }
+}
+
+void VolumeSpace::getOrientation(VolumeSpace::OrientTypes orientOut[3]) const
+{
+    Vector3D ivec, jvec, kvec, origin;
+    getSpacingVectors(ivec, jvec, kvec, origin);
+    int next = 1, bestarray[3] = {0, 0, 0};
+    float bestVal = -1.0f;//make sure at least the first test trips true, if there is a zero spacing vector it will default to report LPI
+    for (int first = 0; first < 3; ++first)//brute force search for best fit - only 6 to try
+    {
+        int third = 3 - first - next;
+        float testVal = abs(ivec[first] * jvec[next] * kvec[third]);
+        if (testVal > bestVal)
+        {
+            bestVal = testVal;
+            bestarray[0] = first;
+            bestarray[1] = next;
+        }
+        testVal = abs(ivec[first] * jvec[third] * kvec[next]);
+        if (testVal > bestVal)
+        {
+            bestVal = testVal;
+            bestarray[0] = first;
+            bestarray[1] = third;
+        }
+        next = 0;
+    }
+    bestarray[2] = 3 - bestarray[0] - bestarray[1];
+    Vector3D spaceHats[3];//to translate into enums without casting
+    spaceHats[0] = ivec;
+    spaceHats[1] = jvec;
+    spaceHats[2] = kvec;
+    for (int i = 0; i < 3; ++i)
+    {
+        bool neg = (spaceHats[i][bestarray[i]] < 0.0f);
+        switch (bestarray[i])
+        {
+            case 0:
+                if (neg)
+                {
+                    orientOut[i] = RIGHT_TO_LEFT;
+                } else {
+                    orientOut[i] = LEFT_TO_RIGHT;
+                }
+                break;
+            case 1:
+                if (neg)
+                {
+                    orientOut[i] = ANTERIOR_TO_POSTERIOR;
+                } else {
+                    orientOut[i] = POSTERIOR_TO_ANTERIOR;
+                }
+                break;
+            case 2:
+                if (neg)
+                {
+                    orientOut[i] = SUPERIOR_TO_INFERIOR;
+                } else {
+                    orientOut[i] = INFERIOR_TO_SUPERIOR;
+                }
+                break;
+            default:
+                CiftiAssert(0);
+        }
+    }
+}
+
+bool VolumeSpace::isPlumb() const
+{
+    char axisUsed = 0;
+    char indexUsed = 0;
+    for (int i = 0; i < 3; ++i)
+    {
+        for (int j = 0; j < 3; ++j)
+        {
+            if (m_sform[i][j] != 0.0f)
+            {
+                if (axisUsed & (1<<i))
+                {
+                    return false;
+                }
+                if (indexUsed & (1<<j))
+                {
+                    return false;
+                }
+                axisUsed |= (1<<i);
+                indexUsed |= (1<<j);
+            }
+        }
+    }
+    return true;
+}
+
+void VolumeSpace::readCiftiXML1(XmlReader& xml)
+{
+    vector<AString> mandAttrs(1, "VolumeDimensions"), transAttrs(3);
+    transAttrs[0] = "UnitsXYZ";
+    transAttrs[1] = "DataSpace";//we ignore the values in these, but they are required by cifti-1, so check that they exist
+    transAttrs[2] = "TransformedSpace";
+    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs);
+    vector<AString> dimStrings = AString_split(myAttrs.mandatoryVals[0], ',');
+    if (dimStrings.size() != 3)
+    {
+        throw CiftiException("VolumeDimensions attribute of Volume must contain exactly two commas");
+    }
+    int64_t newDims[3];//don't parse directly into the internal variables
+    bool ok = false;
+    for (int i = 0; i < 3; ++i)
+    {
+        newDims[i] = AString_toInt(dimStrings[i], ok);
+        if (!ok)
+        {
+            throw CiftiException("noninteger found in VolumeDimensions attribute of Volume: " + dimStrings[i]);
+        }
+        if (newDims[i] < 1)
+        {
+            throw CiftiException("found bad value in VolumeDimensions attribute of Volume: " + dimStrings[i]);
+        }
+    }
+    if (!XmlReader_readNextStartElement(xml))
+    {
+        throw CiftiException("failed to find TransformationMatrixVoxelIndicesIJKtoXYZ element in Volume");
+    }
+    if (XmlReader_elementName(xml) != "TransformationMatrixVoxelIndicesIJKtoXYZ")
+    {
+        throw CiftiException("unexpected element in Volume: " + XmlReader_elementName(xml));
+    }
+    XmlAttributesResult transAttrsRes = XmlReader_parseAttributes(xml, transAttrs);
+    float mult = 0.0f;
+    if (transAttrsRes.mandatoryVals[0] == "NIFTI_UNITS_MM")
+    {
+        mult = 1.0f;
+    } else if (transAttrsRes.mandatoryVals[0] == "NIFTI_UNITS_MICRON") {
+        mult = 0.001f;
+    } else {
+        throw CiftiException("unrecognized value for UnitsXYZ in TransformationMatrixVoxelIndicesIJKtoXYZ: " + transAttrsRes.mandatoryVals[0]);
+    }
+    AString accum = XmlReader_readElementText(xml);
+#ifdef CIFTILIB_USE_QT
+    if (xml.hasError()) return;
+#endif
+    vector<AString> matrixStrings = AString_split_whitespace(accum);
+    if (matrixStrings.size() != 16)
+    {
+        throw CiftiException("text content of TransformationMatrixVoxelIndicesIJKtoXYZ must have exactly 16 numbers separated by whitespace");
+    }
+    FloatMatrix newsform = FloatMatrix::zeros(4, 4);
+    for (int j = 0; j < 4; ++j)
+    {
+        for (int i = 0; i < 4; ++i)
+        {
+            newsform[j][i] = AString_toFloat(matrixStrings[i + j * 4], ok);
+            if (!ok)
+            {
+                throw CiftiException("non-number in text of TransformationMatrixVoxelIndicesIJKtoXYZ: " + matrixStrings[i + j * 4]);
+            }
+        }
+    }
+    if (newsform[3][0] != 0.0f || newsform[3][1] != 0.0f || newsform[3][2] != 0.0f || newsform[3][3] != 1.0f)//yes, using equals, because those are all exact in float
+    {
+        cerr << "last row of matrix in TransformationMatrixVoxelIndicesIJKtoXYZ is not 0 0 0 1" << endl;//not an exception, because some cifti-1 exist with this wrong
+    }
+    if (XmlReader_readNextStartElement(xml))//find Volume end element
+    {
+        throw CiftiException("unexpected element in Volume: " + XmlReader_elementName(xml));
+    }
+    newsform *= mult;//apply units
+    newsform[3][3] = 1.0f;//reset [3][3], since it isn't spatial
+    setSpace(newDims, newsform.getMatrix());
+    CiftiAssert(XmlReader_checkEndElement(xml, "Volume"));
+}
+
+void VolumeSpace::readCiftiXML2(XmlReader& xml)
+{//we changed stuff, so separate code
+    vector<AString> mandAttrs(1, "VolumeDimensions"), transAttrs(1, "MeterExponent");
+    XmlAttributesResult myAttrs = XmlReader_parseAttributes(xml, mandAttrs);
+    vector<AString> dimStrings = AString_split(myAttrs.mandatoryVals[0], ',');
+    if (dimStrings.size() != 3)
+    {
+        throw CiftiException("VolumeDimensions attribute of Volume must contain exactly two commas");
+    }
+    int64_t newDims[3];//don't parse directly into the internal variables
+    bool ok = false;
+    for (int i = 0; i < 3; ++i)
+    {
+        newDims[i] = AString_toInt(dimStrings[i], ok);
+        if (!ok)
+        {
+            throw CiftiException("noninteger found in VolumeDimensions attribute of Volume: " + dimStrings[i]);
+        }
+        if (newDims[i] < 1)
+        {
+            throw CiftiException("found bad value in VolumeDimensions attribute of Volume: " + dimStrings[i]);
+        }
+    }
+    if (!XmlReader_readNextStartElement(xml))
+    {
+        throw CiftiException("failed to find TransformationMatrixVoxelIndicesIJKtoXYZ element in Volume");
+    }
+    if (XmlReader_elementName(xml) != "TransformationMatrixVoxelIndicesIJKtoXYZ")
+    {
+        throw CiftiException("unexpected element in Volume: " + XmlReader_elementName(xml));
+    }
+    XmlAttributesResult transAttrsRes = XmlReader_parseAttributes(xml, transAttrs);
+    int exponent = AString_toInt(transAttrsRes.mandatoryVals[0], ok);
+    if (!ok)
+    {
+        throw CiftiException("noninteger value for MeterExponent in TransformationMatrixVoxelIndicesIJKtoXYZ: " + transAttrsRes.mandatoryVals[0]);
+    }
+    float mult = pow(10.0f, exponent + 3);//because our internal units are mm
+    AString accum = XmlReader_readElementText(xml);
+#ifdef CIFTILIB_USE_QT
+    if (xml.hasError()) return;
+#endif
+    vector<AString> matrixStrings = AString_split_whitespace(accum);
+    if (matrixStrings.size() != 16)
+    {
+        throw CiftiException("text content of TransformationMatrixVoxelIndicesIJKtoXYZ must have exactly 16 numbers separated by whitespace");
+    }
+    FloatMatrix newsform = FloatMatrix::zeros(4, 4);
+    for (int j = 0; j < 4; ++j)
+    {
+        for (int i = 0; i < 4; ++i)
+        {
+            newsform[j][i] = AString_toFloat(matrixStrings[i + j * 4], ok);
+            if (!ok)
+            {
+                throw CiftiException("non-number in text of TransformationMatrixVoxelIndicesIJKtoXYZ: " + matrixStrings[i + j * 4]);
+            }
+        }
+    }
+    if (newsform[3][0] != 0.0f || newsform[3][1] != 0.0f || newsform[3][2] != 0.0f || newsform[3][3] != 1.0f)//yes, using equals, because those are all exact in float
+    {
+        throw CiftiException("last row of matrix in TransformationMatrixVoxelIndicesIJKtoXYZ must be 0 0 0 1");
+    }
+    if (XmlReader_readNextStartElement(xml))//find Volume end element
+    {
+        throw CiftiException("unexpected element in Volume: " + XmlReader_elementName(xml));
+    }
+    newsform *= mult;//apply units
+    newsform[3][3] = 1.0f;//reset [3][3], since it isn't spatial
+    setSpace(newDims, newsform.getMatrix());
+    CiftiAssert(XmlReader_checkEndElement(xml, "Volume"));
+}
+
+void VolumeSpace::writeCiftiXML1(XmlWriter& xml) const
+{
+    xml.writeStartElement("Volume");
+    AString dimString = AString_number(m_dims[0]) + "," + AString_number(m_dims[1]) + "," + AString_number(m_dims[2]);
+    xml.writeAttribute("VolumeDimensions", dimString);
+    xml.writeStartElement("TransformationMatrixVoxelIndicesIJKtoXYZ");
+    xml.writeAttribute("DataSpace", "NIFTI_XFORM_UNKNOWN");//meaningless attribute
+    xml.writeAttribute("TransformedSpace", "NIFTI_XFORM_UNKNOWN");//removed in CIFTI-2, but apparently workbench has been writing this value for CIFTI-1
+    xml.writeAttribute("UnitsXYZ", "NIFTI_UNITS_MM");//only other choice in cifti-1 is micron, which we will probably never need in cifti-1
+    AString matrixString;
+    for (int j = 0; j < 3; ++j)
+    {
+        matrixString += "\n";
+        for (int i = 0; i < 4; ++i)
+        {
+            matrixString += AString_number_fixed(m_sform[j][i], 7) + " ";
+        }
+    }
+    matrixString += "\n";
+    for (int i = 0; i < 3; ++i)
+    {
+        matrixString += AString_number_fixed(0.0f, 7) + " ";
+    }
+    matrixString += AString_number_fixed(1.0f, 7);
+    xml.writeCharacters(matrixString);
+    xml.writeEndElement();//Transfor...
+    xml.writeEndElement();//Volume
+}
+
+void VolumeSpace::writeCiftiXML2(XmlWriter& xml) const
+{
+    xml.writeStartElement("Volume");
+    AString dimString = AString_number(m_dims[0]) + "," + AString_number(m_dims[1]) + "," + AString_number(m_dims[2]);
+    xml.writeAttribute("VolumeDimensions", dimString);
+    xml.writeStartElement("TransformationMatrixVoxelIndicesIJKtoXYZ");
+    Vector3D vecs[4];
+    getSpacingVectors(vecs[0], vecs[1], vecs[2], vecs[3]);
+    float minLength = vecs[0].length();
+    for (int i = 1; i < 3; ++i)
+    {
+        minLength = min(minLength, vecs[i].length());
+    }
+    int myExponent = -3;//if we have a singular spatial dimension somehow, just use mm
+    if (minLength != 0.0f)
+    {
+        myExponent = 3 * (int)floor((log10(minLength) - log10(50.0f)) / 3.0f);//some magic to get the exponent that is a multiple of 3 that puts the length of the smallest spacing vector in [0.05, 50]
+    }
+    float multiplier = pow(10.0f, -3 - myExponent);//conversion factor from mm
+    xml.writeAttribute("MeterExponent", AString_number(myExponent));
+    AString matrixString;
+    for (int j = 0; j < 3; ++j)
+    {
+        matrixString += "\n";
+        for (int i = 0; i < 4; ++i)
+        {
+            matrixString += AString_number_fixed(m_sform[j][i] * multiplier, 7) + " ";
+        }
+    }
+    matrixString += "\n";
+    for (int i = 0; i < 3; ++i)
+    {
+        matrixString += AString_number_fixed(0.0f, 7) + " ";
+    }
+    matrixString += AString_number_fixed(1.0f, 7);//doesn't get multiplied, because it isn't spatial
+    xml.writeCharacters(matrixString);
+    xml.writeEndElement();//Transfor...
+    xml.writeEndElement();//Volume
+}
diff --git a/src/Cifti/VolumeSpace.h b/src/Cifti/VolumeSpace.h
new file mode 100644
index 0000000..eb34df6
--- /dev/null
+++ b/src/Cifti/VolumeSpace.h
@@ -0,0 +1,156 @@
+#ifndef __VOLUME_SPACE_H__
+#define __VOLUME_SPACE_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Common/Vector3D.h"
+
+#include "Common/XmlAdapter.h"
+
+#include "stdint.h"
+#include <vector>
+
+namespace cifti
+{
+
+    class VolumeSpace
+    {
+        int64_t m_dims[3];
+        std::vector<std::vector<float> > m_sform, m_inverse;
+        void computeInverse();
+    public:
+        enum OrientTypes
+        {
+            LEFT_TO_RIGHT = 0,
+            RIGHT_TO_LEFT = 4,
+            POSTERIOR_TO_ANTERIOR = 1,
+            ANTERIOR_TO_POSTERIOR = 5,
+            INFERIOR_TO_SUPERIOR = 2,
+            SUPERIOR_TO_INFERIOR = 6
+        };
+        VolumeSpace();
+        VolumeSpace(const int64_t dims[3], const std::vector<std::vector<float> >& sform);
+        VolumeSpace(const int64_t dims[3], const float sform[12]);
+        void setSpace(const int64_t dims[3], const std::vector<std::vector<float> >& sform);
+        void setSpace(const int64_t dims[3], const float sform[12]);
+        const int64_t* getDims() const { return m_dims; }
+        const std::vector<std::vector<float> >& getSform() const { return m_sform; }
+        void getSpacingVectors(Vector3D& iStep, Vector3D& jStep, Vector3D& kStep, Vector3D& origin) const;
+        bool matchesVolumeSpace(const VolumeSpace& right) const;//allows slight mismatches
+        bool operator==(const VolumeSpace& right) const;//requires that it be exact
+        bool operator!=(const VolumeSpace& right) const { return !(*this == right); }
+
+        ///returns true if volume space is not skew, and each axis and index is separate
+        bool isPlumb() const;
+
+        ///returns orientation, spacing, and center (spacing/center can be negative, spacing/center is LPI rearranged to ijk (first dimension uses first element), will assert false if isOblique is true)
+        void getOrientAndSpacingForPlumb(OrientTypes* orientOut, float* spacingOut, float* originOut) const;
+        
+        ///get just orientation, even for non-plumb volumes
+        void getOrientation(OrientTypes orientOut[3]) const;
+        
+        ///returns coordinate triplet of an index triplet
+        template <typename T>
+        inline void indexToSpace(const T* indexIn, float* coordOut) const
+        { indexToSpace<T>(indexIn[0], indexIn[1], indexIn[2], coordOut[0], coordOut[1], coordOut[2]); }
+        
+        ///returns coordinate triplet of three indices
+        template <typename T>
+        inline void indexToSpace(const T& indexIn1, const T& indexIn2, const T& indexIn3, float* coordOut) const
+        { indexToSpace<T>(indexIn1, indexIn2, indexIn3, coordOut[0], coordOut[1], coordOut[2]); }
+        
+        ///returns three coordinates of an index triplet
+        template <typename T>
+        inline void indexToSpace(const T* indexIn, float& coordOut1, float& coordOut2, float& coordOut3) const
+        { indexToSpace<T>(indexIn[0], indexIn[1], indexIn[2], coordOut1, coordOut2, coordOut3); }
+        
+        ///returns three coordinates of three indices
+        template <typename T>
+        void indexToSpace(const T& indexIn1, const T& indexIn2, const T& indexIn3, float& coordOut1, float& coordOut2, float& coordOut3) const;
+
+        ///returns floating point index triplet of a given coordinate triplet
+        inline void spaceToIndex(const float* coordIn, float* indexOut) const { spaceToIndex(coordIn[0], coordIn[1], coordIn[2], indexOut[0], indexOut[1], indexOut[2]); }
+        ///returns floating point index triplet of three given coordinates
+        inline void spaceToIndex(const float& coordIn1, const float& coordIn2, const float& coordIn3, float* indexOut) const { spaceToIndex(coordIn1, coordIn2, coordIn3, indexOut[0], indexOut[1], indexOut[2]); }
+        ///returns three floating point indexes of a given coordinate triplet
+        inline void spaceToIndex(const float* coordIn, float& indexOut1, float& indexOut2, float& indexOut3) const { spaceToIndex(coordIn[0], coordIn[1], coordIn[2], indexOut1, indexOut2, indexOut3); }
+        ///returns three floating point indexes of three given coordinates
+        void spaceToIndex(const float& coordIn1, const float& coordIn2, const float& coordIn3, float& indexOut1, float& indexOut2, float& indexOut3) const;
+
+        ///returns integer index triplet of voxel whose center is closest to the coordinate triplet
+        inline void enclosingVoxel(const float* coordIn, int64_t* indexOut) const { enclosingVoxel(coordIn[0], coordIn[1], coordIn[2], indexOut[0], indexOut[1], indexOut[2]); }
+        ///returns integer index triplet of voxel whose center is closest to the three coordinates
+        inline void enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t* indexOut) const { enclosingVoxel(coordIn1, coordIn2, coordIn3, indexOut[0], indexOut[1], indexOut[2]); }
+        ///returns integer indexes of voxel whose center is closest to the coordinate triplet
+        inline void enclosingVoxel(const float* coordIn, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const { enclosingVoxel(coordIn[0], coordIn[1], coordIn[2], indexOut1, indexOut2, indexOut3); }
+        ///returns integer indexes of voxel whose center is closest to the three coordinates
+        void enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const;
+
+        template <typename T>
+        inline bool indexValid(const T* indexIn) const
+        {
+            return indexValid(indexIn[0], indexIn[1], indexIn[2]);//implicit cast to int64_t
+        }
+
+        ///checks if an index is within array dimensions
+        inline bool indexValid(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3) const
+        {
+            if (indexIn1 < 0 || indexIn1 >= m_dims[0]) return false;
+            if (indexIn2 < 0 || indexIn2 >= m_dims[1]) return false;
+            if (indexIn3 < 0 || indexIn3 >= m_dims[2]) return false;
+            return true;
+        }
+        
+        inline int64_t getIndex(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3) const
+        {
+            return indexIn1 + m_dims[0] * (indexIn2 + m_dims[1] * indexIn3);
+        }
+        
+        template <typename T>
+        inline int64_t getIndex(const T* indexIn) const
+        {
+            return getIndex(indexIn[0], indexIn[1], indexIn[2]);//implicit cast to int64_t
+        }
+        
+        void readCiftiXML1(XmlReader& xml);//xml functions
+        void readCiftiXML2(XmlReader& xml);
+        void writeCiftiXML1(XmlWriter& xml) const;
+        void writeCiftiXML2(XmlWriter& xml) const;
+    };
+
+    template <typename T>
+    void VolumeSpace::indexToSpace(const T& indexIn1, const T& indexIn2, const T& indexIn3, float& coordOut1, float& coordOut2, float& coordOut3) const
+    {
+        coordOut1 = indexIn1 * m_sform[0][0] + indexIn2 * m_sform[0][1] + indexIn3 * m_sform[0][2] + m_sform[0][3];
+        coordOut2 = indexIn1 * m_sform[1][0] + indexIn2 * m_sform[1][1] + indexIn3 * m_sform[1][2] + m_sform[1][3];
+        coordOut3 = indexIn1 * m_sform[2][0] + indexIn2 * m_sform[2][1] + indexIn3 * m_sform[2][2] + m_sform[2][3];
+    }
+
+}
+
+#endif //__VOLUME_SPACE_H__
diff --git a/src/CiftiFile.cxx b/src/CiftiFile.cxx
new file mode 100644
index 0000000..99dd512
--- /dev/null
+++ b/src/CiftiFile.cxx
@@ -0,0 +1,494 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiFile.h"
+
+#include "Common/CiftiAssert.h"
+#include "Common/MultiDimArray.h"
+#include "NiftiIO.h"
+
+#ifdef CIFTILIB_USE_QT
+    #include <QFileInfo>
+#else
+    //use boost filesystem, because cross-platform filesystem support with POSIX is absurd
+    #define BOOST_FILESYSTEM_VERSION 3
+    #include "boost/filesystem.hpp"
+#endif
+
+#include <iostream>
+
+using namespace std;
+using namespace boost;
+using namespace cifti;
+
+//private implementation classes, helpers
+namespace
+{
+    class CiftiOnDiskImpl : public CiftiFile::WriteImplInterface
+    {
+        mutable NiftiIO m_nifti;//because file objects aren't stateless (current position), so reading "changes" them
+        CiftiXML m_xml;//because we need to parse it to set up the dimensions anyway
+    public:
+        CiftiOnDiskImpl(const AString& filename);//read-only
+        CiftiOnDiskImpl(const AString& filename, const CiftiXML& xml, const CiftiVersion& version, const bool& swapEndian);//make new empty file with read/write
+        void getRow(float* dataOut, const std::vector<int64_t>& indexSelect, const bool& tolerateShortRead) const;
+        void getColumn(float* dataOut, const int64_t& index) const;
+        const CiftiXML& getCiftiXML() const { return m_xml; }
+        AString getFilename() const { return m_nifti.getFilename(); }
+        bool isSwapped() const { return m_nifti.getHeader().isSwapped(); }
+        void setRow(const float* dataIn, const std::vector<int64_t>& indexSelect);
+        void setColumn(const float* dataIn, const int64_t& index);
+    };
+    
+    class CiftiMemoryImpl : public CiftiFile::WriteImplInterface
+    {
+        MultiDimArray<float> m_array;
+    public:
+        CiftiMemoryImpl(const CiftiXML& xml);
+        void getRow(float* dataOut, const std::vector<int64_t>& indexSelect, const bool& tolerateShortRead) const;
+        void getColumn(float* dataOut, const int64_t& index) const;
+        bool isInMemory() const { return true; }
+        void setRow(const float* dataIn, const std::vector<int64_t>& indexSelect);
+        void setColumn(const float* dataIn, const int64_t& index);
+    };
+    
+    bool shouldSwap(const CiftiFile::ENDIAN& endian)
+    {
+        if (ByteSwapping::isBigEndian())
+        {
+            if (endian == CiftiFile::LITTLE) return true;
+        } else {
+            if (endian == CiftiFile::BIG) return true;
+        }
+        return false;//default for all other enum values is to write native endian
+    }
+    
+    bool dontRewrite(const CiftiFile::ENDIAN& endian)
+    {
+        return (endian == CiftiFile::ANY);
+    }
+    
+    AString pathToAbsolute(const AString& mypath)
+    {
+#ifdef CIFTILIB_USE_QT
+        return QFileInfo(mypath).absoluteFilePath();
+#else
+#ifdef CIFTILIB_BOOST_NO_FSV3
+	return filesystem::complete(AString_to_std_string(mypath)).file_string();
+#else
+        return filesystem::absolute(AString_to_std_string(mypath)).native();
+#endif
+#endif
+    }
+    
+    AString pathToCanonical(const AString& mypath)
+    {
+#ifdef CIFTILIB_USE_QT
+        return QFileInfo(mypath).canonicalFilePath();
+#else
+#ifdef CIFTILIB_BOOST_NO_FSV3
+        return filesystem::complete(AString_to_std_string(mypath)).file_string();
+#else
+#ifdef CIFTILIB_BOOST_NO_CANONICAL
+        filesystem::path temp = AString_to_std_string(mypath);
+        if (!filesystem::exists(temp)) return "";
+        return absolute(temp).normalize().native();
+#else
+        string temp = AString_to_std_string(mypath);
+        if (!filesystem::exists(temp)) return "";
+        return filesystem::canonical(temp).native();
+#endif
+#endif
+#endif
+    }
+}
+
+CiftiFile::ReadImplInterface::~ReadImplInterface()
+{
+}
+
+CiftiFile::WriteImplInterface::~WriteImplInterface()
+{
+}
+
+CiftiFile::CiftiFile(const AString& fileName)
+{
+    m_endianPref = NATIVE;
+    openFile(fileName);
+}
+
+void CiftiFile::openFile(const AString& fileName)
+{
+    m_writingImpl.reset();
+    m_readingImpl.reset();//to make sure it closes everything first, even if the open throws
+    m_dims.clear();
+    boost::shared_ptr<CiftiOnDiskImpl> newRead(new CiftiOnDiskImpl(pathToAbsolute(fileName)));//this constructor opens existing file read-only
+    m_readingImpl = newRead;//it should be noted that if the constructor throws (if the file isn't readable), new guarantees the memory allocated for the object will be freed
+    m_xml = newRead->getCiftiXML();
+    m_dims = m_xml.getDimensions();
+    m_onDiskVersion = m_xml.getParsedVersion();
+}
+
+void CiftiFile::setWritingFile(const AString& fileName, const CiftiVersion& writingVersion, const ENDIAN& endian)
+{
+    m_writingFile = pathToAbsolute(fileName);//always resolve paths as soon as they enter CiftiFile, in case some clown changes directory before writing data
+    m_writingImpl.reset();//prevent writing to previous writing implementation, let the next set...() set up for writing
+    m_onDiskVersion = writingVersion;
+    m_endianPref = endian;
+}
+
+void CiftiFile::writeFile(const AString& fileName, const CiftiVersion& writingVersion, const ENDIAN& endian)
+{
+    if (m_readingImpl == NULL || m_dims.empty()) throw CiftiException("writeFile called on uninitialized CiftiFile");
+    bool writeSwapped = shouldSwap(endian);
+    AString canonicalFilename = pathToCanonical(fileName);//NOTE: returns EMPTY STRING for nonexistant file
+    const CiftiOnDiskImpl* testImpl = dynamic_cast<CiftiOnDiskImpl*>(m_readingImpl.get());
+    bool collision = false, hadWriter = (m_writingImpl != NULL);
+    if (testImpl != NULL && canonicalFilename != "" && pathToCanonical(testImpl->getFilename()) == canonicalFilename)
+    {//empty string test is so that we don't say collision if both are nonexistant - could happen if file is removed/unlinked while reading on some filesystems
+        if (m_onDiskVersion == writingVersion && (dontRewrite(endian) || writeSwapped == testImpl->isSwapped())) return;//don't need to copy to itself
+        collision = true;//we need to copy to memory temporarily
+        boost::shared_ptr<WriteImplInterface> tempMemory(new CiftiMemoryImpl(m_xml));//because tempRead is a ReadImpl, can't be used to copy to
+        copyImplData(m_readingImpl.get(), tempMemory.get(), m_dims);
+        m_readingImpl = tempMemory;//we are about to make the old reading impl very unhappy, replace it so that if we get an error while writing, we hang onto the memory version
+        m_writingImpl.reset();//and make it re-magic the writing implementation again if it tries to write again
+    }
+    boost::shared_ptr<WriteImplInterface> tempWrite(new CiftiOnDiskImpl(pathToAbsolute(fileName), m_xml, writingVersion, writeSwapped));
+    copyImplData(m_readingImpl.get(), tempWrite.get(), m_dims);
+    if (collision)//if we rewrote the file, we need the handle to the new file, and to dump the temporary in-memory version
+    {
+        m_onDiskVersion = writingVersion;//also record the current version number
+        m_readingImpl = tempWrite;//replace the temporary memory version
+        if (hadWriter)//if it was in read-write mode
+        {
+            m_writingImpl = tempWrite;//set the writer too
+        }
+    }
+}
+
+void CiftiFile::convertToInMemory()
+{
+    if (isInMemory()) return;
+    if (m_readingImpl == NULL || m_dims.empty())//not set up yet
+    {
+        m_writingFile = "";//make sure it doesn't do on-disk when set...() is called
+        return;
+    }
+    boost::shared_ptr<WriteImplInterface> tempWrite(new CiftiMemoryImpl(m_xml));//if we get an error while reading, free the memory immediately, and don't leave m_readingImpl and m_writingImpl pointing to different things
+    copyImplData(m_readingImpl.get(), tempWrite.get(), m_dims);
+    m_writingImpl = tempWrite;
+    m_readingImpl = tempWrite;
+}
+
+bool CiftiFile::isInMemory() const
+{
+    if (m_readingImpl == NULL)
+    {
+        return (m_writingFile == "");//return what it would be if verifyWriteImpl() was called
+    } else {
+        return m_readingImpl->isInMemory();
+    }
+}
+
+void CiftiFile::getRow(float* dataOut, const vector<int64_t>& indexSelect, const bool& tolerateShortRead) const
+{
+    if (m_dims.empty()) throw CiftiException("getRow called on uninitialized CiftiFile");
+    if (m_readingImpl == NULL) return;//NOT an error because we are pretending to have a matrix already, while we are waiting for setRow to actually start writing the file
+    m_readingImpl->getRow(dataOut, indexSelect, tolerateShortRead);
+}
+
+void CiftiFile::getColumn(float* dataOut, const int64_t& index) const
+{
+    if (m_dims.empty()) throw CiftiException("getColumn called on uninitialized CiftiFile");
+    if (m_dims.size() != 2) throw CiftiException("getColumn called on non-2D CiftiFile");
+    if (m_readingImpl == NULL) return;//NOT an error because we are pretending to have a matrix already, while we are waiting for setRow to actually start writing the file
+    m_readingImpl->getColumn(dataOut, index);
+}
+
+void CiftiFile::setCiftiXML(const CiftiXML& xml, const bool useOldMetadata)
+{
+    m_readingImpl.reset();//drop old implementation, as it is now invalid due to XML (and therefore matrix size) change
+    m_writingImpl.reset();
+    if (xml.getNumberOfDimensions() == 0) throw CiftiException("setCiftiXML called with 0-dimensional CiftiXML");
+    if (useOldMetadata)
+    {
+        MetaData newmd = m_xml.getFileMetaData();//make a copy
+        m_xml = xml;//because this will overwrite the metadata
+        m_xml.setFileMetaData(newmd);
+    } else {
+        m_xml = xml;
+    }
+    m_dims = m_xml.getDimensions();
+    for (size_t i = 0; i < m_dims.size(); ++i)
+    {
+        if (m_dims[i] < 1) throw CiftiException("cifti xml dimensions must be greater than zero");
+    }
+}
+
+void CiftiFile::setRow(const float* dataIn, const vector<int64_t>& indexSelect)
+{
+    verifyWriteImpl();
+    m_writingImpl->setRow(dataIn, indexSelect);
+}
+
+void CiftiFile::setColumn(const float* dataIn, const int64_t& index)
+{
+    verifyWriteImpl();
+    if (m_dims.size() != 2) throw CiftiException("setColumn called on non-2D CiftiFile");
+    m_writingImpl->setColumn(dataIn, index);
+}
+
+//single-index functions
+void CiftiFile::getRow(float* dataOut, const int64_t& index, const bool& tolerateShortRead) const
+{
+    if (m_dims.empty()) throw CiftiException("getRow called on uninitialized CiftiFile");
+    if (m_dims.size() != 2) throw CiftiException("getRow with single index called on non-2D CiftiFile");
+    if (m_readingImpl == NULL) return;//NOT an error because we are pretending to have a matrix already, while we are waiting for setRow to actually start writing the file
+    vector<int64_t> tempvec(1, index);//could use a member if we need more speed
+    m_readingImpl->getRow(dataOut, tempvec, tolerateShortRead);
+}
+
+void CiftiFile::setRow(const float* dataIn, const int64_t& index)
+{
+    verifyWriteImpl();
+    if (m_dims.size() != 2) throw CiftiException("setRow with single index called on non-2D CiftiFile");
+    vector<int64_t> tempvec(1, index);//could use a member if we need more speed
+    m_writingImpl->setRow(dataIn, tempvec);
+}
+//*///end single-index functions
+
+void CiftiFile::verifyWriteImpl()
+{//this is where the magic happens - we want to emulate being a simple in-memory file, but actually be reading/writing on-disk when possible
+    if (m_writingImpl != NULL) return;
+    CiftiAssert(!m_dims.empty());//if the xml hasn't been set, then we can't do anything meaningful
+    if (m_dims.empty()) throw CiftiException("setRow or setColumn attempted on uninitialized CiftiFile");
+    if (m_writingFile == "")
+    {
+        if (m_readingImpl != NULL)
+        {
+            convertToInMemory();
+        } else {
+            m_writingImpl = boost::shared_ptr<CiftiMemoryImpl>(new CiftiMemoryImpl(m_xml));
+        }
+    } else {//NOTE: m_onDiskVersion gets set in setWritingFile
+        if (m_readingImpl != NULL)
+        {
+            CiftiOnDiskImpl* testImpl = dynamic_cast<CiftiOnDiskImpl*>(m_readingImpl.get());
+            if (testImpl != NULL)
+            {
+                AString canonicalCurrent = pathToCanonical(testImpl->getFilename());//returns "" if nonexistant, if unlinked while open
+                if (canonicalCurrent != "" && canonicalCurrent == pathToCanonical(m_writingFile))//these were already absolute
+                {
+                    convertToInMemory();//save existing data in memory before we clobber file
+                }
+            }
+        }
+        m_writingImpl = boost::shared_ptr<CiftiOnDiskImpl>(new CiftiOnDiskImpl(m_writingFile, m_xml, m_onDiskVersion, shouldSwap(m_endianPref)));//this constructor makes new file for writing
+        if (m_readingImpl != NULL)
+        {
+            copyImplData(m_readingImpl.get(), m_writingImpl.get(), m_dims);
+        }
+    }
+    m_readingImpl = m_writingImpl;//read-only implementations are set up in specialized functions
+}
+
+void CiftiFile::copyImplData(const ReadImplInterface* from, WriteImplInterface* to, const vector<int64_t>& dims)
+{
+    vector<int64_t> iterateDims(dims.begin() + 1, dims.end());
+    vector<float> scratchRow(dims[0]);
+    for (MultiDimIterator<int64_t> iter(iterateDims); !iter.atEnd(); ++iter)
+    {
+        from->getRow(scratchRow.data(), *iter, false);
+        to->setRow(scratchRow.data(), *iter);
+    }
+}
+
+CiftiMemoryImpl::CiftiMemoryImpl(const CiftiXML& xml)
+{
+    CiftiAssert(xml.getNumberOfDimensions() != 0);
+    m_array.resize(xml.getDimensions());
+}
+
+void CiftiMemoryImpl::getRow(float* dataOut, const vector<int64_t>& indexSelect, const bool&) const
+{
+    const float* ref = m_array.get(1, indexSelect);
+    int64_t rowSize = m_array.getDimensions()[0];//we don't accept 0-D CiftiXML, so this will always work
+    for (int64_t i = 0; i < rowSize; ++i)
+    {
+        dataOut[i] = ref[i];
+    }
+}
+
+void CiftiMemoryImpl::getColumn(float* dataOut, const int64_t& index) const
+{
+    CiftiAssert(m_array.getDimensions().size() == 2);//otherwise, CiftiFile shouldn't have called this
+    const float* ref = m_array.get(2, vector<int64_t>());//empty vector is intentional, only 2 dimensions exist, so no more to select from
+    int64_t rowSize = m_array.getDimensions()[0];
+    int64_t colSize = m_array.getDimensions()[1];
+    CiftiAssert(index >= 0 && index < rowSize);//because we are doing the indexing math manually for speed
+    for (int64_t i = 0; i < colSize; ++i)
+    {
+        dataOut[i] = ref[index + rowSize * i];
+    }
+}
+
+void CiftiMemoryImpl::setRow(const float* dataIn, const vector<int64_t>& indexSelect)
+{
+    float* ref = m_array.get(1, indexSelect);
+    int64_t rowSize = m_array.getDimensions()[0];//we don't accept 0-D CiftiXML, so this will always work
+    for (int64_t i = 0; i < rowSize; ++i)
+    {
+        ref[i] = dataIn[i];
+    }
+}
+
+void CiftiMemoryImpl::setColumn(const float* dataIn, const int64_t& index)
+{
+    CiftiAssert(m_array.getDimensions().size() == 2);//otherwise, CiftiFile shouldn't have called this
+    float* ref = m_array.get(2, vector<int64_t>());//empty vector is intentional, only 2 dimensions exist, so no more to select from
+    int64_t rowSize = m_array.getDimensions()[0];
+    int64_t colSize = m_array.getDimensions()[1];
+    CiftiAssert(index >= 0 && index < rowSize);//because we are doing the indexing math manually for speed
+    for (int64_t i = 0; i < colSize; ++i)
+    {
+        ref[index + rowSize * i] = dataIn[i];
+    }
+}
+
+CiftiOnDiskImpl::CiftiOnDiskImpl(const AString& filename)
+{//opens existing file for reading
+    m_nifti.openRead(filename);//read-only, so we don't need write permission to read a cifti file
+    if (m_nifti.getNumComponents() != 1) throw CiftiException("complex or rgb datatype found in file '" + filename + "', these are not supported in cifti");
+    const NiftiHeader& myHeader = m_nifti.getHeader();
+    int numExts = (int)myHeader.m_extensions.size(), whichExt = -1;
+    for (int i = 0; i < numExts; ++i)
+    {
+        if (myHeader.m_extensions[i]->m_ecode == NIFTI_ECODE_CIFTI)
+        {
+            whichExt = i;
+            break;
+        }
+    }
+    if (whichExt == -1) throw CiftiException("no cifti extension found in file '" + filename + "'");
+    m_xml.readXML(myHeader.m_extensions[whichExt]->m_bytes);
+    vector<int64_t> dimCheck = m_nifti.getDimensions();
+    if (dimCheck.size() < 5) throw CiftiException("invalid dimensions in cifti file '" + filename + "'");
+    for (int i = 0; i < 4; ++i)
+    {
+        if (dimCheck[i] != 1) throw CiftiException("non-singular dimension #" + AString_number(i + 1) + " in cifti file '" + filename + "'");
+    }
+    if (m_xml.getParsedVersion().hasReversedFirstDims())
+    {
+        while (dimCheck.size() < 6) dimCheck.push_back(1);//just in case
+        int64_t temp = dimCheck[4];//note: nifti dim[5] is the 5th dimension, index 4 in this vector
+        dimCheck[4] = dimCheck[5];
+        dimCheck[5] = temp;
+        m_nifti.overrideDimensions(dimCheck);
+    }
+    if (m_xml.getNumberOfDimensions() + 4 != (int)dimCheck.size()) throw CiftiException("XML does not match number of nifti dimensions in file " + filename + "'");
+    for (int i = 4; i < (int)dimCheck.size(); ++i)
+    {
+        if (m_xml.getDimensionLength(i - 4) < 1)//CiftiXML will only let this happen with cifti-1
+        {
+            m_xml.getSeriesMap(i - 4).setLength(dimCheck[i]);//and only in a series map
+        } else {
+            if (m_xml.getDimensionLength(i - 4) != dimCheck[i])
+            {
+                throw CiftiException("xml and nifti header disagree on matrix dimensions");
+            }
+        }
+    }
+}
+
+CiftiOnDiskImpl::CiftiOnDiskImpl(const AString& filename, const CiftiXML& xml, const CiftiVersion& version, const bool& swapEndian)
+{//starts writing new file
+    NiftiHeader outHeader;
+    outHeader.setDataType(NIFTI_TYPE_FLOAT32);//actually redundant currently, default is float32
+    char intentName[16];
+    int32_t intentCode = xml.getIntentInfo(version, intentName);
+    outHeader.setIntent(intentCode, intentName);
+    boost::shared_ptr<NiftiExtension> outExtension(new NiftiExtension());
+    outExtension->m_ecode = NIFTI_ECODE_CIFTI;
+    outExtension->m_bytes = xml.writeXMLToVector(version);
+    outHeader.m_extensions.push_back(outExtension);
+    vector<int64_t> matrixDims = xml.getDimensions();
+    vector<int64_t> niftiDims(4, 1);//the reserved space and time dims
+    niftiDims.insert(niftiDims.end(), matrixDims.begin(), matrixDims.end());
+    if (version.hasReversedFirstDims())
+    {
+        vector<int64_t> headerDims = niftiDims;
+        while (headerDims.size() < 6) headerDims.push_back(1);//just in case
+        int64_t temp = headerDims[4];
+        headerDims[4] = headerDims[5];
+        headerDims[5] = temp;
+        outHeader.setDimensions(headerDims);//give the header the reversed dimensions
+        m_nifti.writeNew(filename, outHeader, 2, true, swapEndian);
+        m_nifti.overrideDimensions(niftiDims);//and then tell the nifti reader to use the correct dimensions
+    } else {
+        outHeader.setDimensions(niftiDims);
+        m_nifti.writeNew(filename, outHeader, 2, true, swapEndian);
+    }
+    m_xml = xml;
+}
+
+void CiftiOnDiskImpl::getRow(float* dataOut, const vector<int64_t>& indexSelect, const bool& tolerateShortRead) const
+{
+    m_nifti.readData(dataOut, 5, indexSelect, tolerateShortRead);//5 means 4 reserved (space and time) plus the first cifti dimension
+}
+
+void CiftiOnDiskImpl::getColumn(float* dataOut, const int64_t& index) const
+{
+    CiftiAssert(m_xml.getNumberOfDimensions() == 2);//otherwise this shouldn't be called
+    CiftiAssert(index >= 0 && index < m_xml.getDimensionLength(CiftiXML::ALONG_ROW));
+    vector<int64_t> indexSelect(2);
+    indexSelect[0] = index;
+    int64_t colLength = m_xml.getDimensionLength(CiftiXML::ALONG_COLUMN);
+    for (int64_t i = 0; i < colLength; ++i)//assume if they really want getColumn on disk, they don't want their pagecache obliterated, so read it 1 element at a time
+    {
+        indexSelect[1] = i;
+        m_nifti.readData(dataOut + i, 4, indexSelect);//4 means just the 4 reserved dimensions, so 1 element of the matrix
+    }
+}
+
+void CiftiOnDiskImpl::setRow(const float* dataIn, const vector<int64_t>& indexSelect)
+{
+    m_nifti.writeData(dataIn, 5, indexSelect);
+}
+
+void CiftiOnDiskImpl::setColumn(const float* dataIn, const int64_t& index)
+{
+    CiftiAssert(m_xml.getNumberOfDimensions() == 2);//otherwise this shouldn't be called
+    CiftiAssert(index >= 0 && index < m_xml.getDimensionLength(CiftiXML::ALONG_ROW));
+    vector<int64_t> indexSelect(2);
+    indexSelect[0] = index;
+    int64_t colLength = m_xml.getDimensionLength(CiftiXML::ALONG_COLUMN);
+    for (int64_t i = 0; i < colLength; ++i)//don't do RMW, so write it 1 element at a time
+    {
+        indexSelect[1] = i;
+        m_nifti.writeData(dataIn + i, 4, indexSelect);//4 means just the 4 reserved dimensions, so 1 element of the matrix
+    }
+}
diff --git a/src/CiftiFile.h b/src/CiftiFile.h
new file mode 100644
index 0000000..7c2fe8e
--- /dev/null
+++ b/src/CiftiFile.h
@@ -0,0 +1,133 @@
+#ifndef __CIFTI_FILE_H__
+#define __CIFTI_FILE_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Common/AString.h"
+#include "Common/CiftiException.h"
+#include "Common/MultiDimIterator.h"
+#include "Cifti/CiftiXML.h"
+
+#include "boost/shared_ptr.hpp"
+
+#include <vector>
+
+///namespace for all CiftiLib functionality
+namespace cifti
+{
+    ///class for reading and writing cifti files
+    class CiftiFile
+    {
+    public:
+        
+        enum ENDIAN
+        {
+            ANY,//so that writeFile() with default endian argument can do nothing after setWritingFile with any endian argument - uses native if there is no rewrite to avoid
+            NATIVE,//as long as there are more than two options anyway, provide a convenience option so people don't need to figure out the machine endianness for a common case
+            LITTLE,
+            BIG
+        };
+        
+        CiftiFile() { m_endianPref = NATIVE; }
+        
+        ///starts on-disk reading
+        explicit CiftiFile(const AString &fileName);
+        
+        ///starts on-disk reading
+        void openFile(const AString& fileName);
+        
+        ///starts on-disk writing
+        void setWritingFile(const AString& fileName, const CiftiVersion& writingVersion = CiftiVersion(), const ENDIAN& endian = NATIVE);
+        
+        ///does nothing if filename, version, and effective endianness match file currently open, otherwise writes complete file
+        void writeFile(const AString& fileName, const CiftiVersion& writingVersion = CiftiVersion(), const ENDIAN& endian = ANY);
+        
+        ///reads file into memory, closes file
+        void convertToInMemory();
+        
+        const CiftiXML& getCiftiXML() const { return m_xml; }
+        bool isInMemory() const;
+        
+        ///the tolerateShortRead parameter is useful for on-disk writing when it is easiest to do RMW multiple times on a new file
+        void getRow(float* dataOut, const std::vector<int64_t>& indexSelect, const bool& tolerateShortRead = false) const;
+        const std::vector<int64_t>& getDimensions() const { return m_dims; }
+        
+        ///convenience function for iterating over arbitrary numbers of dimensions
+        MultiDimIterator<int64_t> getIteratorOverRows() const
+        {
+            return MultiDimIterator<int64_t>(std::vector<int64_t>(m_dims.begin() + 1, m_dims.end()));
+        }
+        
+        ///for 2D only, will be slow if on disk!
+        void getColumn(float* dataOut, const int64_t& index) const;
+        
+        void setCiftiXML(const CiftiXML& xml, const bool useOldMetadata = true);
+        void setRow(const float* dataIn, const std::vector<int64_t>& indexSelect);
+        
+        ///for 2D only, will be slow if on disk!
+        void setColumn(const float* dataIn, const int64_t& index);
+        
+        ///for 2D only, if you don't want to pass a vector for indexing
+        void getRow(float* dataOut, const int64_t& index, const bool& tolerateShortRead = false) const;
+        
+        ///for 2D only, if you don't want to pass a vector for indexing
+        void setRow(const float* dataIn, const int64_t& index);
+
+        //implementation details from here down
+        class ReadImplInterface
+        {
+        public:
+            virtual void getRow(float* dataOut, const std::vector<int64_t>& indexSelect, const bool& tolerateShortRead) const = 0;
+            virtual void getColumn(float* dataOut, const int64_t& index) const = 0;
+            virtual bool isInMemory() const { return false; }
+            virtual ~ReadImplInterface();
+        };
+        //assume if you can write to it, you can also read from it
+        class WriteImplInterface : public ReadImplInterface
+        {
+        public:
+            virtual void setRow(const float* dataIn, const std::vector<int64_t>& indexSelect) = 0;
+            virtual void setColumn(const float* dataIn, const int64_t& index) = 0;
+            virtual ~WriteImplInterface();
+        };
+    private:
+        std::vector<int64_t> m_dims;
+        boost::shared_ptr<WriteImplInterface> m_writingImpl;//this will be equal to m_readingImpl when non-null
+        boost::shared_ptr<ReadImplInterface> m_readingImpl;
+        AString m_writingFile;
+        CiftiXML m_xml;
+        CiftiVersion m_onDiskVersion;
+        ENDIAN m_endianPref;
+        
+        void verifyWriteImpl();
+        static void copyImplData(const ReadImplInterface* from, WriteImplInterface* to, const std::vector<int64_t>& dims);
+    };
+    
+}
+
+#endif //__CIFTI_FILE_H__
diff --git a/src/Common/AString.cxx b/src/Common/AString.cxx
new file mode 100644
index 0000000..cd92c86
--- /dev/null
+++ b/src/Common/AString.cxx
@@ -0,0 +1,151 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "AString.h"
+
+#ifdef CIFTILIB_USE_QT
+#include <QStringList>
+#endif
+
+#ifdef CIFTILIB_USE_XMLPP
+#include "boost/lexical_cast.hpp"
+using namespace boost;
+#endif
+
+using namespace std;
+using namespace cifti;
+
+vector<AString> cifti::AString_split(const AString& input, const char& delim)
+{
+    vector<AString> ret;
+#ifdef CIFTILIB_USE_QT
+    QStringList temp = input.split(delim);
+    int listSize = temp.size();//yes, QT uses int...
+    ret.resize(listSize);
+    for (int i = 0; i < listSize; ++i)
+    {
+        ret[i] = temp[i];
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    size_t start = 0, end = input.find(delim);
+    while (end != AString::npos)
+    {
+        ret.push_back(input.substr(start, end - start));
+        start = end + 1;
+        end = input.find(delim, start);
+    }
+    ret.push_back(input.substr(start));
+#else
+#error "not implemented"
+#endif
+#endif
+    return ret;
+}
+
+vector<AString> cifti::AString_split_whitespace(const AString& input)
+{
+    vector<AString> ret;
+#ifdef CIFTILIB_USE_QT
+    QStringList temp = input.split(QRegExp("\\s+"), QString::SkipEmptyParts);
+    int listSize = temp.size();//yes, QT uses int...
+    ret.resize(listSize);
+    for (int i = 0; i < listSize; ++i)
+    {
+        ret[i] = temp[i];
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    AString::const_iterator iter = input.begin(), end = input.end();
+    while (iter != end)
+    {
+        while (iter != end && g_unichar_isspace(*iter)) ++iter;//skip spaces, including at start of input
+        if (iter == end) break;//ignore spaces on end of input
+        AString::const_iterator start = iter;
+        while (iter != end && !g_unichar_isspace(*iter)) ++iter;//continue to space or end
+        ret.push_back(AString(start, iter));
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    return ret;
+}
+
+int64_t cifti::AString_toInt(const AString& input, bool& ok)
+{
+#ifdef CIFTILIB_USE_QT
+    return input.toLongLong(&ok);
+#else
+#ifdef CIFTILIB_USE_XMLPP
+#ifdef CIFTILIB_BOOST_NO_TRY_LEXICAL
+    try
+    {
+        ok = true;
+        return lexical_cast<int64_t>(input);
+    } catch (...) {
+        ok = false;
+        return 0;
+    }
+#else
+    int64_t ret;
+    ok = conversion::try_lexical_convert(input, ret);
+    if (!ok) ret = 0;
+    return ret;
+#endif
+#else
+#error "not implemented"
+#endif
+#endif
+}
+
+float cifti::AString_toFloat(const AString& input, bool& ok)
+{
+#ifdef CIFTILIB_USE_QT
+    return input.toFloat(&ok);
+#else
+#ifdef CIFTILIB_USE_XMLPP
+#ifdef CIFTILIB_BOOST_NO_TRY_LEXICAL
+    try
+    {
+        ok = true;
+        return lexical_cast<float>(input);
+    } catch (...) {
+        ok = false;
+        return 0.0f;
+    }
+#else
+    float ret;
+    ok = conversion::try_lexical_convert(input, ret);
+    if (!ok) ret = 0.0f;
+    return ret;
+#endif
+#else
+#error "not implemented"
+#endif
+#endif
+}
diff --git a/src/Common/AString.h b/src/Common/AString.h
new file mode 100644
index 0000000..0109c0d
--- /dev/null
+++ b/src/Common/AString.h
@@ -0,0 +1,122 @@
+#ifndef __ASTRING_H__
+#define __ASTRING_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <vector>
+
+#include "stdint.h"
+
+#ifdef __ASTRING_H_HAVE_IMPL__
+#undef __ASTRING_H_HAVE_IMPL__
+#endif
+
+#ifdef CIFTILIB_USE_QT
+#define __ASTRING_H_HAVE_IMPL__
+#include <QString>
+namespace cifti
+{
+    typedef QString AString;
+#define ASTRING_TO_CSTR(mystr) ((mystr).toLocal8Bit().constData())
+#define ASTRING_UTF8_RAW(mystr) ((mystr).toUtf8().constData())
+    inline std::string AString_to_std_string(const AString& mystr)
+    {
+        QByteArray temparray = mystr.toLocal8Bit();
+        return std::string(temparray.constData(), temparray.size());
+    }
+    inline AString AString_from_latin1(const char* data, const int& size)
+    {
+        return QString::fromLatin1(data, size);
+    }
+    inline AString AString_substr(const AString& mystr, const int& first, const int& count = -1)
+    {
+        return mystr.mid(first, count);
+    }
+    template <typename T>
+    AString AString_number(const T& num)
+    {
+        return QString::number(num);
+    }
+    template <typename T>
+    AString AString_number_fixed(const T& num, const int& numDecimals)
+    {
+        return QString::number(num, 'f', numDecimals);
+    }
+}
+#endif //CIFTILIB_USE_QT
+
+#ifdef CIFTILIB_USE_XMLPP
+#define __ASTRING_H_HAVE_IMPL__
+#include "glibmm/convert.h"
+#include "glibmm/ustring.h"
+#include <iomanip>
+namespace cifti
+{
+    typedef Glib::ustring AString;
+#define ASTRING_TO_CSTR(mystr) (Glib::locale_from_utf8((mystr)).c_str())
+#define ASTRING_UTF8_RAW(mystr) ((mystr).data())
+    inline std::string AString_to_std_string(const AString& mystr)
+    {
+        return Glib::locale_from_utf8(mystr);
+    }
+    inline AString AString_from_latin1(const char* data, const int& size)
+    {
+        return Glib::convert(std::string(data, size), "UTF-8", "ISO-8859-1");
+    }
+    inline AString AString_substr(const AString& mystr, const Glib::ustring::size_type& first, const Glib::ustring::size_type& count = std::string::npos)
+    {//HACK: Glib::ustring::npos is undefined at link time with glibmm 2.4 for unknown reasons, but the header says it is equal to std::string's, so use it instead
+        return mystr.substr(first, count);
+    }
+    template <typename T>
+    AString AString_number(const T& num)
+    {
+        return Glib::ustring::format(num);
+    }
+    template <typename T>
+    AString AString_number_fixed(const T& num, const int& numDecimals)
+    {
+        return Glib::ustring::format(std::fixed, std::setprecision(numDecimals), num);
+    }
+}
+#endif //CIFTILIB_USE_XMLPP
+
+#ifndef __ASTRING_H_HAVE_IMPL__
+#error "you must define either CIFTILIB_USE_QT or CIFTILIB_USE_XMLPP to select what unicode string implementation to use"
+#endif
+
+namespace cifti
+{
+    //more helper functions
+    std::vector<AString> AString_split(const AString& input, const char& delim);
+    std::vector<AString> AString_split_whitespace(const AString& input);
+    int64_t AString_toInt(const AString& input, bool& ok);
+    float AString_toFloat(const AString& input, bool& ok);
+}
+
+#endif //__ASTRING_H__
diff --git a/src/Common/BinaryFile.cxx b/src/Common/BinaryFile.cxx
new file mode 100644
index 0000000..1c234d7
--- /dev/null
+++ b/src/Common/BinaryFile.cxx
@@ -0,0 +1,520 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+//try to force large file support from zlib, any other file reading calls
+#ifndef CIFTILIB_OS_MACOSX
+#define _LARGEFILE64_SOURCE
+#define _LFS64_LARGEFILE 1
+#define _FILE_OFFSET_BITS 64
+#endif
+
+#include "BinaryFile.h"
+#include "CiftiAssert.h"
+#include "CiftiException.h"
+
+#ifdef CIFTILIB_USE_QT
+    #include <QFile>
+#else
+    #include "stdio.h"
+    #include "errno.h"
+    #define BOOST_FILESYSTEM_VERSION 3
+    #include "boost/filesystem.hpp"
+#endif
+
+#ifdef CIFTILIB_HAVE_ZLIB
+#include "zlib.h"
+#endif //CIFTILIB_HAVE_ZLIB
+
+#include <algorithm>
+#include <iostream>
+
+using namespace cifti;
+using boost::shared_ptr;
+using namespace std;
+
+//private implementation classes
+namespace cifti
+{
+#ifdef ZLIB_VERSION
+    class ZFileImpl : public BinaryFile::ImplInterface
+    {
+        gzFile m_zfile;
+        const static int64_t CHUNK_SIZE;
+    public:
+        ZFileImpl() { m_zfile = NULL; }
+        void open(const AString& filename, const BinaryFile::OpenMode& opmode);
+        void close();
+        void seek(const int64_t& position);
+        int64_t pos();
+        void read(void* dataOut, const int64_t& count, int64_t* numRead);
+        void write(const void* dataIn, const int64_t& count);
+        ~ZFileImpl();
+    };
+
+    const int64_t ZFileImpl::CHUNK_SIZE = 1<<26;//64MiB, large enough for good performance, small enough for zlib, must convert to uint32
+#endif //ZLIB_VERSION
+
+#ifdef CIFTILIB_USE_QT
+    class QFileImpl : public BinaryFile::ImplInterface
+    {
+        QFile m_file;
+        const static int64_t CHUNK_SIZE;
+    public:
+        void open(const AString& filename, const BinaryFile::OpenMode& opmode);
+        void close();
+        void seek(const int64_t& position);
+        int64_t pos();
+        void read(void* dataOut, const int64_t& count, int64_t* numRead);
+        void write(const void* dataIn, const int64_t& count);
+    };
+
+    const int64_t QFileImpl::CHUNK_SIZE = 1<<30;//1GiB, QT4 apparently chokes at more than 2GiB via buffer.read using int32
+#else
+    class StrFileImpl : public BinaryFile::ImplInterface
+    {
+        FILE* m_file;
+        int64_t m_curPos;//so we can avoid calling seek when it is to current position - QFile does this, and it makes it much faster for some cases
+    public:
+        StrFileImpl() { m_file = NULL; m_curPos = -1; }
+        void open(const AString& filename, const BinaryFile::OpenMode& opmode);
+        void close();
+        void seek(const int64_t& position);
+        int64_t pos();
+        void read(void* dataOut, const int64_t& count, int64_t* numRead);
+        void write(const void* dataIn, const int64_t& count);
+        ~StrFileImpl();
+    };
+#endif //CIFTILIB_USE_QT
+}
+
+BinaryFile::ImplInterface::~ImplInterface()
+{
+}
+
+BinaryFile::BinaryFile(const AString& filename, const OpenMode& fileMode)
+{
+    open(filename, fileMode);
+}
+
+void BinaryFile::close()
+{
+    m_curMode = NONE;
+    if (m_impl == NULL) return;
+    m_impl->close();
+    m_impl.reset();
+}
+
+AString BinaryFile::getFilename() const
+{
+    if (m_impl == NULL) return "";//don't throw, its not really a problem
+    return m_impl->getFilename();
+}
+
+bool BinaryFile::getOpenForRead()
+{
+    return (m_curMode | READ) != 0;
+}
+
+bool BinaryFile::getOpenForWrite()
+{
+    return (m_curMode | WRITE) != 0;
+}
+
+void BinaryFile::open(const AString& filename, const OpenMode& opmode)
+{
+    close();
+    if (opmode == NONE) throw CiftiException("can't open file with NONE mode");
+    if (AString_substr(filename, filename.size() - 3) == ".gz")
+    {
+#ifdef ZLIB_VERSION
+        m_impl = boost::shared_ptr<ZFileImpl>(new ZFileImpl());
+#else //ZLIB_VERSION
+        throw CiftiException("can't open .gz file '" + filename + "', compiled without zlib support");
+#endif //ZLIB_VERSION
+    } else {
+#ifdef CIFTILIB_USE_QT
+        m_impl = boost::shared_ptr<QFileImpl>(new QFileImpl());
+#else
+        m_impl = boost::shared_ptr<StrFileImpl>(new StrFileImpl());
+#endif
+    }
+    m_impl->open(filename, opmode);
+    m_curMode = opmode;
+}
+
+void BinaryFile::read(void* dataOut, const int64_t& count, int64_t* numRead)
+{
+    CiftiAssert(count >= 0);//not sure about allowing 0
+    if (!getOpenForRead()) throw CiftiException("file is not open for reading");
+    m_impl->read(dataOut, count, numRead);
+}
+
+void BinaryFile::seek(const int64_t& position)
+{
+    CiftiAssert(position >= 0);
+    if (m_curMode == NONE) throw CiftiException("file is not open, can't seek");
+    m_impl->seek(position);
+}
+
+int64_t BinaryFile::pos()
+{
+    if (m_curMode == NONE) throw CiftiException("file is not open, can't report position");
+    return m_impl->pos();
+}
+
+void BinaryFile::write(const void* dataIn, const int64_t& count)
+{
+    CiftiAssert(count >= 0);//not sure about allowing 0
+    if (!getOpenForWrite()) throw CiftiException("file is not open for writing");
+    m_impl->write(dataIn, count);
+}
+
+#ifdef ZLIB_VERSION
+void ZFileImpl::open(const AString& filename, const BinaryFile::OpenMode& opmode)
+{
+    close();//don't need to, but just because
+    m_fileName = filename;
+    const char* mode = NULL;
+    switch (opmode)//we only support a limited number of combinations, and the string modes are quirky
+    {
+        case BinaryFile::READ:
+            mode = "rb";
+            break;
+        case BinaryFile::WRITE_TRUNCATE:
+            mode = "wb";//you have to do "r+b" in order to ask it to not truncate, which zlib doesn't support anyway
+            break;
+        default:
+            throw CiftiException("compressed file only supports READ and WRITE_TRUNCATE modes");
+    }
+#if !defined(CIFTILIB_OS_MACOSX) && ZLIB_VERNUM > 0x1232
+    m_zfile = gzopen64(ASTRING_TO_CSTR(filename), mode);
+#else
+    m_zfile = gzopen(ASTRING_TO_CSTR(filename), mode);
+#endif
+    if (m_zfile == NULL)
+    {
+#ifdef CIFTILIB_USE_QT
+        if (QFile::exists(filename))
+#else
+        if (boost::filesystem::exists(AString_to_std_string(filename)))
+#endif
+        {
+            if (!(opmode & BinaryFile::TRUNCATE))
+            {
+                throw CiftiException("failed to open compressed file '" + filename + "', file does not exist, or folder permissions prevent seeing it");
+            } else {//m_file.error() doesn't help identify this case, see below
+                throw CiftiException("failed to open compressed file '" + filename + "', unable to create file");
+            }
+        }
+        throw CiftiException("failed to open compressed file '" + filename + "'");
+    }
+}
+
+void ZFileImpl::close()
+{
+    if (m_zfile == NULL) return;//happens when closed and then destroyed, error opening
+    gzflush(m_zfile, Z_FULL_FLUSH);
+    if (gzclose(m_zfile) != 0) throw CiftiException("error closing compressed file '" + m_fileName + "'");
+    m_zfile = NULL;
+}
+
+void ZFileImpl::read(void* dataOut, const int64_t& count, int64_t* numRead)
+{
+    if (m_zfile == NULL) throw CiftiException("read called on unopened ZFileImpl");//shouldn't happen
+    int64_t totalRead = 0;
+    int readret = 0;//to preserve the info of the read that broke early
+    while (totalRead < count)
+    {
+        int64_t iterSize = min(count - totalRead, CHUNK_SIZE);
+        readret = gzread(m_zfile, ((char*)dataOut) + totalRead, iterSize);
+        if (readret < 1) break;//0 or -1 indicate eof or error
+        totalRead += readret;
+    }
+    if (numRead == NULL)
+    {
+        if (totalRead != count)
+        {
+            if (readret < 0) throw CiftiException("error while reading compressed file '" + m_fileName + "'");
+            throw CiftiException("premature end of file in compressed file '" + m_fileName + "'");
+        }
+    } else {
+        *numRead = totalRead;
+    }
+}
+
+void ZFileImpl::seek(const int64_t& position)
+{
+    if (m_zfile == NULL) throw CiftiException("seek called on unopened ZFileImpl");//shouldn't happen
+    if (pos() == position) return;//slight hack, since gzseek is slow or nonfunctional for some cases, so don't try it unless necessary
+#if !defined(CIFTILIB_OS_MACOSX) && ZLIB_VERNUM > 0x1232
+    int64_t ret = gzseek64(m_zfile, position, SEEK_SET);
+#else
+    int64_t ret = gzseek(m_zfile, position, SEEK_SET);
+#endif
+    if (ret != position) throw CiftiException("seek failed in compressed file '" + m_fileName + "'");
+}
+
+int64_t ZFileImpl::pos()
+{
+    if (m_zfile == NULL) throw CiftiException("pos called on unopened ZFileImpl");//shouldn't happen
+#if !defined(CIFTILIB_OS_MACOSX) && ZLIB_VERNUM > 0x1232
+    return gztell64(m_zfile);
+#else
+    return gztell(m_zfile);
+#endif
+}
+
+void ZFileImpl::write(const void* dataIn, const int64_t& count)
+{
+    if (m_zfile == NULL) throw CiftiException("write called on unopened ZFileImpl");//shouldn't happen
+    int64_t totalWritten = 0;
+    while (totalWritten < count)
+    {
+        int64_t iterSize = min(count - totalWritten, CHUNK_SIZE);
+        int writeret = gzwrite(m_zfile, ((const char*)dataIn) + totalWritten, iterSize);
+        if (writeret < 1) break;//0 or -1 indicate eof or error
+        totalWritten += writeret;
+    }
+    if (totalWritten != count) throw CiftiException("failed to write to compressed file '" + m_fileName + "'");
+}
+
+ZFileImpl::~ZFileImpl()
+{
+    try//throwing from a destructor is a bad idea
+    {
+        close();
+    } catch (const CiftiException& e) {
+        cerr << AString_to_std_string(e.whatString()) << endl;
+    } catch (exception& e) {
+        cerr << e.what() << endl;
+    } catch (...) {
+        cerr << AString_to_std_string("caught unknown exception type while closing compressed file '" + m_fileName + "'") << endl;
+    }
+}
+#endif //ZLIB_VERSION
+
+#ifdef CIFTILIB_USE_QT
+
+void QFileImpl::open(const AString& filename, const BinaryFile::OpenMode& opmode)
+{
+    close();//don't need to, but just because
+    m_fileName = filename;
+    QIODevice::OpenMode mode = QIODevice::NotOpen;//means 0
+    if (opmode & BinaryFile::READ) mode |= QIODevice::ReadOnly;
+    if (opmode & BinaryFile::WRITE) mode |= QIODevice::WriteOnly;
+    if (opmode & BinaryFile::TRUNCATE) mode |= QIODevice::Truncate;//expect QFile to recognize silliness like TRUNCATE by itself
+    m_file.setFileName(filename);
+    if (!m_file.open(mode))
+    {
+        if (!m_file.exists())
+        {
+            if (!(opmode & BinaryFile::TRUNCATE))
+            {
+                throw CiftiException("failed to open file '" + filename + "', file does not exist, or folder permissions prevent seeing it");
+            } else {//m_file.error() doesn't help identify this case, see below
+                throw CiftiException("failed to open file '" + filename + "', unable to create file");
+            }
+        }
+        switch (m_file.error())
+        {
+            case QFile::ResourceError://on linux at least, it never gives another code besides the unhelpful OpenError
+                throw CiftiException("failed to open file '" + filename + "', too many open files");
+            default:
+                throw CiftiException("failed to open file '" + filename + "'");
+        }
+    }
+}
+
+void QFileImpl::close()
+{
+    m_file.close();
+}
+
+void QFileImpl::read(void* dataOut, const int64_t& count, int64_t* numRead)
+{
+    int64_t total = 0;
+    int64_t readret = -1;
+    while (total < count)
+    {
+        int64_t maxToRead = min(count - total, CHUNK_SIZE);
+        readret = m_file.read(((char*)dataOut) + total, maxToRead);//QFile chokes on large reads also
+        if (readret < 1) break;//0 or -1 means error or eof
+        total += readret;
+    }
+    if (numRead == NULL)
+    {
+        if (total != count)
+        {
+            if (readret < 0) throw CiftiException("error while reading file '" + m_fileName + "'");
+            throw CiftiException("premature end of file in '" + m_fileName + "'");
+        }
+    } else {
+        *numRead = total;
+    }
+}
+
+void QFileImpl::seek(const int64_t& position)
+{
+    if (!m_file.seek(position)) throw CiftiException("seek failed in file '" + m_fileName + "'");
+}
+
+int64_t QFileImpl::pos()
+{
+    return m_file.pos();
+}
+
+void QFileImpl::write(const void* dataIn, const int64_t& count)
+{
+    int64_t total = 0;
+    int64_t writeret = -1;
+    while (total < count)
+    {
+        int64_t maxToWrite = min(count - total, CHUNK_SIZE);
+        writeret = m_file.write((const char*)dataIn, maxToWrite);//QFile probably also chokes on large writes
+        if (writeret < 1) break;//0 or -1 means error or eof
+        total += writeret;
+    }
+    if (total != count) throw CiftiException("failed to write to file '" + m_fileName + "'");
+}
+
+#else //CIFTILIB_USE_QT
+
+void StrFileImpl::open(const AString& filename, const BinaryFile::OpenMode& opmode)
+{
+    close();
+    m_fileName = filename;
+    const char* mode = NULL;
+    switch (opmode)
+    {
+        case BinaryFile::READ:
+            mode = "rb";
+            break;
+        case BinaryFile::READ_WRITE:
+            mode = "r+b";
+            break;
+        case BinaryFile::WRITE_TRUNCATE:
+            mode = "wb";
+            break;
+        case BinaryFile::READ_WRITE_TRUNCATE:
+            mode = "w+b";
+            break;
+        default:
+            throw CiftiException("unsupported open mode in StrFileImpl");
+    }
+    errno = 0;
+    m_file = fopen(ASTRING_TO_CSTR(filename), mode);
+    int save_err = errno;
+    if (m_file == NULL)
+    {
+        if (!boost::filesystem::exists(AString_to_std_string(filename)))
+        {
+            if (!(opmode & BinaryFile::TRUNCATE))
+            {
+                throw CiftiException("failed to open file '" + filename + "', file does not exist, or folder permissions prevent seeing it");
+            } else {
+                throw CiftiException("failed to open file '" + filename + "', unable to create file");
+            }
+        }
+        switch (save_err)
+        {
+            case EMFILE:
+            case ENFILE:
+                throw CiftiException("failed to open file '" + filename + "', too many open files");
+            default:
+                throw CiftiException("failed to open file '" + filename + "'");
+        }
+    }
+    m_curPos = 0;
+}
+
+void StrFileImpl::close()
+{
+    if (m_file == NULL) return;
+    int ret = fclose(m_file);
+    m_file = NULL;
+    m_curPos = -1;
+    if (ret != 0) throw CiftiException("error closing file '" + m_fileName + "'");
+}
+
+void StrFileImpl::read(void* dataOut, const int64_t& count, int64_t* numRead)
+{
+    if (m_file == NULL) throw CiftiException("read called on unopened StrFileImpl");//shouldn't happen
+    int64_t readret = fread(dataOut, 1, count, m_file);//expect fread to not have read size limitations comapred to memory
+    m_curPos += readret;//the item size is 1
+    CiftiAssert(m_curPos == ftello(m_file));//double check it in debug, ftello is fast on linux at least
+    if (numRead == NULL)
+    {
+        if (readret != count)
+        {
+            if (feof(m_file)) throw CiftiException("premature end of file in file '" + m_fileName + "'");
+            throw CiftiException("error while reading file '" + m_fileName + "'");
+        }
+    } else {
+        *numRead = readret;
+    }
+}
+
+void StrFileImpl::seek(const int64_t& position)
+{
+    if (m_file == NULL) throw CiftiException("seek called on unopened StrFileImpl");//shouldn't happen
+    if (position == m_curPos) return;//optimization: calling fseeko causes nontrivial system call time, on linux at least
+    int ret = fseeko(m_file, position, SEEK_SET);
+    if (ret != 0) throw CiftiException("seek failed in file '" + m_fileName + "'");
+    m_curPos = position;
+}
+
+int64_t StrFileImpl::pos()
+{
+    if (m_file == NULL) throw CiftiException("pos called on unopened StrFileImpl");//shouldn't happen
+    CiftiAssert(m_curPos == ftello(m_file));//make sure it is right in debug
+    return m_curPos;//we can avoid a call here also
+}
+
+void StrFileImpl::write(const void* dataIn, const int64_t& count)
+{
+    if (m_file == NULL) throw CiftiException("write called on unopened StrFileImpl");//shouldn't happen
+    int64_t writeret = fwrite(dataIn, 1, count, m_file);//expect fwrite to not have write size limitations compared to memory
+    m_curPos += writeret;//the item size is 1
+    CiftiAssert(m_curPos == ftello(m_file));//double check it in debug, ftello is fast on linux at least
+    if (writeret != count) throw CiftiException("failed to write to file '" + m_fileName + "'");
+}
+
+StrFileImpl::~StrFileImpl()
+{
+    try//throwing from a destructor is a bad idea
+    {
+        close();
+    } catch (const CiftiException& e) {
+        cerr << AString_to_std_string(e.whatString()) << endl;
+    } catch (exception& e) {
+        cerr << e.what() << endl;
+    } catch (...) {
+        cerr << AString_to_std_string("caught unknown exception type while closing file '" + m_fileName + "'") << endl;
+    }
+}
+
+#endif //CIFTILIB_USE_QT
diff --git a/src/Common/BinaryFile.h b/src/Common/BinaryFile.h
new file mode 100644
index 0000000..513bf77
--- /dev/null
+++ b/src/Common/BinaryFile.h
@@ -0,0 +1,85 @@
+#ifndef __BINARY_FILE_H__
+#define __BINARY_FILE_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "boost/shared_ptr.hpp"
+
+#include "AString.h"
+
+#include <stdint.h>
+
+namespace cifti {
+    
+    //class to hide difference between compressed and standard binary file reading, and to automate error checking (throws if problem)
+    class BinaryFile
+    {
+    public:
+        enum OpenMode
+        {
+            NONE = 0,
+            READ = 1,
+            WRITE = 2,
+            READ_WRITE = 3,//for convenience
+            TRUNCATE = 4,
+            WRITE_TRUNCATE = 6,//ditto
+            READ_WRITE_TRUNCATE = 7//ditto
+        };
+        BinaryFile() { }
+        ///constructor that opens file
+        BinaryFile(const AString& filename, const OpenMode& fileMode = READ);
+        void open(const AString& filename, const OpenMode& opmode = READ);
+        void close();
+        AString getFilename() const;//not a reference because when no file is open, m_impl is NULL
+        bool getOpenForRead();
+        bool getOpenForWrite();
+        void seek(const int64_t& position);
+        int64_t pos();
+        void read(void* dataOut, const int64_t& count, int64_t* numRead = NULL);//throw if numRead is NULL and (error or end of file reached early)
+        void write(const void* dataIn, const int64_t& count);//failure to complete write is always an exception
+        class ImplInterface
+        {
+        protected:
+            AString m_fileName;//filename is tracked here so error messages can be implementation-specific
+        public:
+            virtual void open(const AString& filename, const OpenMode& opmode) = 0;
+            virtual void close() = 0;
+            const AString& getFilename() const { return m_fileName; }
+            virtual void seek(const int64_t& position) = 0;
+            virtual int64_t pos() = 0;
+            virtual void read(void* dataOut, const int64_t& count, int64_t* numRead) = 0;
+            virtual void write(const void* dataIn, const int64_t& count) = 0;
+            virtual ~ImplInterface();
+        };
+    private:
+        boost::shared_ptr<ImplInterface> m_impl;
+        OpenMode m_curMode;//so implementation classes don't have to track it
+    };
+} //namespace cifti
+
+#endif //__BINARY_FILE_H__
diff --git a/src/Common/ByteSwapping.h b/src/Common/ByteSwapping.h
new file mode 100644
index 0000000..609b5f8
--- /dev/null
+++ b/src/Common/ByteSwapping.h
@@ -0,0 +1,82 @@
+#ifndef __BYTE_SWAPPING_H__
+#define __BYTE_SWAPPING_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+
+namespace cifti {
+
+/**
+ * This class contains static methods for byte swapping data, typically used
+ * when reading binary data files.
+ */
+
+    class ByteSwapping {
+    public:
+        template<typename T>
+        static void swap(T& toSwap);
+
+        template<typename T>
+        static void swapArray(T* toSwap, const uint64_t& count);
+        
+        inline static bool isBigEndian()
+        {
+            uint16_t test = 1;
+            return (((char*)&test)[0] == 0);
+        }
+
+    };
+
+    template<typename T>
+    void ByteSwapping::swap(T& toSwap)
+    {
+        if (sizeof(T) == 1) return;//we could specialize 1-byte types, but this should optimize out
+        T temp = toSwap;
+        char* from = (char*)&temp;
+        char* to = (char*)&toSwap;
+        for (int i = 0; i < (int)sizeof(T); ++i)
+        {
+            to[i] = from[sizeof(T) - i - 1];
+        }
+    }
+
+    template<typename T>
+    void ByteSwapping::swapArray(T* toSwap, const uint64_t& count)
+    {
+        if (sizeof(T) == 1) return;//ditto
+        for (uint64_t i = 0; i < count; ++i)
+        {
+            swap(toSwap[i]);
+        }
+    }
+
+}
+
+#endif  // __BYTE_SWAPPING_H__
+
diff --git a/src/Common/CMakeLists.txt b/src/Common/CMakeLists.txt
new file mode 100644
index 0000000..1131092
--- /dev/null
+++ b/src/Common/CMakeLists.txt
@@ -0,0 +1,31 @@
+
+PROJECT(Common)
+
+SET(HEADERS
+AString.h
+ByteSwapping.h
+CiftiAssert.h
+BinaryFile.h
+CiftiException.h
+CiftiMutex.h
+Compact3DLookup.h
+CompactLookup.h
+FloatMatrix.h
+MatrixFunctions.h
+MathFunctions.h
+MultiDimArray.h
+MultiDimIterator.h
+Vector3D.h
+VoxelIJK.h
+XmlAdapter.h
+)
+
+SET(SOURCES
+AString.cxx
+BinaryFile.cxx
+CiftiException.cxx
+FloatMatrix.cxx
+MathFunctions.cxx
+Vector3D.cxx
+XmlAdapter.cxx
+)
diff --git a/src/Common/CiftiAssert.h b/src/Common/CiftiAssert.h
new file mode 100644
index 0000000..6d84b3d
--- /dev/null
+++ b/src/Common/CiftiAssert.h
@@ -0,0 +1,11 @@
+#ifndef __CARET_ASSERT_H__
+#define __CARET_ASSERT_H__
+
+//some minimal adaptation to cassert
+#include <cassert>
+
+#define CiftiAssert(i) assert(i)
+
+#define CiftiAssertVectorIndex(v, i) assert((i) >= 0 && (i) < static_cast<int64_t>((v).size()))
+
+#endif //__CARET_ASSERT_H__
diff --git a/src/Common/CiftiException.cxx b/src/Common/CiftiException.cxx
new file mode 100644
index 0000000..814ba52
--- /dev/null
+++ b/src/Common/CiftiException.cxx
@@ -0,0 +1,98 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiException.h"
+
+using namespace cifti;
+
+/**
+ * Constructor.
+ *
+ */
+CiftiException::CiftiException()
+: std::runtime_error("")
+{
+}
+
+/**
+ * Constructor.
+ *
+ * @param  s  Description of the exception.
+ *
+ */
+CiftiException::CiftiException(
+                   const AString& s)
+: std::runtime_error(AString_to_std_string(s))
+{
+    this->exceptionDescription = s;
+}
+
+/**
+ * Copy Constructor.
+ * @param e
+ *     Exception that is copied.
+ */
+CiftiException::CiftiException(const CiftiException& e)
+: std::runtime_error(e)
+{
+    this->exceptionDescription = e.exceptionDescription;
+}
+
+/**
+ * Assignment operator.
+ * @param e
+ *     Exception that is copied.
+ * @return 
+ *     Copy of the exception.
+ */
+CiftiException& 
+CiftiException::operator=(const CiftiException& e)
+{
+    if (this != &e) {
+        std::runtime_error::operator=(e);
+        this->exceptionDescription = e.exceptionDescription;
+    }
+    
+    return *this;
+}
+
+/**
+ * Destructor
+ */
+CiftiException::~CiftiException() throw()
+{
+}
+
+/**
+ * Get a message describing the exception.
+ * @return A message describing the exception.
+ */
+AString
+CiftiException::whatString() const throw() 
+{
+    return this->exceptionDescription;
+}
diff --git a/src/Common/CiftiException.h b/src/Common/CiftiException.h
new file mode 100644
index 0000000..f39a18e
--- /dev/null
+++ b/src/Common/CiftiException.h
@@ -0,0 +1,62 @@
+#ifndef __CIFTI_EXCEPTION_H__
+#define __CIFTI_EXCEPTION_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "AString.h"
+
+#include <stdexcept>
+
+namespace cifti {
+
+/**
+ * An exception thrown from CiftiLib.
+ */
+    class CiftiException : public std::runtime_error {
+
+public:
+    CiftiException();
+
+    CiftiException(const AString& s);
+
+    CiftiException(const CiftiException& e);
+        
+    CiftiException& operator=(const CiftiException& e);
+        
+    virtual ~CiftiException() throw();
+    
+    virtual AString whatString() const throw();
+
+private:
+    /// Description of the exception
+    AString exceptionDescription;
+};
+
+} // namespace
+
+#endif // __CIFTI_EXCEPTION_H__
diff --git a/src/Common/CiftiMutex.h b/src/Common/CiftiMutex.h
new file mode 100644
index 0000000..5a6e357
--- /dev/null
+++ b/src/Common/CiftiMutex.h
@@ -0,0 +1,105 @@
+#ifndef __CIFTI_MUTEX_H__
+#define __CIFTI_MUTEX_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2016, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef _OPENMP
+#define __CIFTI_MUTEX_H_HAVE_IMPL__
+
+#include "omp.h"
+
+namespace cifti
+{
+    class CiftiMutex
+    {
+        omp_lock_t m_lock;
+    public:
+        CiftiMutex(const CiftiMutex&) { omp_init_lock(&m_lock); };//allow copy, assign, but make them do nothing other than default construct
+        CiftiMutex& operator=(const CiftiMutex&) { return *this; };
+        CiftiMutex() { omp_init_lock(&m_lock); }
+        ~CiftiMutex() { omp_destroy_lock(&m_lock); }
+        friend class CiftiMutexLocker;
+    };
+    
+    class CiftiMutexLocker
+    {
+        CiftiMutex* m_mutex;
+        CiftiMutexLocker();//disallow default construction, assign
+        CiftiMutexLocker& operator=(const CiftiMutexLocker& rhs);
+    public:
+        CiftiMutexLocker(CiftiMutex* mutex) { m_mutex = mutex; omp_set_lock(&(m_mutex->m_lock)); }
+        ~CiftiMutexLocker() { omp_unset_lock(&(m_mutex->m_lock)); }
+    };
+}
+
+#else //_OPENMP
+
+#ifdef CIFTILIB_USE_QT
+#define __CIFTI_MUTEX_H_HAVE_IMPL__
+
+#include <QMutex>
+
+namespace cifti
+{
+    typedef QMutex CiftiMutex;
+    typedef QMutexLocker CiftiMutexLocker;
+}
+
+#endif //CIFTILIB_USE_QT
+
+#ifdef CIFTILIB_USE_XMLPP
+#define __CIFTI_MUTEX_H_HAVE_IMPL__
+
+#include <glibmm/thread.h>
+
+namespace cifti
+{
+    typedef Glib::Mutex CiftiMutex;
+    
+    //API difference: glib's locker class takes a reference, while QT's takes a pointer
+    class CiftiMutexLocker
+    {
+        CiftiMutex* m_mutex;
+        CiftiMutexLocker();//disallow default construction, assign
+        CiftiMutexLocker& operator=(const CiftiMutexLocker& rhs);
+    public:
+        CiftiMutexLocker(CiftiMutex* mutex) { m_mutex = mutex; m_mutex->lock(); }
+        ~CiftiMutexLocker() { m_mutex->unlock(); }
+    };
+}
+
+#endif //CIFTILIB_USE_XMLPP
+
+#endif //_OPENMP
+
+
+#ifndef __CIFTI_MUTEX_H_HAVE_IMPL__
+#error "you must have openmp support, or define either CIFTILIB_USE_QT or CIFTILIB_USE_XMLPP to select what mutex implementation to use"
+#endif
+
+#endif //__CIFTI_MUTEX_H__
diff --git a/src/Common/Compact3DLookup.h b/src/Common/Compact3DLookup.h
new file mode 100644
index 0000000..2dfee63
--- /dev/null
+++ b/src/Common/Compact3DLookup.h
@@ -0,0 +1,101 @@
+#ifndef __COMPACT_3D_LOOKUP_H__
+#define __COMPACT_3D_LOOKUP_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CompactLookup.h"
+
+namespace cifti
+{
+    
+    template <typename T>
+    class Compact3DLookup
+    {
+        CompactLookup<CompactLookup<CompactLookup<T> > > m_lookup;//the whole point of this class is to deal with this ugliness
+    public:
+        ///creates the element if it didn't exist, and returns a reference to it
+        T& at(const int64_t& index1, const int64_t& index2, const int64_t& index3);
+        ///creates the element if it didn't exist, and returns a reference to it
+        T& at(const int64_t index[3]) { return at(index[0], index[1], index[2]); }
+        ///add or overwrite an element in the lookup
+        void insert(const int64_t& index1, const int64_t& index2, const int64_t& index3, const T& value)
+        { at(index1, index2, index3) = value; }
+        ///add or overwrite an element in the lookup
+        void insert(const int64_t index[3], const T& value)
+        { at(index) = value; }
+        ///returns a pointer to the desired element, or NULL if no such element is found
+        T* find(const int64_t& index1, const int64_t& index2, const int64_t& index3);
+        ///returns a pointer to the desired element, or NULL if no such element is found
+        T* find(const int64_t index[3]) { return find(index[0], index[1], index[2]); }
+        ///returns a pointer to the desired element, or NULL if no such element is found
+        const T* find(const int64_t& index1, const int64_t& index2, const int64_t& index3) const;
+        ///returns a pointer to the desired element, or NULL if no such element is found
+        const T* find(const int64_t index[3]) const { return find(index[0], index[1], index[2]); }
+        ///empties the lookup
+        void clear();
+    };
+    
+    template<typename T>
+    T& Compact3DLookup<T>::at(const int64_t& index1, const int64_t& index2, const int64_t& index3)
+    {
+        return m_lookup[index3][index2][index1];//a lot of complexity is hidden in those operator[]s
+    }
+
+    template<typename T>
+    T* Compact3DLookup<T>::find(const int64_t& index1, const int64_t& index2, const int64_t& index3)
+    {
+        typename CompactLookup<CompactLookup<CompactLookup<T> > >::iterator iter1 = m_lookup.find(index3);//oh the humanity
+        if (iter1 == m_lookup.end()) return NULL;
+        typename CompactLookup<CompactLookup<T> >::iterator iter2 = iter1->find(index2);
+        if (iter2 == iter1->end()) return NULL;
+        typename CompactLookup<T>::iterator iter3 = iter2->find(index1);
+        if (iter3 == iter2->end()) return NULL;
+        return &(*iter3);
+    }
+
+    template <typename T>
+    const T* Compact3DLookup<T>::find(const int64_t& index1, const int64_t& index2, const int64_t& index3) const
+    {
+        typename CompactLookup<CompactLookup<CompactLookup<T> > >::const_iterator iter1 = m_lookup.find(index3);
+        if (iter1 == m_lookup.end()) return NULL;
+        typename CompactLookup<CompactLookup<T> >::const_iterator iter2 = iter1->find(index2);
+        if (iter2 == iter1->end()) return NULL;
+        typename CompactLookup<T>::const_iterator iter3 = iter2->find(index1);
+        if (iter3 == iter2->end()) return NULL;
+        return &(*iter3);
+    }
+    
+    template <typename T>
+    void Compact3DLookup<T>::clear()
+    {
+        m_lookup.clear();
+    }
+
+}
+
+#endif //__COMPACT_3D_LOOKUP_H__
diff --git a/src/Common/CompactLookup.h b/src/Common/CompactLookup.h
new file mode 100644
index 0000000..7373558
--- /dev/null
+++ b/src/Common/CompactLookup.h
@@ -0,0 +1,237 @@
+#ifndef __COMPACT_LOOKUP_H__
+#define __COMPACT_LOOKUP_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiAssert.h"
+#include "stdint.h"
+#include <vector>
+
+namespace cifti
+{
+    
+    template <typename T>
+    class CompactLookup
+    {
+        struct Chunk
+        {
+            int64_t start;
+            std::vector<T> elements;
+        };
+        std::vector<Chunk> m_chunks;
+    public:
+        class iterator
+        {
+            CompactLookup<T>& m_container;
+            std::size_t m_chunk;
+            int64_t m_elem;
+            iterator(CompactLookup<T>& container, std::size_t chunk, int64_t elem) : m_container(container), m_chunk(chunk), m_elem(elem) { }
+        public:
+            bool operator==(const iterator& rhs) const
+            {
+                if (&m_container != &(rhs.m_container)) return false;
+                if (m_chunk != rhs.m_chunk) return false;
+                if (m_elem != rhs.m_elem) return false;
+                return true;
+            }
+            T& operator*()
+            {
+                CiftiAssert(m_chunk < m_container.m_chunks.size());
+                CiftiAssert(m_elem >= 0 && m_elem < (int64_t)m_container.m_chunks[m_chunk].elements.size());
+                return m_container.m_chunks[m_chunk].elements[m_elem];
+            }
+            T* operator->()
+            {
+                CiftiAssert(m_chunk < m_container.m_chunks.size());
+                CiftiAssert(m_elem >= 0 && m_elem < (int64_t)m_container.m_chunks[m_chunk].elements.size());
+                return &(m_container.m_chunks[m_chunk].elements[m_elem]);
+            }
+            friend class CompactLookup<T>;
+        };
+        class const_iterator
+        {
+            const CompactLookup<T>& m_container;
+            std::size_t m_chunk;
+            int64_t m_elem;
+            const_iterator(const CompactLookup<T>& container, std::size_t chunk, std::size_t elem) : m_container(container), m_chunk(chunk), m_elem(elem) { }
+        public:
+            bool operator==(const const_iterator& rhs) const
+            {
+                if (&m_container != &(rhs.m_container)) return false;
+                if (m_chunk != rhs.m_chunk) return false;
+                if (m_elem != rhs.m_elem) return false;
+                return true;
+            }
+            const T& operator*()
+            {
+                CiftiAssert(m_chunk < m_container.m_chunks.size());
+                CiftiAssert(m_elem >= 0 && m_elem < (int64_t)m_container.m_chunks[m_chunk].elements.size());
+                return m_container.m_chunks[m_chunk].elements[m_elem];
+            }
+            const T* operator->()
+            {
+                CiftiAssert(m_chunk < m_container.m_chunks.size());
+                CiftiAssert(m_elem >= 0 && m_elem < (int64_t)m_container.m_chunks[m_chunk].elements.size());
+                return &(m_container.m_chunks[m_chunk].elements[m_elem]);
+            }
+            friend class CompactLookup<T>;
+        };
+        ///creates the element if it didn't exist, and returns a reference to it
+        T& operator[](const int64_t& index);
+        ///returns an iterator pointing to the desired element, or one equal to end() if no such element is found
+        iterator find(const int64_t& index);
+        ///returns a const_iterator pointing to the desired element, or one equal to end() if no such element is found
+        const_iterator find(const int64_t& index) const;
+        iterator end();
+        const_iterator end() const;
+        ///empties the lookup
+        void clear();
+    };
+    
+    template <typename T>
+    T& CompactLookup<T>::operator[](const int64_t& index)
+    {
+        std::size_t numChunks = m_chunks.size();
+        std::size_t low = 0, high = numChunks, guess;//NOTE: low is 0 because size_t is unsigned, really means -1
+        bool attach_low = false, attach_high = false;
+        while (low < high)//bisection search for the chunk with the largest start value not greater than
+        {
+            guess = (low + high - 1) / 2;//which is why we subtract 1 here
+            CiftiAssert(guess < m_chunks.size());
+            if (m_chunks[guess].start > index)
+            {
+                high = guess;
+            } else {
+                low = guess + 1;
+            }
+        }//NOTE: low == high after loop ends
+        if (high > 0 && m_chunks[high - 1].start + (int64_t)(m_chunks[high - 1].elements.size()) > index)//element exists, return it
+        {
+            CiftiAssertVectorIndex(m_chunks[high -1].elements, index - m_chunks[high - 1].start);
+            return m_chunks[high - 1].elements[index - m_chunks[high - 1].start];
+        }
+        if (high > 0 && m_chunks[high - 1].start + (int64_t)(m_chunks[high - 1].elements.size()) == index) attach_low = true;//index is 1 beyond the range
+        if (high < numChunks && m_chunks[high].start == index + 1) attach_high = true;//index is 1 before the next range
+        if (attach_low)
+        {
+            std::vector<T>& lowvec = m_chunks[high - 1].elements;
+            std::size_t retIndex = lowvec.size();
+            lowvec.push_back(T());
+            if (attach_high)
+            {
+                std::vector<T>& highvec = m_chunks[high].elements;
+                lowvec.insert(lowvec.end(), highvec.begin(), highvec.end());
+                m_chunks.erase(m_chunks.begin() + high);
+            }
+            return lowvec[retIndex];
+        } else {
+            if (attach_high)
+            {
+                std::vector<T>& highvec = m_chunks[high].elements;
+                highvec.insert(highvec.begin(), T());//add a new element to the start of the vector (yes, this could be slow)
+                m_chunks[high].start = index;//and change the start value of the chunk
+                return highvec[0];
+            } else {
+                m_chunks.insert(m_chunks.begin() + high, Chunk());
+                m_chunks[high].start = index;
+                m_chunks[high].elements.push_back(T());
+                return m_chunks[high].elements[0];
+            }
+        }
+    }
+
+    template <typename T>
+    typename CompactLookup<T>::iterator CompactLookup<T>::find(const int64_t& index)
+    {
+        std::size_t numChunks = m_chunks.size();
+        std::size_t low = 0, high = numChunks, guess;//NOTE: low is 0 because size_t is unsigned, really means -1
+        while (low < high)//bisection search for the chunk with the largest start value not greater than
+        {
+            guess = (low + high - 1) / 2;//which is why we subtract 1 here
+            CiftiAssert(guess < m_chunks.size());
+            if (m_chunks[guess].start > index)
+            {
+                high = guess;
+            } else {
+                low = guess + 1;
+            }
+        }//NOTE: low == high after loop ends
+        if (high > 0 && m_chunks[high - 1].start + (int64_t)(m_chunks[high - 1].elements.size()) > index)//element exists, return it
+        {
+            std::size_t outIndex = index - m_chunks[high - 1].start;
+            CiftiAssert(outIndex < m_chunks[high - 1].elements.size());
+            return iterator(*this, high - 1, outIndex);
+        }
+        return end();
+    }
+
+    template <typename T>
+    typename CompactLookup<T>::const_iterator CompactLookup<T>::find(const int64_t& index) const
+    {
+        std::size_t numChunks = m_chunks.size();
+        std::size_t low = 0, high = numChunks, guess;//NOTE: low is 0 because size_t is unsigned, really means -1
+        while (low < high)//bisection search for the chunk with the largest start value not greater than
+        {
+            guess = (low + high - 1) / 2;//which is why we subtract 1 here
+            CiftiAssert(guess < m_chunks.size());
+            if (m_chunks[guess].start > index)
+            {
+                high = guess;
+            } else {
+                low = guess + 1;
+            }
+        }//NOTE: low == high after loop ends
+        if (high > 0 && m_chunks[high - 1].start + (int64_t)(m_chunks[high - 1].elements.size()) > index)//element exists, return it
+        {
+            std::size_t outIndex = index - m_chunks[high - 1].start;
+            CiftiAssert(outIndex < m_chunks[high - 1].elements.size());
+            return const_iterator(*this, high - 1, outIndex);
+        }
+        return end();
+    }
+    
+    template <typename T>
+    typename CompactLookup<T>::iterator CompactLookup<T>::end()
+    {
+        return iterator(*this, 0, -1);
+    }
+
+    template <typename T>
+    typename CompactLookup<T>::const_iterator CompactLookup<T>::end() const
+    {
+        return const_iterator(*this, 0, -1);
+    }
+
+    template <typename T>
+    void CompactLookup<T>::clear()
+    {
+        m_chunks.clear();
+    }
+}
+
+#endif //__COMPACT_LOOKUP_H__
diff --git a/src/Common/FloatMatrix.cxx b/src/Common/FloatMatrix.cxx
new file mode 100644
index 0000000..ab91262
--- /dev/null
+++ b/src/Common/FloatMatrix.cxx
@@ -0,0 +1,338 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiAssert.h"
+#include "CiftiException.h"
+#include "FloatMatrix.h"
+#include "MatrixFunctions.h"
+
+using namespace cifti;
+using namespace std;
+
+bool FloatMatrix::checkDimensions() const
+{
+   uint64_t rows = m_matrix.size(), cols;
+   if (rows == 0) return true;//treat it as fine for now
+   cols = m_matrix[0].size();
+   for (uint64_t i = 1; i < rows; ++i)
+   {
+      if (m_matrix[i].size() != cols)
+      {
+         return false;
+      }
+   }
+   return true;
+}
+
+FloatMatrix::FloatMatrix(const vector<vector<float> >& matrixIn)
+{
+   m_matrix = matrixIn;
+   CiftiAssert(checkDimensions());
+}
+
+FloatMatrix::FloatMatrix(const int64_t& rows, const int64_t& cols)
+{
+    resize(rows, cols, true);
+}
+
+bool FloatMatrix::operator!=(const FloatMatrix& right) const
+{
+   return !(*this == right);
+}
+
+FloatMatrix FloatMatrix::operator*(const FloatMatrix& right) const
+{
+   FloatMatrix ret;
+   MatrixFunctions::multiply<float, float, float, double>(m_matrix, right.m_matrix, ret.m_matrix);
+   return ret;
+}
+
+FloatMatrix& FloatMatrix::operator*=(const FloatMatrix& right)
+{
+   MatrixFunctions::multiply<float, float, float, double>(m_matrix, right.m_matrix, m_matrix);//would need a copy anyway, so let it make the copy internally
+   return *this;
+}
+
+FloatMatrix FloatMatrix::concatHoriz(const FloatMatrix& right) const
+{
+   FloatMatrix ret;
+   MatrixFunctions::horizCat(m_matrix, right.m_matrix, ret.m_matrix);
+   return ret;
+}
+
+FloatMatrix FloatMatrix::concatVert(const FloatMatrix& bottom) const
+{
+   FloatMatrix ret;
+   MatrixFunctions::vertCat(m_matrix, bottom.m_matrix, ret.m_matrix);
+   return ret;
+}
+
+FloatMatrix FloatMatrix::getRange(const int64_t firstRow, const int64_t afterLastRow, const int64_t firstCol, const int64_t afterLastCol) const
+{
+   FloatMatrix ret;
+   MatrixFunctions::getChunk(firstRow, afterLastRow, firstCol, afterLastCol, m_matrix, ret.m_matrix);
+   return ret;
+}
+
+FloatMatrix FloatMatrix::identity(const int64_t rows)
+{
+   FloatMatrix ret;
+   MatrixFunctions::identity(rows, ret.m_matrix);
+   return ret;
+}
+
+FloatMatrix FloatMatrix::inverse() const
+{
+   FloatMatrix ret;
+   MatrixFunctions::inverse(m_matrix, ret.m_matrix);
+   return ret;
+}
+
+FloatMatrix& FloatMatrix::operator*=(const float& right)
+{
+   MatrixFunctions::multiply(m_matrix, right, m_matrix);//internally makes a copy
+   return *this;
+}
+
+FloatMatrix FloatMatrix::operator+(const FloatMatrix& right) const
+{
+   FloatMatrix ret;
+   MatrixFunctions::add(m_matrix, right.m_matrix, ret.m_matrix);
+   return ret;
+}
+
+FloatMatrix& FloatMatrix::operator+=(const FloatMatrix& right)
+{
+   MatrixFunctions::add(m_matrix, right.m_matrix, m_matrix);
+   return *this;
+}
+
+FloatMatrix& FloatMatrix::operator+=(const float& right)
+{
+   MatrixFunctions::add(m_matrix, right, m_matrix);
+   return *this;
+}
+
+FloatMatrix FloatMatrix::operator-(const FloatMatrix& right) const
+{
+   FloatMatrix ret;
+   MatrixFunctions::subtract(m_matrix, right.m_matrix, ret.m_matrix);
+   return ret;
+}
+
+FloatMatrix& FloatMatrix::operator-=(const FloatMatrix& right)
+{
+   MatrixFunctions::subtract(m_matrix, right.m_matrix, m_matrix);
+   return *this;
+}
+
+FloatMatrix& FloatMatrix::operator-=(const float& right)
+{
+   MatrixFunctions::add(m_matrix, (-right), m_matrix);
+   return *this;
+}
+
+FloatMatrix& FloatMatrix::operator/=(const float& right)
+{
+   return ((*this) *= 1.0f / right);
+}
+
+bool FloatMatrix::operator==(const FloatMatrix& right) const
+{
+   if (this == &right)
+   {
+      return true;//short circuit true on pointer equivalence
+   }
+   int64_t i, j, rows = (int64_t)m_matrix.size(), cols;
+   if (rows != (int64_t)right.m_matrix.size())
+   {
+      return false;
+   }
+   if (rows == 0)
+   {
+      return true;//don't try to get the second dimension
+   }
+   cols = (int64_t)m_matrix[0].size();
+   if (cols != (int64_t)right.m_matrix[0].size())
+   {
+      return false;
+   }
+   for (i = 0; i < rows; ++i)
+   {
+      for (j = 0; j < cols; ++j)
+      {
+         if (m_matrix[i][j] != right.m_matrix[i][j])
+         {
+            return false;
+         }
+      }
+   }
+   return true;
+}
+
+void FloatMatrix::getDimensions(int64_t& rows, int64_t& cols) const
+{
+   rows = (int64_t)m_matrix.size();
+   if (rows == 0)
+   {
+      cols = 0;
+   } else {
+      cols = (int64_t)m_matrix[0].size();
+   }
+}
+
+FloatMatrixRowRef FloatMatrix::operator[](const int64_t& index)
+{
+   CiftiAssert(index > -1 && index < (int64_t)m_matrix.size());
+   FloatMatrixRowRef ret(m_matrix[index]);
+   return ret;
+}
+
+ConstFloatMatrixRowRef FloatMatrix::operator[](const int64_t& index) const
+{
+   CiftiAssert(index > -1 && index < (int64_t)m_matrix.size());
+   ConstFloatMatrixRowRef ret(m_matrix[index]);
+   return ret;
+}
+
+FloatMatrix FloatMatrix::reducedRowEchelon() const
+{
+   FloatMatrix ret(*this);
+   MatrixFunctions::rref(ret.m_matrix);
+   return ret;
+}
+
+void FloatMatrix::resize(const int64_t rows, const int64_t cols, const bool destructive)
+{
+   MatrixFunctions::resize(rows, cols, m_matrix, destructive);
+}
+
+FloatMatrix FloatMatrix::transpose() const
+{
+   FloatMatrix ret;
+   MatrixFunctions::transpose(m_matrix, ret.m_matrix);
+   return ret;
+}
+
+FloatMatrix FloatMatrix::zeros(const int64_t rows, const int64_t cols)
+{
+   FloatMatrix ret;
+   MatrixFunctions::zeros(rows, cols, ret.m_matrix);
+   return ret;
+}
+
+FloatMatrix FloatMatrix::ones(const int64_t rows, const int64_t cols)
+{
+   FloatMatrix ret;
+   MatrixFunctions::ones(rows, cols, ret.m_matrix);
+   return ret;
+}
+
+const vector<vector<float> >& FloatMatrix::getMatrix() const
+{
+   return m_matrix;
+}
+
+void FloatMatrix::getAffineVectors(Vector3D& xvec, Vector3D& yvec, Vector3D& zvec, Vector3D& offset) const
+{
+    if (m_matrix.size() < 3 || m_matrix.size() > 4 || m_matrix[0].size() != 4)
+    {
+        throw CiftiException("getAffineVectors called on incorrectly sized matrix");
+    }
+    xvec[0] = m_matrix[0][0]; xvec[1] = m_matrix[1][0]; xvec[2] = m_matrix[2][0];
+    yvec[0] = m_matrix[0][1]; yvec[1] = m_matrix[1][1]; yvec[2] = m_matrix[2][1];
+    zvec[0] = m_matrix[0][2]; zvec[1] = m_matrix[1][2]; zvec[2] = m_matrix[2][2];
+    offset[0] = m_matrix[0][3]; offset[1] = m_matrix[1][3]; offset[2] = m_matrix[2][3];
+}
+
+FloatMatrix FloatMatrix::operator-() const
+{
+   int64_t rows, cols;
+   getDimensions(rows, cols);
+   FloatMatrix ret = zeros(rows, cols);
+   ret -= *this;
+   return ret;
+}
+
+FloatMatrixRowRef::FloatMatrixRowRef(vector<float>& therow) : m_row(therow)
+{
+}
+
+FloatMatrixRowRef& FloatMatrixRowRef::operator=(const FloatMatrixRowRef& right)
+{
+   if (&m_row == &(right.m_row))
+   {//just in case vector isn't smart enough to check self assignment
+      return *this;
+   }
+   CiftiAssert(m_row.size() == right.m_row.size());//maybe this should be an exception, not an assertion?
+   m_row = right.m_row;
+   return *this;
+}
+
+FloatMatrixRowRef& FloatMatrixRowRef::operator=(const float& right)
+{
+   for (int64_t i = 0; i < (int64_t)m_row.size(); ++i)
+   {
+      m_row[i] = right;
+   }
+   return *this;
+}
+
+float& FloatMatrixRowRef::operator[](const int64_t& index)
+{
+   CiftiAssert(index > -1 && index < (int64_t)m_row.size());//instead of segfaulting, explicitly check in debug
+   return m_row[index];
+}
+
+FloatMatrixRowRef::FloatMatrixRowRef(FloatMatrixRowRef& right) : m_row(right.m_row)
+{
+}
+
+FloatMatrixRowRef& FloatMatrixRowRef::operator=(const ConstFloatMatrixRowRef& right)
+{
+   if (&m_row == &(right.m_row))
+   {//just in case vector isn't smart enough to check self assignment
+      return *this;
+   }
+   CiftiAssert(m_row.size() == right.m_row.size());
+   m_row = right.m_row;
+   return *this;
+}
+
+const float& ConstFloatMatrixRowRef::operator[](const int64_t& index)
+{
+   CiftiAssert(index > -1 && index < (int64_t)m_row.size());//instead of segfaulting, explicitly check in debug
+   return m_row[index];
+}
+
+ConstFloatMatrixRowRef::ConstFloatMatrixRowRef(const ConstFloatMatrixRowRef& right) : m_row(right.m_row)
+{
+}
+
+ConstFloatMatrixRowRef::ConstFloatMatrixRowRef(const vector<float>& therow) : m_row(therow)
+{
+}
diff --git a/src/Common/FloatMatrix.h b/src/Common/FloatMatrix.h
new file mode 100644
index 0000000..ab9cd47
--- /dev/null
+++ b/src/Common/FloatMatrix.h
@@ -0,0 +1,126 @@
+#ifndef __FLOAT_MATRIX_H__
+#define __FLOAT_MATRIX_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <vector>
+#include "stdint.h"
+#include "Vector3D.h"
+
+namespace cifti {
+
+   class ConstFloatMatrixRowRef
+   {//needed to do [][] on a const FloatMatrix
+      const std::vector<float>& m_row;
+      ConstFloatMatrixRowRef();//disallow default construction, this contains a reference
+   public:
+      ConstFloatMatrixRowRef(const ConstFloatMatrixRowRef& right);//copy constructor
+      ConstFloatMatrixRowRef(const std::vector<float>& therow);
+      const float& operator[](const int64_t& index);//access element
+      friend class FloatMatrixRowRef;//so it can check if it points to the same row
+   };
+
+   class FloatMatrixRowRef
+   {//needed to ensure some joker doesn't call mymatrix[1].resize();, while still allowing mymatrix[1][2] = 5; and mymatrix[1] = mymatrix[2];
+      std::vector<float>& m_row;
+      FloatMatrixRowRef();//disallow default construction, this contains a reference
+   public:
+      FloatMatrixRowRef(FloatMatrixRowRef& right);//copy constructor
+      FloatMatrixRowRef(std::vector<float>& therow);
+      FloatMatrixRowRef& operator=(const FloatMatrixRowRef& right);//NOTE: copy row contents!
+      FloatMatrixRowRef& operator=(const ConstFloatMatrixRowRef& right);//NOTE: copy row contents!
+      FloatMatrixRowRef& operator=(const float& right);//NOTE: set all row values!
+      float& operator[](const int64_t& index);//access element
+   };
+
+   ///class for using single precision matrices (insulates other code from the MatrixFunctions templated header)
+   ///errors will result in a matrix of size 0x0, or an assertion failure if the underlying vector<vector> isn't rectangular
+   class FloatMatrix
+   {
+      std::vector<std::vector<float> > m_matrix;
+      bool checkDimensions() const;//put this inside asserts at the end of functions
+   public:
+      FloatMatrix() { };//to make the compiler happy
+      ///construct from a simple vector<vector<float> >
+      FloatMatrix(const std::vector<std::vector<float> >& matrixIn);
+      ///construct uninitialized with given size
+      FloatMatrix(const int64_t& rows, const int64_t& cols);
+      FloatMatrixRowRef operator[](const int64_t& index);//allow direct indexing to rows
+      ConstFloatMatrixRowRef operator[](const int64_t& index) const;//allow direct indexing to rows while const
+      FloatMatrix& operator+=(const FloatMatrix& right);//add to
+      FloatMatrix& operator-=(const FloatMatrix& right);//subtract from
+      FloatMatrix& operator*=(const FloatMatrix& right);//multiply by
+      FloatMatrix& operator+=(const float& right);//add scalar to
+      FloatMatrix& operator-=(const float& right);//subtract scalar from
+      FloatMatrix& operator*=(const float& right);//multiply by scalar
+      FloatMatrix& operator/=(const float& right);//divide by scalar
+      FloatMatrix operator+(const FloatMatrix& right) const;//add
+      FloatMatrix operator-(const FloatMatrix& right) const;//subtract
+      FloatMatrix operator-() const;//negate
+      FloatMatrix operator*(const FloatMatrix& right) const;//multiply
+      bool operator==(const FloatMatrix& right) const;//compare
+      bool operator!=(const FloatMatrix& right) const;//anti-compare
+      ///return the inverse
+      FloatMatrix inverse() const;
+      ///return the reduced row echelon form
+      FloatMatrix reducedRowEchelon() const;
+      ///return the transpose
+      FloatMatrix transpose() const;
+      ///resize the matrix - keeps contents within bounds unless destructive is true (destructive is faster)
+      void resize(const int64_t rows, const int64_t cols, const bool destructive = false);
+      ///return a matrix of zeros
+      static FloatMatrix zeros(const int64_t rows, const int64_t cols);
+      ///return a matrix of ones
+      static FloatMatrix ones(const int64_t rows, const int64_t cols);
+      ///return square identity matrix
+      static FloatMatrix identity(const int64_t rows);
+      ///get the range of values from first until one before afterLast, as a new matrix
+      FloatMatrix getRange(const int64_t firstRow, const int64_t afterLastRow, const int64_t firstCol, const int64_t afterLastCol) const;
+      ///return a matrix formed by concatenating right to the right of this
+      FloatMatrix concatHoriz(const FloatMatrix& right) const;
+      ///returns a matrix formed by concatenating bottom to the bottom of this
+      FloatMatrix concatVert(const FloatMatrix& bottom) const;
+      ///get the dimensions
+      void getDimensions(int64_t& rows, int64_t& cols) const;
+      ///get the matrix as a vector<vector>
+      const std::vector<std::vector<float> >& getMatrix() const;
+      ///separate 3x4 or 4x4 into Vector3Ds, throw on wrong dimensions
+      void getAffineVectors(Vector3D& xvec, Vector3D& yvec, Vector3D& zvec, Vector3D& offset) const;
+      ///get number of rows
+      int64_t getNumberOfRows() { return (int64_t)m_matrix.size(); }
+      ///get number of columns
+      int64_t getNumberOfColumns()
+      {
+          if (m_matrix.size() == 0) return 0;
+          return (int64_t)m_matrix[0].size();
+      }
+   };
+
+}
+
+#endif //__FLOAT_MATRIX_H__
diff --git a/src/Common/MathFunctions.cxx b/src/Common/MathFunctions.cxx
new file mode 100644
index 0000000..9e8a352
--- /dev/null
+++ b/src/Common/MathFunctions.cxx
@@ -0,0 +1,1713 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <algorithm>
+#include <cmath>
+
+#include "MathFunctions.h"
+
+using namespace cifti;
+
+/**
+ * Calulate the number of combinations for choosing "k" elements
+ * from a total of "n" elements.
+ * 
+ * Formula: [n!/(k!*(n-k!))
+ *    If k > (n-k):  [n(n-1)(n-2)...(k+1)] / (n-k)!
+ *    If k < (n-k):  [n(n-1)(n-2)...(n-k+1)] / k!
+ * 
+ * @param n - total number of elements.
+ * @param k - number of elements to choose.
+ * @return  - number of combinations.
+ *
+ */
+int64_t
+MathFunctions::combinations(
+                   const int64_t n,
+                   const int64_t k)
+{
+    int64_t denominator = 1;
+    int64_t nmk         = n - k;
+    int64_t iStart      = 1;
+    
+    if (k > nmk) {
+        iStart = k + 1;
+        denominator = MathFunctions::factorial(nmk);
+    }
+    else {
+        iStart = nmk + 1;
+        denominator = MathFunctions::factorial(k);
+    }
+    int64_t numerator   = 1;
+    for (int64_t i = iStart; i <= n; i++) {
+        numerator *= i;
+    }
+    
+    int64_t numCombosLong = numerator / denominator;
+    int64_t numCombos = (int)numCombosLong;
+    
+    return numCombos;
+}
+
+/**
+ * Calulate the number of permuations for choosing "k" elements
+ * from a total of "n" elements.
+ * 
+ * Formula: [n!/(n-m)!] = n(n-1)(n-2)...(n-m+1).
+ * 
+ * @param n - total number of elements.
+ * @param k - number of elements to choose.
+ * @return  - number of combinations.
+ *
+ */
+int64_t
+MathFunctions::permutations(
+                   const int64_t n,
+                   const int64_t k)
+{
+    int64_t iStart = n - k + 1;
+    int numPerms = 1;
+    for (int i = iStart; i <= n; i++) {
+        numPerms *= i;
+    }
+    
+    return numPerms;
+}
+
+/**
+ * Calculate the factorial for a number.
+ * 
+ * @param n - the number.
+ * @return  - its factiorial
+ *
+ */
+int64_t
+MathFunctions::factorial(const int64_t n)
+{
+    int64_t num = 1;
+    
+    for (int64_t i = 1; i <= n; i++) {
+        num *= i;
+    }
+    
+    return num;
+}
+
+/**
+ * Compute a normal vector from three vertices and make it a unit vector.
+ *
+ * @param  v1  the first vertex, an array of three floats.
+ * @param  v2  the first vertex, an array of three floats.
+ * @param  v3  the first vertex, an array of three floats.
+ * @param normalVectorOut A three-dimensional array passed into which
+ * the normal vector is loaded.
+ * @return true if vector is valid (non-zero length).
+ *
+ */
+bool
+MathFunctions::normalVector(
+                   const float v1[3],
+                   const float v2[3],
+                   const float v3[3],
+                   float normalVectorOut[3])
+{
+    /*
+    * DOUBLE PRECISION is needed when points are a small or sliver triangle.
+    */
+    double a0 = v3[0] - v2[0];
+    double a1 = v3[1] - v2[1];
+    double a2 = v3[2] - v2[2];
+    
+    double b0 = v1[0] - v2[0];
+    double b1 = v1[1] - v2[1];
+    double b2 = v1[2] - v2[2];
+    
+    double nv0 = (a1 * b2 - a2 * b1);
+    double nv1 = (a2 * b0 - a0 * b2);
+    double nv2 = (a0 * b1 - a1 * b0);
+    
+    double length = std::sqrt(nv0*nv0 + nv1*nv1 + nv2*nv2);
+    bool valid = false;
+    if (length != 0.0) {
+        nv0 /= length;
+        nv1 /= length;
+        nv2 /= length;
+        valid = true;
+    }
+    
+    normalVectorOut[0] = (float)nv0;
+    normalVectorOut[1] = (float)nv1;
+    normalVectorOut[2] = (float)nv2;
+    
+    return valid;
+}
+
+/**
+ * Compute a normal vector from three vertices and make it a unit vector.
+ *
+ * @param  v1  the first vertex, an array of three floats.
+ * @param  v2  the first vertex, an array of three floats.
+ * @param  v3  the first vertex, an array of three floats.
+ * @param normalVectorOut A three-dimensional array passed into which
+ * the normal vector is loaded.
+ * @return true if vector is valid (non-zero length).
+ *
+ */
+bool
+MathFunctions::normalVector(
+                   const double v1[3],
+                   const double v2[3],
+                   const double v3[3],
+                   double normalVectorOut[3])
+{
+    double a0 = v3[0] - v2[0];
+    double a1 = v3[1] - v2[1];
+    double a2 = v3[2] - v2[2];
+    
+    double b0 = v1[0] - v2[0];
+    double b1 = v1[1] - v2[1];
+    double b2 = v1[2] - v2[2];
+    
+    double nv0 = (a1 * b2 - a2 * b1);
+    double nv1 = (a2 * b0 - a0 * b2);
+    double nv2 = (a0 * b1 - a1 * b0);
+    
+    double length = std::sqrt(nv0*nv0 + nv1*nv1 + nv2*nv2);
+    bool valid = false;
+    if (length != 0.0) {
+        nv0 /= length;
+        nv1 /= length;
+        nv2 /= length;
+        valid = true;
+    }
+    
+    normalVectorOut[0] = nv0;
+    normalVectorOut[1] = nv1;
+    normalVectorOut[2] = nv2;
+    
+    return valid;
+}
+
+/**
+ * Compute a normal vector from three vertices and but the returned
+ * vector IS NOT a unit vector.
+ *
+ * @param  v1  the first vertex, an array of three floats.
+ * @param  v2  the first vertex, an array of three floats.
+ * @param  v3  the first vertex, an array of three floats.
+ * @return     The normal vector, an array of three floats.
+ *
+ */
+void
+MathFunctions::normalVectorDirection(
+                   const float v1[3],
+                   const float v2[3],
+                   const float v3[3],
+                   float directionOut[3])
+{
+    float a[] = {
+        v3[0] - v2[0],
+        v3[1] - v2[1],
+        v3[2] - v2[2]
+    };
+    float b[] = {
+        v1[0] - v2[0],
+        v1[1] - v2[1],
+        v1[2] - v2[2]
+    };
+    
+    directionOut[0] = (a[1] * b[2] - a[2] * b[1]);
+    directionOut[1] = (a[2] * b[0] - a[0] * b[2]);
+    directionOut[2] = (a[0] * b[1] - a[1] * b[0]);
+}
+
+/**
+ * Cross product of two 3D vectors.
+ * @param  v1  The first vector, an array of three floats.
+ * @param  v2  The first vector, an array of three floats.
+ * @param resultOut  Output containing the cross product.
+ *
+ */
+void
+MathFunctions::crossProduct(
+                   const float v1[],
+                   const float v2[],
+                   float resultOut[])
+{
+    resultOut[0] = v1[1] * v2[2] - v1[2] * v2[1];
+    resultOut[1] = v1[2] * v2[0] - v1[0] * v2[2];
+    resultOut[2] = v1[0] * v2[1] - v1[1] * v2[0];
+}
+
+/**
+ * Cross product of two 3D vectors.
+ * @param  v1  The first vector, an array of three floats.
+ * @param  v2  The first vector, an array of three floats.
+ * @param resultOut  Output containing the cross product.
+ *
+ */
+void
+MathFunctions::crossProduct(
+                            const double v1[],
+                            const double v2[],
+                            double resultOut[])
+{
+    resultOut[0] = v1[1] * v2[2] - v1[2] * v2[1];
+    resultOut[1] = v1[2] * v2[0] - v1[0] * v2[2];
+    resultOut[2] = v1[0] * v2[1] - v1[1] * v2[0];
+}
+
+/**
+ * Cross product of two 3D vectors with normalizing both the
+ * input and output vectors.
+ *
+ * @param  x1  The first vector, an array of three floats.
+ * @param  x2  The first vector, an array of three floats.
+ * @return     The cross product, an array of three floats.
+ *
+ */
+void
+MathFunctions::normalizedCrossProduct(
+                   const float x1[],
+                                      const float x2[],
+                                      float resultOut[])
+{
+    float v1[3] = { x1[0], x1[1], x1[2] };
+    MathFunctions::normalizeVector(v1);
+    float v2[3] = { x2[0], x2[1], x2[2] };
+    MathFunctions::normalizeVector(v2);
+    
+    MathFunctions::crossProduct(v1, v2, resultOut);
+    MathFunctions::normalizeVector(resultOut);
+}
+
+/**
+ * Normalize a 3D vector (make its length 1.0).
+ * 
+ * @param  vectorsAll  Array containing the XYZ components
+ *    of the vectors.
+ * @param offset Offset of the vector's X-component in the
+ *    vectorsAll array.
+ * @return The length of the vector prior to normalization.
+ *
+ */
+float
+MathFunctions::normalizeVector(
+                   float vectorsAll[],
+                   const int32_t offset)
+{
+    float len = MathFunctions::vectorLength(vectorsAll, offset);
+    if (len != 0.0) {
+        vectorsAll[offset] /= len;
+        vectorsAll[offset+1] /= len;
+        vectorsAll[offset+2] /= len;
+    }
+    return len;
+}
+
+/**
+ * Normalize a 3D vector (make its length 1.0).
+ * 
+ * @param  vectorInOut  vector that is normalized.
+ * @return The length of the vector prior to normalization.
+ *
+ */
+float
+MathFunctions::normalizeVector(float vector[3])
+{
+    float len = vectorLength(vector);
+    if (len != 0.0) {
+        vector[0] /= len;
+        vector[1] /= len;
+        vector[2] /= len;
+    }
+    return len;
+}
+
+/**
+ * Normalize a 3D vector (make its length 1.0).
+ *
+ * @param  vectorInOut  vector that is normalized.
+ * @return The length of the vector prior to normalization.
+ *
+ */
+double
+MathFunctions::normalizeVector(double vector[3])
+{
+    double len = vectorLength(vector);
+    if (len != 0.0) {
+        vector[0] /= len;
+        vector[1] /= len;
+        vector[2] /= len;
+    }
+    return len;
+}
+
+/**
+ * Get length of vector.
+ * 
+ * @param  vector  Vector whose length is computed.
+ *
+ * @return  The length of the vector.
+ *
+ */
+float
+MathFunctions::vectorLength(const float vector[3])
+{
+    float len =
+    (float)std::sqrt(vector[0]*vector[0] +
+                     vector[1]*vector[1] +
+                     vector[2]*vector[2]);
+    return len;
+}
+
+/**
+ * Get length of vector.
+ * 
+ * @param  vectorsAll  Array containing three-dimensional vectors.
+ * @param offset   Offset of vector's X-component in vectorsAll array.
+ *
+ * @return  The length of the vector.
+ *
+ */
+float
+MathFunctions::vectorLength(
+                   const float vectorsAll[],
+                   const int32_t offset)
+{
+    float len =
+    (float)std::sqrt(vectorsAll[offset]*vectorsAll[offset] +
+                     vectorsAll[offset+1]*vectorsAll[offset+1] +
+                     vectorsAll[offset+2]*vectorsAll[offset+2]);
+    return len;
+}
+
+/**
+ * Get length of vector.
+ *
+ * @param  vector  Vector whose length is computed.
+ *
+ * @return  The length of the vector.
+ *
+ */
+double
+MathFunctions::vectorLength(const double vector[3])
+{
+    double len = std::sqrt(vector[0]*vector[0] +
+                     vector[1]*vector[1] +
+                     vector[2]*vector[2]);
+    return len;
+}
+
+/**
+ * Get the squared distance between two 3D points.
+ * 
+ * @param  p1   Point 1 (3 element array)
+ * @param  p2   Point 2 (3 element array)
+ * @return Distance squared between the two points.
+ *
+ */
+float
+MathFunctions::distanceSquared3D(
+                   const float p1[3],
+                   const float p2[3])
+{
+    float dx = p1[0] - p2[0];
+    float dy = p1[1] - p2[1];
+    float dz = p1[2] - p2[2];
+    float distSQ = dx*dx + dy*dy + dz*dz;
+    return distSQ;
+}
+
+/**
+ * Get the squared distance between two 3D coordinates.
+ * 
+ * @param xyzAll Array containing all of the XYZ coordinates.
+ * @param  offsetCoord1 Offset of the first coordinates X-coordinate.
+ * @param  offsetCoord2 Offset of the second coordinates X-coordinate.
+ * @return Distance squared between the two coordinates.
+ *
+ */
+float
+MathFunctions::distanceSquared3D(
+                   const float xyzAll[],
+                   const int32_t offsetCoord1,
+                   const int32_t offsetCoord2)
+{
+    float dx = xyzAll[offsetCoord1]   - xyzAll[offsetCoord2];
+    float dy = xyzAll[offsetCoord1+1] - xyzAll[offsetCoord2+1];
+    float dz = xyzAll[offsetCoord1+2] - xyzAll[offsetCoord2+2];
+    float distSQ = dx*dx + dy*dy + dz*dz;
+    return distSQ;
+}
+
+/**
+ * Get the distance between two 3D points.
+ * 
+ * @param  p1   Point 1 (3 element array)
+ * @param  p2   Point 2 (3 element array)
+ * @return Distance between the two points.
+ *
+ */
+float
+MathFunctions::distance3D(
+                   const float p1[3],
+                   const float p2[3])
+{
+    float dist = distanceSquared3D(p1, p2);
+    if (dist != 0.0f) {
+        dist = (float)std::sqrt(dist);
+    }
+    return dist;
+}
+
+/**
+ * Get the squared distance between two 3D points.
+ *
+ * @param  p1   Point 1 (3 element array)
+ * @param  p2   Point 2 (3 element array)
+ * @return Distance squared between the two points.
+ *
+ */
+double
+MathFunctions::distanceSquared3D(
+                   const double p1[3],
+                   const double p2[3])
+{
+    double dx = p1[0] - p2[0];
+    double dy = p1[1] - p2[1];
+    double dz = p1[2] - p2[2];
+    double distSQ = dx*dx + dy*dy + dz*dz;
+    return distSQ;
+//    double dist = distanceSquared3D(p1, p2);
+//    if (dist != 0.0f) {
+//        dist = std::sqrt(dist);
+//    }
+//    return dist;
+}
+
+/**
+ * Get the distance between two 3D points.
+ *
+ * @param  p1   Point 1 (3 element array)
+ * @param  p2   Point 2 (3 element array)
+ * @return Distance between the two points.
+ *
+ */
+double
+MathFunctions::distance3D(
+                   const double p1[3],
+                   const double p2[3])
+{
+    double dist = distanceSquared3D(p1, p2);
+    if (dist != 0.0f) {
+        dist = std::sqrt(dist);
+    }
+    return dist;
+}
+
+/**
+ * subtract vectors (3d)  result = v1 - v2.
+ * @param v1  1st vector input
+ * @param v2  2nd vector input
+ * @param resultOut output, 3D vector containing result of subtraction.
+ *
+ */
+void
+MathFunctions::subtractVectors(
+                   const float v1[3],
+                   const float v2[3],
+                               float resultOut[3])
+{
+    resultOut[0] = v1[0] - v2[0];
+    resultOut[1] = v1[1] - v2[1];
+    resultOut[2] = v1[2] - v2[2];
+}
+
+void MathFunctions::addVectors(const float v1[3], const float v2[3], float resultOut[3])
+{
+    resultOut[0] = v1[0] + v2[0];
+    resultOut[1] = v1[1] + v2[1];
+    resultOut[2] = v1[2] + v2[2];
+}
+
+/**
+ * Create the unit vector for a vector that starts at startXYZ and
+ * ends at endXYZ.
+ * 
+ * @param startXYZ - Starting position of vector.
+ * @param endXYZ - Ending position of vector.
+ * @param unitVectorOut - output, vector starting at startXYZ and pointing to endXYZ.
+ *
+ */
+void
+MathFunctions::createUnitVector(
+                   const float startXYZ[3],
+                   const float endXYZ[3],
+                                float unitVectorOut[3])
+{
+    unitVectorOut[0] = endXYZ[0] - startXYZ[0];
+    unitVectorOut[1] = endXYZ[1] - startXYZ[1];
+    unitVectorOut[2] = endXYZ[2] - startXYZ[2];
+    
+    MathFunctions::normalizeVector(unitVectorOut);
+}
+
+/**
+ * Create the unit vector for a vector that starts at startXYZ and
+ * ends at endXYZ.
+ *
+ * @param startXYZ - Starting position of vector.
+ * @param endXYZ - Ending position of vector.
+ * @param unitVectorOut - output, vector starting at startXYZ and pointing to endXYZ.
+ *
+ */
+void
+MathFunctions::createUnitVector(
+                                const double startXYZ[3],
+                                const double endXYZ[3],
+                                double unitVectorOut[3])
+{
+    unitVectorOut[0] = endXYZ[0] - startXYZ[0];
+    unitVectorOut[1] = endXYZ[1] - startXYZ[1];
+    unitVectorOut[2] = endXYZ[2] - startXYZ[2];
+    
+    MathFunctions::normalizeVector(unitVectorOut);
+}
+
+/**
+ * Dot produce of three dimensional vectors.
+ * @param p1   vector 1
+ * @param p2   vector 2
+ * @return     Dot product of the two vectors.
+ *
+ */
+float
+MathFunctions::dotProduct(
+                   const float p1[3],
+                   const float p2[3])
+{
+    float dot = p1[0]*p2[0] + p1[1]*p2[1] + p1[2]*p2[2];
+    return dot;
+}
+
+/**
+ * Dot produce of three dimensional vectors.
+ * @param p1   vector 1
+ * @param p2   vector 2
+ * @return     Dot product of the two vectors.
+ *
+ */
+double
+MathFunctions::dotProduct(
+                          const double p1[3],
+                          const double p2[3])
+{
+    return p1[0]*p2[0] + p1[1]*p2[1] + p1[2]*p2[2];
+}
+
+/**
+ * Calculate the area for a triangle.
+ * @param v1 - XYZ coordinates for vertex 1
+ * @param v2 - XYZ coordinates for vertex 2
+ * @param v3 - XYZ coordinates for vertex 3
+ * 
+ * @return Area of triangle.
+ *
+ */
+float
+MathFunctions::triangleArea(
+                   const float v1[3],
+                   const float v2[3],
+                   const float v3[3])
+{
+    /*
+     * Using doubles for the intermediate calculations
+     * produces results different from that if floats
+     * were used in the "area" equation.  I'm
+     * assuming double is more accurate (JWH).
+     */
+    double a = MathFunctions::distanceSquared3D(v1,v2);
+    double b = MathFunctions::distanceSquared3D(v2,v3);
+    double c = MathFunctions::distanceSquared3D(v3,v1);
+    float area =
+    (float)(0.25f* std::sqrt(std::abs(4.0*a*c - (a-b+c)*(a-b+c))));
+    return area;
+}
+
+/**
+ * Calculate the area for a triangle (with doubles)
+ * @param v1 - XYZ coordinates for vertex 1
+ * @param v2 - XYZ coordinates for vertex 2
+ * @param v3 - XYZ coordinates for vertex 3
+ * 
+ * @return Area of triangle.
+ *
+ */
+float
+MathFunctions::triangleArea(const double v1[3],
+                            const double v2[3],
+                            const double v3[3])
+{
+    /*
+     * Using doubles for the intermediate calculations
+     * produces results different from that if floats
+     * were used in the "area" equation.  I'm
+     * assuming double is more accurate (JWH).
+     */
+    double a = MathFunctions::distanceSquared3D(v1,v2);
+    double b = MathFunctions::distanceSquared3D(v2,v3);
+    double c = MathFunctions::distanceSquared3D(v3,v1);
+    float area =
+    (float)(0.25f* std::sqrt(std::abs(4.0*a*c - (a-b+c)*(a-b+c))));
+    return area;
+}
+
+/**
+ * Calculate the area of a triangle formed by 3 coordinates.
+ * @param xyzAll One-dimensional array containing the XYZ coordinates.
+ * @param offsetCoord1  Offset of node 1's X-coordinate which is
+ *    followed by the Y- and Z-coordinates.
+ * @param offsetCoord2  Offset of node 2's X-coordinate which is
+ *    followed by the Y- and Z-coordinates.
+ * @param offsetCoord3  Offset of node 3's X-coordinate which is
+ *    followed by the Y- and Z-coordinates.
+ * @return  Area of the triangle formed by the coordinates.
+ *
+ */
+float
+MathFunctions::triangleArea(
+                   const float xyzAll[],
+                   const int32_t offsetCoord1,
+                   const int32_t offsetCoord2,
+                   const int32_t offsetCoord3)
+{
+    /*
+     * Using doubles for the intermediate calculations
+     * produces results different from that if floats
+     * were used in the "area" equation.  I'm
+     * assuming double is more accurate (JWH).
+     */
+    double a = MathFunctions::distanceSquared3D(xyzAll, offsetCoord1, offsetCoord2);
+    double b = MathFunctions::distanceSquared3D(xyzAll, offsetCoord2, offsetCoord3);
+    double c = MathFunctions::distanceSquared3D(xyzAll, offsetCoord3, offsetCoord1);
+    float area =
+        (float)(0.25f* std::sqrt(std::abs(4.0*a*c - (a-b+c)*(a-b+c))));
+    return area;
+}
+
+/**
+ * Compute the signed area of a triangle in 2D.
+ * @param p1 - 1st coordinate of triangle
+ * @param p2 - 2nd coordinate of triangle
+ * @param p3 - 3rd coordinate of triangle
+ * @return Signed area of triangle which is positive if the vertices
+ *    are in counter-clockwise orientation or negative if the vertices
+ *    are in clockwise orientation.
+ *
+ */
+float
+MathFunctions::triangleAreaSigned2D(
+                   const float p1[2],
+                   const float p2[2],
+                   const float p3[2])
+{
+    float area = (  p1[0]*p2[1] + p2[0]*p3[1] + p3[0]*p1[1]
+                  - p1[1]*p2[0] - p2[1]*p3[0] - p3[1]*p1[0] ) * 0.5f;
+    return area;
+}
+
+/**
+ * Compute the signed area of a triangle in 3D.
+ * @param referenceNormal - Normal vector.
+ * @param p1 - 1st coordinate of triangle
+ * @param p2 - 2nd coordinate of triangle
+ * @param p3 - 3rd coordinate of triangle
+ * @return Signed area of triangle which is positive if the vertices
+ *    are in counter-clockwise orientation or negative if the vertices
+ *    are in clockwise orientation.
+ *
+ */
+float
+MathFunctions::triangleAreaSigned3D(
+                   const float referenceNormal[3],
+                   const float p1[3],
+                   const float p2[3],
+                   const float p3[3])
+{
+    //
+    // Area of the triangle formed by the three points
+    //
+    float area = triangleArea(p1, p2, p3);
+    
+    //
+    // Normal for the three points
+    //
+    float triangleNormal[3];
+    MathFunctions::normalVector(p1, p2, p3, triangleNormal);
+    
+    //
+    // Dot Product is the cosine of the angle between the two normals.  When this value is less
+    // than zero, the absolute angle between the normals is greater than 90 degrees.
+    //
+    float dot = MathFunctions::dotProduct(referenceNormal, triangleNormal);
+    if (dot < 0.0) {
+        area = -area;
+    }
+    
+    return area;
+}
+
+/**
+ * Determine if 2D line segments intersect.
+ * Algorithm from http://mathworld.wolfram.com/Line-LineIntersection.html
+ *
+ * @param p1 Line 1 end point 1.
+ * @param p2 Line 1 end point 2.
+ * @param q1 Line 2 end point 1.
+ * @param q2 Line 2 end point 2.
+ * @param tolerance  Tolerance around the vertices (essentially
+ *    lengthens lines by this quantity).  Caret5 set this
+ *    parameter to 0.01.
+ * @param intersectionOut Location of intersection.
+ * @return  true if the line segments intersect else false.
+ *
+ */
+bool
+MathFunctions::lineIntersection2D(
+                   const float p1[2],
+                   const float p2[2],
+                   const float q1[2],
+                   const float q2[2],
+                   const float tolerance,
+                   float intersectionOut[2])
+{
+    double tol = tolerance;
+    double x1 = p1[0];
+    double y1 = p1[1];
+    double x2 = p2[0];
+    double y2 = p2[1];
+    
+    double x3 = q1[0];
+    double y3 = q1[1];
+    double x4 = q2[0];
+    double y4 = q2[1];
+    
+    double denom = ((x1 - x2) * (y3 - y4)) - ((x3 - x4) * (y1 - y2));
+    
+    if (denom != 0.0) {
+        double a = (x1 * y2) - (x2 * y1);
+        double c = (x3 * y4) - (x4 * y3);
+        double x = ((a * (x3 - x4)) - (c * (x1 - x2))) / denom;
+        double y = ((a * (y3 - y4)) - (c * (y1 - y2))) / denom;
+        
+        double pxMax = std::max(x1, x2) + tol;
+        double pxMin = std::min(x1, x2) - tol;
+        double pyMax = std::max(y1, y2) + tol;
+        double pyMin = std::min(y1, y2) - tol;
+        
+        double qxMax = std::max(x3, x4) + tol;
+        double qxMin = std::min(x3, x4) - tol;
+        double qyMax = std::max(y3, y4) + tol;
+        double qyMin = std::min(y3, y4) - tol;
+        
+        intersectionOut[0] = (float)x;
+        intersectionOut[1] = (float)y;
+        if ((x >= pxMin) && (x <= pxMax) && (x >= qxMin) && (x <= qxMax) &&
+            (y >= pyMin) && (y <= pyMax) && (y >= qyMin) && (y <= qyMax)) {
+            return true;
+        }
+    }
+    
+    return false;
+}
+
+/**
+ * Determine if a ray intersects a plane.
+ * @param p1 - 1st point defining the plane
+ * @param p2 - 2nd point defining the plane
+ * @param p3 - 3rd point defining the plane
+ * @param rayOrigin - origin of the ray
+ * @param rayVector - vector defining the ray
+ * @param intersectionXYZandDistance - An array of four that will contain
+ *    the XYZ or the intersection point and the distance from the plane.
+ * @return  true if the ray intersects the plane, else false.
+ *
+ */
+bool
+MathFunctions::rayIntersectPlane(
+                   const float p1[3],
+                   const float p2[3],
+                   const float p3[3],
+                   const float rayOrigin[3],
+                   const float rayVector[3],
+                   float intersectionXYZandDistance[4])
+{
+    // Convert the ray into a unit vector
+    //
+    double ray[3] = { rayVector[0], rayVector[1], rayVector[2] };
+    MathFunctions::normalizeVector(ray);
+    
+    //
+    // Normal of plane
+    //
+    float normal[3]; 
+    MathFunctions::normalVector(p1, p2, p3, normal);
+    
+    //
+    // Compute the plane equation
+    //
+    double A = normal[0];
+    double B = normal[1];
+    double C = normal[2];
+    double D = -(A*p1[0] + B*p1[1] + C*p1[2]);
+    
+    //
+    // Parametric coordinate of where ray intersects plane
+    //
+    double denom = A * ray[0] + B * ray[1] + C * ray[2];
+    if (denom != 0) {
+        const double t = -(A * rayOrigin[0] + B * rayOrigin[1] + C * rayOrigin[2] + D) / denom;
+        
+        intersectionXYZandDistance[0] = (float)(rayOrigin[0] + ray[0] * t);
+        intersectionXYZandDistance[1] = (float)(rayOrigin[1] + ray[1] * t);
+        intersectionXYZandDistance[2] = (float)(rayOrigin[2] + ray[2] * t);
+        
+        intersectionXYZandDistance[3] = (float)t;
+        
+        return true;
+    }
+    
+    return false;
+}
+
+/**
+ * Project a point to a plane.
+ * @param pt - the point to project.
+ * @param origin - point in the plane.
+ * @param normal - normal vector of plane.
+ * @param projectedPointOut - output, the projected position of "pt" on the plane.
+ *
+ */
+void
+MathFunctions::projectPoint(
+                   const float pt[3],
+                   const float origin[3],
+                   const float normal[3],
+                            float projectedPointOut[3])
+{
+    float xo[3] = {
+        pt[0] - origin[0],
+        pt[1] - origin[1],
+        pt[2] - origin[2]
+    };
+    
+    float t = MathFunctions::dotProduct(normal, xo);
+    
+    projectedPointOut[0] = pt[0] - t * normal[0];
+    projectedPointOut[1] = pt[1] - t * normal[1];
+    projectedPointOut[2] = pt[2] - t * normal[2];
+}
+
+/**
+ * Get the signed distance from the plane to the "queryPoint".  
+ * A positive distance indicates "queryPoint" is above the plane
+ * and a negative distance indicates "queryPoint" is below
+ * the plane.
+ * 
+ * @param planeNormal - plane's normal vector.
+ * @param pointInPlane - point on the plane.
+ * @param queryPoint - the query point for which distance to plane is sought.
+ * 
+ * @return Distance from the plane to the query point.
+ *
+ */
+float
+MathFunctions::signedDistanceFromPlane(
+                   const float planeNormal[3],
+                   const float pointInPlane[3],
+                   const float queryPoint[3])
+{
+    // Find out where query point projects on the plane
+    //
+    float queryPointProjectedOntoPlane[3];
+    MathFunctions::projectPoint(queryPoint, pointInPlane, planeNormal, queryPointProjectedOntoPlane);
+    float dx = planeNormal[0]
+        * (queryPoint[0] - queryPointProjectedOntoPlane[0]);
+    float dy = planeNormal[1]
+        * (queryPoint[1] - queryPointProjectedOntoPlane[1]);
+    float dz = planeNormal[2]
+        * (queryPoint[2] - queryPointProjectedOntoPlane[2]);
+    float dist = dx + dy + dz;
+    
+    return dist;
+}
+
+/**
+ * Limit the "value" to be inclusively between the minimum and maximum.
+ * @param value - Value for testing.
+ * @param minimumValue - Minimum inclusive value.
+ * @param maximumValue - Maximum inclusive value.
+ * @return Value limited inclusively to the minimum and maximum values.
+ *
+ */
+int32_t
+MathFunctions::limitRange(
+                   const int32_t value,
+                   const int32_t minimumValue,
+                   const int32_t maximumValue)
+{
+    if (value < minimumValue) {
+        return minimumValue;
+    }
+    
+    if (value > maximumValue) {
+        return maximumValue;
+    }
+    
+    return value;
+}
+
+/**
+ * Limit the "value" to be inclusively between the minimum and maximum.
+ * @param value - Value for testing.
+ * @param minimumValue - Minimum inclusive value.
+ * @param maximumValue - Maximum inclusive value.
+ * @return Value limited inclusively to the minimum and maximum values.
+ *
+ */
+float
+MathFunctions::limitRange(
+                   const float value,
+                   const float minimumValue,
+                   const float maximumValue)
+{
+    if (value < minimumValue) {
+        return minimumValue;
+    }
+    
+    if (value > maximumValue) {
+        return maximumValue;
+    }
+    
+    return value;
+}
+
+/**
+ * Limit the "value" to be inclusively between the minimum and maximum.
+ * @param value - Value for testing.
+ * @param minimumValue - Minimum inclusive value.
+ * @param maximumValue - Maximum inclusive value.
+ * @return Value limited inclusively to the minimum and maximum values.
+ *
+ */
+double
+MathFunctions::limitRange(
+                   const double value,
+                   const double minimumValue,
+                   const double maximumValue)
+{
+    if (value < minimumValue) {
+        return minimumValue;
+    }
+    
+    if (value > maximumValue) {
+        return maximumValue;
+    }
+    
+    return value;
+}
+
+/**
+ * Find the distance from the point to the line defined by p1 and p2.
+ * Formula is from
+ *    "http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html".
+ *
+ * @param p1 - First coordinate in line.
+ * @param p2 - Second coordinate in line.
+ * @param point - coordinate for which distance to line is sought.
+ * @return Distance from point to the line (p1, p2).
+ *
+ */
+float
+MathFunctions::distanceToLine3D(
+                   const float p1[3],
+                   const float p2[3],
+                   const float point[3])
+{
+    float dv2v1[3];
+    MathFunctions::subtractVectors(p2, p1, dv2v1);
+    float dv1pt[3];
+    MathFunctions::subtractVectors(p1, point, dv1pt);
+    
+    float crossed[3];
+    MathFunctions::crossProduct(dv2v1, dv1pt, crossed);
+    
+    float numerator = MathFunctions::vectorLength(crossed);
+    float denomenator = MathFunctions::vectorLength(dv2v1);
+    
+    float dist = numerator / denomenator;
+    
+    return dist;
+}
+
+/**
+ * Determine if two arrays are equal, same number of elements and
+ * corresponding elements equal.
+ *
+ * @param a - first array.
+ * @param b - second array.
+ * @return true if arrays are equal, else false.
+ *
+ */
+bool
+MathFunctions::arraysEqual(
+                   const float a[],
+                   const float b[],
+                           const int numElements)
+{
+    for (int i = 0; i < numElements; i++) {
+        if (a[i] != b[i]) {
+            return false;
+        }
+    }
+    
+    return true;
+}
+
+/**
+ * Get the average of three coordinates.
+ * @param c1 - coordinate 1
+ * @param c2 - coordinate 2
+ * @param c3 - coordinate 3
+ * @param outputAverage A three-dimensional array into 
+ * which the average of the three coordinates is 
+ * placed.
+ *
+ */
+void
+MathFunctions::averageOfThreeCoordinates(
+                   const float c1[3],
+                   const float c2[3],
+                   const float c3[3],
+                   float outputAverage[3])
+{
+    outputAverage[0] = (c1[0] + c2[0] + c3[0]) / 3.0f;
+    outputAverage[1] = (c1[1] + c2[1] + c3[1]) / 3.0f;
+    outputAverage[2] = (c1[2] + c2[2] + c3[2]) / 3.0f;
+}
+
+/**
+ * Calculate the average of 3 coordinates.
+ * @param xyzAll One-dimensional array containing the XYZ coordinates.
+ * @param offsetCoord1  Offset of node 1's X-coordinate which is
+ *    followed by the Y- and Z-coordinates.
+ * @param offsetCoord2  Offset of node 2's X-coordinate which is
+ *    followed by the Y- and Z-coordinates.
+ * @param offsetCoord3  Offset of node 3's X-coordinate which is
+ *    followed by the Y- and Z-coordinates.
+ * @param outputAverage  3 dimensional array passed in, into which
+ * the average is placed.
+ * @param outputOffset  Offset of average into outputAverage array.
+ *
+ */
+void
+MathFunctions::averageOfThreeCoordinates(
+                   const float xyzAll[],
+                   const int32_t offsetCoord1,
+                   const int32_t offsetCoord2,
+                   const int32_t offsetCoord3,
+                   float outputAverage[],
+                   const int32_t outputOffset)
+{
+    outputAverage[outputOffset] = (xyzAll[offsetCoord1]   + xyzAll[offsetCoord2]   + xyzAll[offsetCoord3]) / 3.0f;
+    outputAverage[outputOffset+1] = (xyzAll[offsetCoord1+1] + xyzAll[offsetCoord2+1] + xyzAll[offsetCoord3+1]) / 3.0f;
+    outputAverage[outputOffset+2] = (xyzAll[offsetCoord1+2] + xyzAll[offsetCoord2+2] + xyzAll[offsetCoord3+2]) / 3.0f;
+}
+
+/**
+ * Angle formed by p1, p2, p3 (angle at p2).  Returned angle is in radians.
+ * This method uses Java Math.acos() and produces highly accurate results.
+ * @param p1 - point.
+ * @param p2 - point.
+ * @param p3 - point.
+ * @return Angle formed by points.
+ *
+ */
+float
+MathFunctions::angle(
+                   const float p1[3],
+                   const float p2[3],
+                   const float p3[3])
+{
+    //
+    // Vector from P2 to P1
+    //
+    float v21[3] = { p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2] };
+    
+    //
+    // Vector from P2 to P3
+    //
+    float v23[3] = { p3[0] - p2[0], p3[1] - p2[1], p3[2] - p2[2] };
+    
+    //
+    // Normalize the vectors
+    //
+    float v21len = MathFunctions::normalizeVector(v21);
+    float v23len = MathFunctions::normalizeVector(v23);
+    
+    float angleOut = 0.0f;
+    if ((v21len > 0.0) && (v23len > 0.0)) {          //
+        // angle is inverse cosine of the dot product
+        // and be sure to handle numerical errors.
+        //
+        float dot = MathFunctions::dotProduct(v21, v23);
+        if (dot > 1.0f) dot = 1.0f;
+        else if (dot < -1.0f) dot = -1.0f;
+        angleOut = (float)std::acos(dot);
+    }
+    
+    return angleOut;
+}
+
+/**
+ * Signed angle for "jik".
+ * @param pi - point.
+ * @param pj - point.
+ * @param pk - point.
+ * @param n - normal
+ * @return signed angle formed by the points.
+ *
+ */
+float
+MathFunctions::signedAngle(
+                   const float pi[3],
+                   const float pj[3],
+                   const float pk[3],
+                   const float n[3])
+{
+    float x1 = pj[0] - pi[0];
+    float y1 = pj[1] - pi[1];
+    float z1 = pj[2] - pi[2];
+    float x2 = pk[0] - pi[0];
+    float y2 = pk[1] - pi[1];
+    float z2 = pk[2] - pi[2];
+    
+    /* s = |(ji)||(ki)| sin(phi) by cross product */
+    float dx = y1*z2 - y2*z1;
+    float dy = x2*z1 - x1*z2;
+    float dz = x1*y2 - x2*y1;
+    float t = (dx*n[0]) + (dy*n[1]) + (dz*n[2]);
+    float s = (float)std::sqrt((dx*dx) + (dy*dy) + (dz*dz));
+    if (t < 0.0f) {
+        s = -s;
+    }
+    
+    /* c = |(ji)||(ki)| cos(phi) by inner product */
+    float c = x1*x2 + y1*y2 + z1*z2;
+    float phi = (float)std::atan2(s,c);
+    return phi;
+}
+
+/**
+ * Determine if an integer is an odd number.
+ * @param number Integer to test.
+ * @return  true if integer is odd, else false.
+ *
+ */
+bool
+MathFunctions::isOddNumber(const int32_t number)
+{
+    bool result = ((number & 1) != 0);
+    return result;
+}
+
+/**
+ * Determine if an integer is an odd number.
+ * @param number Integer to test.
+ * @return  true if integer is odd, else false.
+ *
+ */
+bool
+MathFunctions::isEvenNumber(const int32_t number)
+{
+    bool result = ((number & 1) == 0);
+    return result;
+}
+
+/**
+ * Determine if two arrays are equal.
+ * @param a1 First array.
+ * @param a2 Second array.
+ * @param tolerance  Allowable difference in elements at same index.
+ * @return  true if arrays are of same length and corresponding
+ * elements have a difference less than tolerance.
+ *
+ */
+bool
+MathFunctions::compareArrays(
+                   const float a1[],
+                   const float a2[],
+                   const int numElements,
+                   const float tolerance)
+{
+    for (int i = 0; i < numElements; i++) {
+        float diff = a1[i] - a2[i];
+        if (diff < 0.0f) diff = -diff;
+        if (diff > tolerance) {
+            return false;
+        }
+    }
+    
+    return true;
+}
+
+/**
+ * Clamp a value to the range minimum to maximum.
+ * @param value  Value for clamping.
+ * @param minimum  Minimum allowed value.
+ * @param maximum  Maximum allowed value.
+ * @return Value clamped to minimum and maximum.
+ *
+ */
+int32_t
+MathFunctions::clamp(
+                   const int32_t value,
+                   const int32_t minimum,
+                   const int32_t maximum)
+{
+    return MathFunctions::limitRange(value, minimum, maximum);
+}
+
+/**
+ * Clamp a value to the range minimum to maximum.
+ * @param value  Value for clamping.
+ * @param minimum  Minimum allowed value.
+ * @param maximum  Maximum allowed value.
+ * @return Value clamped to minimum and maximum.
+ *
+ */
+float
+MathFunctions::clamp(
+                   const float value,
+                   const float minimum,
+                   const float maximum)
+{
+    return MathFunctions::limitRange(value, minimum, maximum);
+}
+
+/**
+ * Distance SQUARED from (x1, y1) to (x2, y2)
+ * @param X-coordinate of first point.
+ * @param Y-coordinate of first point.
+ * @param X-coordinate of second point.
+ * @param Y-coordinate of second point.
+ * @return  Distance squared between the points.
+ */
+double 
+MathFunctions::distanceSquared2D(const double x1,
+                                 const double y1,
+                                 const double x2,
+                                 const double y2)
+{
+    const double dx = x2 - x1;
+    const double dy = y2 - y1;
+    const double d = (dx*dx) + (dy*dy);
+    return d;
+}
+
+uint32_t MathFunctions::gcd(uint32_t num1, uint32_t num2)
+{
+    if (num1 == 0 || num2 == 0)
+    {//catch zeros
+        return 0;//gcd(0,x)=gcd(x,0)=0, seems less confusing than returning x
+    }
+    //modulus method for good worst-case asymptotic performance
+    uint32_t temp;
+    if (num2 > num1)//num1 kept as the larger number to simplify the code
+    {
+        temp = num1;
+        num1 = num2;
+        num2 = temp;
+    }
+    while (num2)
+    {//maintain num2 as the smaller number
+        temp = num1 % num2;//modulus to reduce the larger as much as possible, result will be smaller than num2
+        num1 = num2;//so, we need to swap them
+        num2 = temp;//when result becomes zero, num1 is our gcd
+    }
+    return num1;
+}
+
+bool MathFunctions::isInf(const float number)
+{
+    return (std::fabs(number) > 1.0f && number * 2.0f == number);
+}
+
+bool MathFunctions::isNaN(const float number)
+{
+    return (number != number);
+}
+
+bool MathFunctions::isNegInf(const float number)
+{
+    return (number < -1.0f && number * 2.0f == number);
+}
+
+bool MathFunctions::isNumeric(const float number)
+{
+    return (!isNaN(number) && !isInf(number));
+}
+
+bool MathFunctions::isPosInf(const float number)
+{
+    return (number > 1.0f && number * 2.0f == number);
+}
+
+void MathFunctions::quaternToMatrix(const float cijk[4], float matrix[3][3])
+{//formula from http://en.wikipedia.org/wiki/Rotation_matrix#Quaternion
+    double qlengthsqr = cijk[0] * cijk[0] + cijk[1] * cijk[1] + cijk[2] * cijk[2] + cijk[3] * cijk[3];
+    double mult = 0.0;
+    if (qlengthsqr > 0.0f)
+    {
+        mult = 2.0f / qlengthsqr;
+    }
+    double ijkmult[4] = { cijk[1] * mult, cijk[2] * mult, cijk[3] * mult };
+    double wX = cijk[0] * ijkmult[0], wY = cijk[0] * ijkmult[1], wZ = cijk[0] * ijkmult[2];
+    double xX = cijk[1] * ijkmult[0], xY = cijk[1] * ijkmult[1], xZ = cijk[1] * ijkmult[2];
+    double yY = cijk[2] * ijkmult[1], yZ = cijk[2] * ijkmult[2];
+    double zZ = cijk[3] * ijkmult[2];
+    matrix[0][0] = 1.0 - (yY + zZ);//equals nifti1 formula because for unit quaternion, a*a + b*b + c*c + d*d = 1, and yY = 2 * c*c
+    matrix[0][1] = xY - wZ;
+    matrix[0][2] = xZ + wY;
+    matrix[1][0] = xY + wZ;
+    matrix[1][1] = 1.0 - (xX + zZ);
+    matrix[1][2] = yZ - wX;
+    matrix[2][0] = xZ - wY;
+    matrix[2][1] = yZ + wX;
+    matrix[2][2] = 1.0 - (xX + yY);
+}
+
+void MathFunctions::quaternToMatrix(const double cijk[4], double matrix[3][3])
+{//formula from http://en.wikipedia.org/wiki/Rotation_matrix#Quaternion
+    double qlengthsqr = cijk[0] * cijk[0] + cijk[1] * cijk[1] + cijk[2] * cijk[2] + cijk[3] * cijk[3];
+    double mult = 0.0;
+    if (qlengthsqr > 0.0f)
+    {
+        mult = 2.0f / qlengthsqr;
+    }
+    double ijkmult[4] = { cijk[1] * mult, cijk[2] * mult, cijk[3] * mult };
+    double wX = cijk[0] * ijkmult[0], wY = cijk[0] * ijkmult[1], wZ = cijk[0] * ijkmult[2];
+    double xX = cijk[1] * ijkmult[0], xY = cijk[1] * ijkmult[1], xZ = cijk[1] * ijkmult[2];
+    double yY = cijk[2] * ijkmult[1], yZ = cijk[2] * ijkmult[2];
+    double zZ = cijk[3] * ijkmult[2];
+    matrix[0][0] = 1.0 - (yY + zZ);//equals nifti1 formula because for unit quaternion, a*a + b*b + c*c + d*d = 1, and yY = 2 * c*c
+    matrix[0][1] = xY - wZ;
+    matrix[0][2] = xZ + wY;
+    matrix[1][0] = xY + wZ;
+    matrix[1][1] = 1.0 - (xX + zZ);
+    matrix[1][2] = yZ - wX;
+    matrix[2][0] = xZ - wY;
+    matrix[2][1] = yZ + wX;
+    matrix[2][2] = 1.0 - (xX + yY);
+}
+
+bool MathFunctions::matrixToQuatern(const float matrix[3][3], float cijk[4])
+{//formulas from http://en.wikipedia.org/wiki/Rotation_matrix#Quaternion
+    const float toler = 0.0001f;
+    float ivec[3] = { matrix[0][0], matrix[1][0], matrix[2][0] };
+    float jvec[3] = { matrix[0][1], matrix[1][1], matrix[2][1] };
+    float kvec[3] = { matrix[0][2], matrix[1][2], matrix[2][2] };
+    if (!(std::abs(1.0f - normalizeVector(ivec)) <= toler)) return false;//use the "not less than or equal to" trick to catch NaNs
+    if (!(std::abs(1.0f - normalizeVector(jvec)) <= toler)) return false;
+    if (!(std::abs(1.0f - normalizeVector(kvec)) <= toler)) return false;
+    if (!(dotProduct(ivec, jvec) <= toler)) return false;
+    if (!(dotProduct(ivec, kvec) <= toler)) return false;
+    if (!(dotProduct(jvec, kvec) <= toler)) return false;
+    float tempvec[3];
+    crossProduct(ivec, jvec, tempvec);
+    if (!(dotProduct(tempvec, kvec) >= 0.9f)) return false;//i cross j must be k, otherwise it contains a flip
+    int method = 0;
+    double trace = matrix[0][0] + matrix[1][1] + matrix[2][2];
+    if (trace < 0.0)
+    {
+        method = 1;
+        float tempf = matrix[0][0];
+        if (matrix[1][1] > tempf)
+        {
+            method = 2;
+            tempf = matrix[1][1];
+        }
+        if (matrix[2][2] > tempf)
+        {
+            method = 3;
+        }
+    }
+    switch (method)
+    {
+        case 0:
+            {
+                double r = std::sqrt(1.0 + trace);
+                double s = 0.5 / r;
+                cijk[0] = 0.5 * r;
+                cijk[1] = (matrix[2][1] - matrix[1][2]) * s;
+                cijk[2] = (matrix[0][2] - matrix[2][0]) * s;
+                cijk[3] = (matrix[1][0] - matrix[0][1]) * s;
+            }
+        break;
+        case 1:
+            {
+                double r = std::sqrt(1.0 + matrix[0][0] - matrix[1][1] - matrix[2][2]);
+                double s = 0.5 / r;
+                cijk[0] = (matrix[2][1] - matrix[1][2]) * s;
+                cijk[1] = 0.5 * r;
+                cijk[2] = (matrix[0][1] + matrix[1][0]) * s;
+                cijk[3] = (matrix[2][0] + matrix[0][2]) * s;
+            }
+        break;
+        case 2:
+            {//DISCLAIMER: these last two were worked out by pattern since they aren't on wikipedia
+                double r = std::sqrt(1.0 - matrix[0][0] + matrix[1][1] - matrix[2][2]);
+                double s = 0.5 / r;
+                cijk[0] = (matrix[0][2] - matrix[2][0]) * s;
+                cijk[1] = (matrix[0][1] + matrix[1][0]) * s;
+                cijk[2] = 0.5 * r;
+                cijk[3] = (matrix[1][2] + matrix[2][1]) * s;
+            }
+        break;
+        case 3:
+            {
+                double r = std::sqrt(1.0 - matrix[0][0] - matrix[1][1] + matrix[2][2]);
+                double s = 0.5 / r;
+                cijk[0] = (matrix[1][0] - matrix[0][1]) * s;
+                cijk[1] = (matrix[2][0] + matrix[0][2]) * s;
+                cijk[2] = (matrix[1][2] + matrix[2][1]) * s;
+                cijk[3] = 0.5 * r;
+            }
+        break;
+        default:
+            return false;
+    }
+    if (cijk[0] < 0.0f)
+    {
+        cijk[0] = -cijk[0];
+        cijk[1] = -cijk[1];
+        cijk[2] = -cijk[2];
+        cijk[3] = -cijk[3];
+    }
+    return true;
+}
+
+bool MathFunctions::matrixToQuatern(const double matrix[3][3], double cijk[4])
+{//formulas from http://en.wikipedia.org/wiki/Rotation_matrix#Quaternion
+    const float toler = 0.0001f;
+    double ivec[3] = { matrix[0][0], matrix[1][0], matrix[2][0] };
+    double jvec[3] = { matrix[0][1], matrix[1][1], matrix[2][1] };
+    double kvec[3] = { matrix[0][2], matrix[1][2], matrix[2][2] };
+    if (!(std::abs(1.0f - normalizeVector(ivec)) <= toler)) return false;//use the "not less than or equal to" trick to catch NaNs
+    if (!(std::abs(1.0f - normalizeVector(jvec)) <= toler)) return false;
+    if (!(std::abs(1.0f - normalizeVector(kvec)) <= toler)) return false;
+    if (!(dotProduct(ivec, jvec) <= toler)) return false;
+    if (!(dotProduct(ivec, kvec) <= toler)) return false;
+    if (!(dotProduct(jvec, kvec) <= toler)) return false;
+    double tempvec[3];
+    crossProduct(ivec, jvec, tempvec);
+    if (!(dotProduct(tempvec, kvec) >= 0.9f)) return false;//i cross j must be k, otherwise it contains a flip
+    int method = 0;
+    double trace = matrix[0][0] + matrix[1][1] + matrix[2][2];
+    if (trace < 0.0)
+    {
+        method = 1;
+        float tempf = matrix[0][0];
+        if (matrix[1][1] > tempf)
+        {
+            method = 2;
+            tempf = matrix[1][1];
+        }
+        if (matrix[2][2] > tempf)
+        {
+            method = 3;
+        }
+    }
+    switch (method)
+    {
+        case 0:
+        {
+            double r = std::sqrt(1.0 + trace);
+            double s = 0.5 / r;
+            cijk[0] = 0.5 * r;
+            cijk[1] = (matrix[2][1] - matrix[1][2]) * s;
+            cijk[2] = (matrix[0][2] - matrix[2][0]) * s;
+            cijk[3] = (matrix[1][0] - matrix[0][1]) * s;
+        }
+            break;
+        case 1:
+        {
+            double r = std::sqrt(1.0 + matrix[0][0] - matrix[1][1] - matrix[2][2]);
+            double s = 0.5 / r;
+            cijk[0] = (matrix[2][1] - matrix[1][2]) * s;
+            cijk[1] = 0.5 * r;
+            cijk[2] = (matrix[0][1] + matrix[1][0]) * s;
+            cijk[3] = (matrix[2][0] + matrix[0][2]) * s;
+        }
+            break;
+        case 2:
+        {//DISCLAIMER: these last two were worked out by pattern since they aren't on wikipedia
+            double r = std::sqrt(1.0 - matrix[0][0] + matrix[1][1] - matrix[2][2]);
+            double s = 0.5 / r;
+            cijk[0] = (matrix[0][2] - matrix[2][0]) * s;
+            cijk[1] = (matrix[0][1] + matrix[1][0]) * s;
+            cijk[2] = 0.5 * r;
+            cijk[3] = (matrix[1][2] + matrix[2][1]) * s;
+        }
+            break;
+        case 3:
+        {
+            double r = std::sqrt(1.0 - matrix[0][0] - matrix[1][1] + matrix[2][2]);
+            double s = 0.5 / r;
+            cijk[0] = (matrix[1][0] - matrix[0][1]) * s;
+            cijk[1] = (matrix[2][0] + matrix[0][2]) * s;
+            cijk[2] = (matrix[1][2] + matrix[2][1]) * s;
+            cijk[3] = 0.5 * r;
+        }
+            break;
+        default:
+            return false;
+    }
+    if (cijk[0] < 0.0f)
+    {
+        cijk[0] = -cijk[0];
+        cijk[1] = -cijk[1];
+        cijk[2] = -cijk[2];
+        cijk[3] = -cijk[3];
+    }
+    return true;
+}
+
+/**
+ * Return the remainder from the resulting division using the given values.
+ * 
+ * This method is written to match the result produced by the remainder() 
+ * function that is part of C99 but not supported on all platforms.
+ *
+ * Code may appear verbose but it avoid functions calls to fabs(), ceil(),
+ * and floor().
+ *
+ *     Note: X is the numerator.
+ *           Y is the denominator.
+ *
+ *     The remainder() functions compute the value r such that r = x - n*y,
+ *     where n is the integer nearest the exact value of x/y.
+ *     
+ *     If there are two integers closest to x/y, n shall be the even one. 
+ *
+ * @param numerator
+ *    The numerator.
+ * @param denominator
+ *    The denominator.
+ * @return
+ *    The remainder from numerator divided by denominator.
+ */
+double
+MathFunctions::remainder(const double numerator,
+                         const double denominator)
+{
+    if (denominator == 0.0) {
+        return 0.0;
+    }
+    
+    const double quotient = numerator / denominator;
+    
+    /*
+     * Integer value greater than or equal to the quotient
+     * and its difference with the quotient (ceiling)
+     */
+    const int64_t nearestIntegerOne = static_cast<int64_t>(quotient + 0.5);
+    double diffOne = quotient - nearestIntegerOne;
+    if (diffOne < 0.0) diffOne = -diffOne;
+
+    /*
+     * Integer value less than or equal to the quotient
+     * and its difference with the quotient (floor)
+     */
+    const int64_t nearestIntegerTwo = static_cast<int64_t>(quotient - 0.5);
+    double diffTwo = quotient - nearestIntegerTwo;
+    if (diffTwo < 0.0) diffTwo = -diffTwo;
+
+    /*
+     * Helps determine if the two integer value are the same
+     * distance from the quotient (value will be very close
+     * to zero).
+     */
+    double diffOneTwo = diffOne - diffTwo;
+    if (diffOneTwo < 0.0) diffOneTwo = -diffOneTwo;
+
+    int64_t nearestInteger = 0;
+    
+    /*
+     * If the two integer values are the same distance from zero
+     */
+    if (diffOneTwo < 0.000001) {
+        /*
+         * Use the integer that is even.
+         * Note that if an integer is even, first bit is zero.
+         */
+        if ((nearestIntegerOne & 1) == 0) {
+            nearestInteger = nearestIntegerOne;
+        }
+        else {
+            nearestInteger = nearestIntegerTwo;
+        }
+    }
+    else if (diffOne < diffTwo) {
+        nearestInteger = nearestIntegerOne;
+    }
+    else {
+        nearestInteger = nearestIntegerTwo;
+    }
+    
+    const double remainderValue = numerator - nearestInteger * denominator;
+    return remainderValue;
+}
+
+/**
+ * Return the value rounded to the nearest integral (integer) value.
+ * 
+ * @param value
+ *    Value that is rounded.
+ * @return
+ *    Value rounded to nearest integral value.
+ */
+double
+MathFunctions::round(const double value)
+{
+    if (value < 0.0) {
+        return std::ceil(value - 0.5f);
+    }
+    return std::floor(value + 0.5f);
+}
+
diff --git a/src/Common/MathFunctions.h b/src/Common/MathFunctions.h
new file mode 100644
index 0000000..b7bd766
--- /dev/null
+++ b/src/Common/MathFunctions.h
@@ -0,0 +1,313 @@
+#ifndef __MATHFUNCTIONS_H__
+#define __MATHFUNCTIONS_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+
+namespace cifti {
+
+/**
+ * Various mathematical functions.
+ */
+//NOTE: in CiftiLib, we only reference matrixToQuatern and quaternToMatrix, both used only in NiftiHeader when using volume files, not cifti files
+class MathFunctions {
+public:
+    static int64_t combinations(
+                    const int64_t n,
+                    const int64_t k);
+
+    static int64_t permutations(
+                    const int64_t n,
+                    const int64_t k);
+
+    static int64_t factorial(const int64_t n);
+
+    static bool normalVector(
+                    const float v1[3],
+                    const float v2[3],
+                    const float v3[3],
+                    float normalVectorOut[3]);
+
+    static bool normalVector(
+                    const double v1[3],
+                    const double v2[3],
+                    const double v3[3],
+                    double normalVectorOut[3]);
+
+    static void normalVectorDirection(
+                    const float v1[3],
+                    const float v2[3],
+                    const float v3[3],
+                    float directionOut[3]);
+
+    static void crossProduct(
+                    const float v1[],
+                    const float v2[],
+                    float resultOut[]);
+
+    static void crossProduct(
+                             const double v1[],
+                             const double v2[],
+                             double resultOut[]);
+    
+    static void normalizedCrossProduct(
+                    const float x1[],
+                                 const float x2[],
+                                 float resultOut[]);
+
+    static float normalizeVector(
+                    float vectorsAll[],
+                    const int32_t offset);
+
+    static float normalizeVector(float vectorInOut[3]);
+
+    static double normalizeVector(double vectorInOut[3]);
+
+    static float vectorLength(const float vector[3]);
+
+    static float vectorLength(
+                    const float vectorsAll[],
+                    const int32_t offset);
+
+    static double vectorLength(const double vector[3]);
+
+    static float distanceSquared3D(
+                    const float p1[3],
+                    const float p2[3]);
+
+    static float distanceSquared3D(
+                    const float xyzAll[],
+                    const int32_t offsetCoord1,
+                    const int32_t offsetCoord2);
+
+    static float distance3D(
+                    const float p1[3],
+                    const float p2[3]);
+
+    static double distanceSquared3D(
+                    const double p1[3],
+                    const double p2[3]);
+
+    static double distance3D(
+                    const double p1[3],
+                    const double p2[3]);
+
+    static double distanceSquared2D(const double x1,
+                                    const double y1,
+                                    const double x2,
+                                    const double y2);
+    
+    static void subtractVectors(
+                    const float v1[3],
+                    const float v2[3],
+                    float resultOut[3]);
+
+    static void addVectors(
+                    const float v1[3],
+                    const float v2[3],
+                    float resultOut[3]);
+    
+    static void createUnitVector(
+                    const float startXYZ[3],
+                    const float endXYZ[3],
+                    float unitVectorOut[3]);
+
+    static void createUnitVector(
+                                 const double startXYZ[3],
+                                 const double endXYZ[3],
+                                 double unitVectorOut[3]);
+    
+    static float dotProduct(
+                    const float p1[3],
+                    const float p2[3]);
+
+    static double dotProduct(
+                            const double p1[3],
+                            const double p2[3]);
+    
+    static float triangleArea(
+                    const float v1[3],
+                    const float v2[3],
+                    const float v3[3]);
+
+    static float triangleArea(const double v1[3],
+                              const double v2[3],
+                              const double v3[3]);
+    
+    static float triangleArea(
+                    const float xyzAll[],
+                    const int32_t offsetCoord1,
+                    const int32_t offsetCoord2,
+                    const int32_t offsetCoord3);
+
+    static float triangleAreaSigned2D(
+                    const float p1[3],
+                    const float p2[3],
+                    const float p3[3]);
+
+    static float triangleAreaSigned3D(
+                    const float referenceNormal[3],
+                    const float p1[3],
+                    const float p2[3],
+                    const float p3[3]);
+
+    static bool lineIntersection2D(
+                    const float p1[3],
+                    const float p2[3],
+                    const float q1[3],
+                    const float q2[3],
+                    const float tolerance,
+                    float intersectionOut[3]);
+
+    static bool rayIntersectPlane(
+                    const float p1[3],
+                    const float p2[3],
+                    const float p3[3],
+                    const float rayOrigin[3],
+                    const float rayVector[3],
+                    float intersectionXYZandDistance[3]);
+
+    static void projectPoint(
+                    const float pt[3],
+                    const float origin[3],
+                    const float normal[3],
+                              float projectedPointOut[3]);
+
+    static float signedDistanceFromPlane(
+                    const float planeNormal[3],
+                    const float pointInPlane[3],
+                    const float queryPoint[3]);
+
+    static int32_t limitRange(
+                    const int32_t value,
+                    const int32_t minimumValue,
+                    const int32_t maximumValue);
+
+    static float limitRange(
+                    const float value,
+                    const float minimumValue,
+                    const float maximumValue);
+
+    static double limitRange(
+                    const double value,
+                    const double minimumValue,
+                    const double maximumValue);
+
+    static float distanceToLine3D(
+                    const float p1[3],
+                    const float p2[3],
+                    const float point[3]);
+
+    static bool arraysEqual(
+                    const float a[],
+                    const float b[],
+                    const int32_t numElements);
+
+    static void averageOfThreeCoordinates(
+                    const float c1[3],
+                    const float c2[3],
+                    const float c3[3],
+                    float outputAverage[3]);
+
+    static void averageOfThreeCoordinates(
+                    const float xyzAll[],
+                    const int32_t offsetCoord1,
+                    const int32_t offsetCoord2,
+                    const int32_t offsetCoord3,
+                    float outputAverage[],
+                    const int32_t outputOffset);
+
+    static float angle(
+                    const float p1[3],
+                    const float p2[3],
+                    const float p3[3]);
+
+    static float signedAngle(
+                    const float pi[3],
+                    const float pj[3],
+                    const float pk[3],
+                    const float n[3]);
+
+    static bool isOddNumber(const int32_t number);
+
+    static bool isEvenNumber(const int32_t number);
+    
+    static bool isNaN(const float number);
+    
+    static bool isPosInf(const float number);
+    
+    static bool isNegInf(const float number);
+    
+    ///true if either inf or -inf
+    static bool isInf(const float number);
+    
+    ///true only if not NaN, inf, or -inf
+    static bool isNumeric(const float number);
+
+    static bool compareArrays(
+                    const float a1[],
+                    const float a2[],
+                    const int32_t numElements,
+                    const float tolerance);
+
+    static int32_t clamp(
+                    const int32_t value,
+                    const int32_t minimum,
+                    const int32_t maximum);
+
+    static float clamp(
+                    const float value,
+                    const float minimum,
+                    const float maximum);
+
+    ///greatest common divisor
+    static uint32_t gcd(uint32_t num1, uint32_t num2);
+    
+    ///convert quaternion to rotation matrix
+    static void quaternToMatrix(const float cijk[4], float matrix[3][3]);
+    
+    ///convert quaternion to rotation matrix
+    static void quaternToMatrix(const double cijk[4], double matrix[3][3]);
+    
+    ///try to convert 3x3 matrix to quaternion (return false if not a rotation matrix)
+    static bool matrixToQuatern(const float matrix[3][3], float cijk[4]);
+
+    ///try to convert 3x3 matrix to quaternion (return false if not a rotation matrix)
+    static bool matrixToQuatern(const double matrix[3][3], double cijk[4]);
+    
+    static double remainder(const double numerator,
+                            const double denominator);
+    
+    static double round(const double value);
+    
+};
+
+} // namespace
+
+#endif // __MATHFUNCTIONS_H__
diff --git a/src/Common/MatrixFunctions.h b/src/Common/MatrixFunctions.h
new file mode 100644
index 0000000..fedf295
--- /dev/null
+++ b/src/Common/MatrixFunctions.h
@@ -0,0 +1,708 @@
+#ifndef __MATRIX_UTILITIES_H__
+#define __MATRIX_UTILITIES_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <vector>
+#include <cmath>
+#include "stdint.h"
+
+using namespace std;
+
+//NOTE: this is not intended to be used outside of FloatMatrix.cxx, error condition is a 0x0 matrix result
+//NOTE: if a matrix has a row shorter than the first row, expect problems.  Calling checkDim will look for this, but it is not used internally, as FloatMatrix maintains this invariant
+
+namespace cifti {
+
+   class MatrixFunctions
+   {
+      typedef int64_t msize_t;//NOTE: must be signed due to using -1 as a sentinel
+      
+      public:
+      ///
+      /// matrix multiplication
+      ///
+      template <typename T1, typename T2, typename T3, typename A>
+      static void multiply(const vector<vector<T1> > &left, const vector<vector<T2> > &right, vector<vector<T3> > &result);
+
+      ///
+      /// scalar multiplication
+      ///
+      template <typename T1, typename T2, typename T3>
+      static void multiply(const vector<vector<T1> > &left, const T2 right, vector<vector<T3> > &result);
+
+      ///
+      /// reduced row echelon form
+      ///
+      template <typename T>
+      static void rref(vector<vector<T> > &inout);
+      
+      ///
+      /// matrix inversion - wrapper to rref for now
+      ///
+      template <typename T>
+      static void inverse(const vector<vector<T> > &in, vector<vector<T> > &result);
+      
+      ///
+      /// matrix addition - for simple code
+      ///
+      template <typename T1, typename T2, typename T3>
+      static void add(const vector<vector<T1> > &left, const vector<vector<T2> > &right, vector<vector<T3> > &result);
+      
+      ///
+      /// scalar addition - for simple code
+      ///
+      template <typename T1, typename T2, typename T3>
+      static void add(const vector<vector<T1> > &left, const T2 right, vector<vector<T3> > &result);
+      
+      ///
+      /// matrix subtraction - for simple code
+      ///
+      template <typename T1, typename T2, typename T3>
+      static void subtract(const vector<vector<T1> > &left, const vector<vector<T2> > &right, vector<vector<T3> > &result);
+      
+      ///
+      /// transpose - for simple code
+      ///
+      template <typename T>
+      static void transpose(const vector<vector<T> > &in, vector<vector<T> > &result);
+      
+      ///
+      /// debugging - verify matrix is rectangular and show its dimensions - returns true if rectangular
+      ///
+      template <typename T>
+      static bool checkDim(const vector<vector<T> > &in);
+      
+      ///
+      /// allocate a matrix, don't initialize
+      ///
+      template <typename T>
+      static void resize(const msize_t rows, const msize_t columns, vector<vector<T> > &result, bool destructive = false);
+      
+      ///
+      /// allocate a matrix of specified size
+      ///
+      template <typename T>
+      static void zeros(const msize_t rows, const msize_t columns, vector<vector<T> > &result);
+      
+      ///
+      /// allocate a matrix of specified size
+      ///
+      template <typename T>
+      static void ones(const msize_t rows, const msize_t columns, vector<vector<T> > &result);
+      
+      ///
+      /// make an identity matrix
+      ///
+      template <typename T>
+      static void identity(const msize_t size, vector<vector<T> > &result);
+      
+      ///
+      /// horizontally concatenate matrices
+      ///
+      template <typename T1, typename T2, typename T3>
+      static void horizCat(const vector<vector<T1> > &left, const vector<vector<T2> > &right, vector<vector<T3> > &result);
+      
+      ///
+      /// vertically concatenate matrices
+      ///
+      template <typename T1, typename T2, typename T3>
+      static void vertCat(const vector<vector<T1> > &top, const vector<vector<T2> > &bottom, vector<vector<T3> > &result);
+      
+      ///
+      /// grab a piece of a matrix
+      ///
+      template <typename T>
+      static void getChunk(const msize_t firstrow, const msize_t lastrow, const msize_t firstcol, const msize_t lastcol, const vector<vector<T> > &in, vector<vector<T> > &result);
+      
+   private:
+      ///
+      /// reduced row echelon form that is faster on larger matrices, is called by rref() if the matrix is big enough
+      ///
+      template <typename T>
+      static void rref_big(vector<vector<T> > &inout);
+   };
+
+   template <typename T1, typename T2, typename T3, typename A>
+   void MatrixFunctions::multiply(const vector<vector<T1> >& left, const vector<vector<T2> >& right, vector<vector<T3> >& result)
+   {//the stupid multiply O(n^3) - the O(n^2.78) version might not be that hard to implement with the other functions here, but not as stable
+      msize_t leftrows = (msize_t)left.size(), rightrows = (msize_t)right.size(), leftcols, rightcols;
+      vector<vector<T3> > tempstorage, *tresult = &result;//pointer because you can't change a reference
+      bool copyout = false;
+      if (&left == &result || &right == &result)
+      {
+         copyout = true;
+         tresult = &tempstorage;
+      }
+      if (leftrows && rightrows)
+      {
+         leftcols = (msize_t)left[0].size();
+         rightcols = (msize_t)right[0].size();
+         if (leftcols && rightcols && (rightrows == leftcols))
+         {
+            resize(leftrows, rightcols, (*tresult), true);//could use zeros(), but common index last lets us zero at the same time
+            msize_t i, j, k;
+            for (i = 0; i < leftrows; ++i)
+            {
+               for (j = 0; j < rightcols; ++j)
+               {
+                  A accum = 0;
+                  for (k = 0; k < leftcols; ++k)
+                  {
+                     accum += left[i][k] * right[k][j];
+                  }
+                  (*tresult)[i][j] = accum;
+               }
+            }
+         } else {
+            result.resize(0);
+            return;
+         }
+      } else {
+         result.resize(0);
+         return;
+      }
+      if (copyout)
+      {
+         result = tempstorage;
+      }
+   }
+
+   template <typename T1, typename T2, typename T3>
+   void MatrixFunctions::multiply(const vector<vector<T1> > &left, const T2 right, vector<vector<T3> > &result)
+   {
+      msize_t leftrows = (msize_t)left.size(), leftcols;
+      bool doresize = true;
+      if (&left == &result)
+      {
+         doresize = false;//don't resize if an input is an output
+      }
+      if (leftrows)
+      {
+         leftcols = (msize_t)left[0].size();
+         if (leftcols)
+         {
+            if (doresize) resize(leftrows, leftcols, result, true);
+            msize_t i, j;
+            for (i = 0; i < leftrows; ++i)
+            {
+               for (j = 0; j < leftcols; ++j)
+               {
+                  result[i][j] = left[i][j] * right;
+               }
+            }
+         } else {
+            result.resize(0);
+            return;
+         }
+      } else {
+         result.resize(0);
+         return;
+      }
+   }
+
+   template<typename T>
+   void MatrixFunctions::rref_big(vector<vector<T> > &inout)
+   {
+      msize_t rows = (msize_t)inout.size(), cols;
+      if (rows > 0)
+      {
+         cols = (msize_t)inout[0].size();
+         if (cols > 0)
+         {
+            vector<msize_t> pivots(rows, -1), missingPivots;
+            msize_t i, j, k, myrow = 0;
+            msize_t pivotrow;
+            T tempval;
+            for (i = 0; i < cols; ++i)
+            {
+               if (myrow >= rows) break;//no pivots left
+               tempval = 0;
+               pivotrow = -1;
+               for (j = myrow; j < rows; ++j)
+               {//only search below for new pivot
+                  if (abs(inout[j][i]) > tempval)
+                  {
+                     pivotrow = (msize_t)j;
+                     tempval = abs(inout[j][i]);
+                  }
+               }
+               if (pivotrow == -1)
+               {//naively expect linearly dependence to show as an exact zero
+                  missingPivots.push_back(i);//record the missing pivot
+                  continue;//move to the next column
+               }
+               inout[pivotrow].swap(inout[myrow]);//STL swap via pointers for constant time row swap
+               pivots[myrow] = i;//save the pivot location for back substitution
+               tempval = inout[myrow][i];
+               inout[myrow][i] = (T)1;
+               for (j = i + 1; j < cols; ++j)
+               {
+                  inout[myrow][j] /= tempval;//divide row by pivot
+               }
+               for (j = myrow + 1; j < rows; ++j)
+               {//zero ONLY below pivot for now
+                  tempval = inout[j][i];
+                  inout[j][i] = (T)0;
+                  for (k = i + 1; k < cols; ++k)
+                  {
+                     inout[j][k] -= tempval * inout[myrow][k];
+                  }
+               }
+               ++myrow;//increment row on successful pivot
+            }
+            msize_t numMissing = (msize_t)missingPivots.size();
+            if (myrow > 1)//if there is only 1 pivot, there is no back substitution to do
+            {
+                msize_t lastPivotCol = pivots[myrow - 1];
+                for (i = myrow - 1; i > 0; --i)//loop through pivots, can't zero above the top pivot so exclude it
+                {
+                    msize_t pivotCol = pivots[i];
+                    for (j = i - 1; j >= 0; --j)//loop through rows above pivot
+                    {
+                        tempval = inout[j][pivotCol];
+                        inout[j][pivotCol] = (T)0;//flat zero the entry above the pivot
+                        for (k = numMissing - 1; k >= 0; --k)//back substitute within pivot range where pivots are missing
+                        {
+                            msize_t missingCol = missingPivots[k];
+                            if (missingCol <= pivotCol) break;//equals will never trip, but whatever
+                            inout[j][missingCol] -= tempval * inout[i][missingCol];
+                        }
+                        for (k = lastPivotCol + 1; k < cols; ++k)//loop through elements that are outside the pivot area
+                        {
+                            inout[j][k] -= tempval * inout[i][k];
+                        }
+                    }
+                }
+            }
+         } else {
+            inout.resize(0);
+            return;
+         }
+      } else {
+         inout.resize(0);
+         return;
+      }
+   }
+
+   template<typename T>
+   void MatrixFunctions::rref(vector<vector<T> > &inout)
+   {
+      msize_t rows = (msize_t)inout.size(), cols;
+      if (rows)
+      {
+         cols = (msize_t)inout[0].size();
+         if (cols)
+         {
+            if (rows > 7 || cols > 7)//when the matrix has this many rows/columns, it is faster to allocate storage for tracking pivots, and back substitute
+            {
+                rref_big(inout);
+                return;
+            }
+            msize_t i, j, k, myrow = 0;
+            msize_t pivotrow;
+            T tempval;
+            for (i = 0; i < cols; ++i)
+            {
+               if (myrow >= rows) break;//no pivots left
+               tempval = 0;
+               pivotrow = -1;
+               for (j = myrow; j < rows; ++j)
+               {//only search below for new pivot
+                  if (abs(inout[j][i]) > tempval)
+                  {
+                     pivotrow = (msize_t)j;
+                     tempval = abs(inout[j][i]);
+                  }
+               }
+               if (pivotrow == -1)//it may be a good idea to include a "very small value" check here, but it could mess up if used on a matrix with all values very small
+               {//naively expect linearly dependence to show as an exact zero
+                  continue;//move to the next column
+               }
+               inout[pivotrow].swap(inout[myrow]);//STL swap via pointers for constant time row swap
+               tempval = inout[myrow][i];
+               inout[myrow][i] = 1;
+               for (j = i + 1; j < cols; ++j)
+               {
+                  inout[myrow][j] /= tempval;//divide row by pivot
+               }
+               for (j = 0; j < myrow; ++j)
+               {//zero above pivot
+                  tempval = inout[j][i];
+                  inout[j][i] = 0;
+                  for (k = i + 1; k < cols; ++k)
+                  {
+                     inout[j][k] -= tempval * inout[myrow][k];
+                  }
+               }
+               for (j = myrow + 1; j < rows; ++j)
+               {//zero below pivot
+                  tempval = inout[j][i];
+                  inout[j][i] = 0;
+                  for (k = i + 1; k < cols; ++k)
+                  {
+                     inout[j][k] -= tempval * inout[myrow][k];
+                  }
+               }
+               ++myrow;//increment row on successful pivot
+            }
+         } else {
+            inout.resize(0);
+            return;
+         }
+      } else {
+         inout.resize(0);
+         return;
+      }
+   }
+
+   template<typename T>
+   void MatrixFunctions::inverse(const vector<vector<T> > &in, vector<vector<T> > &result)
+   {//rref implementation, there are faster (more complicated) ways - if it isn't invertible, it will hand back something strange
+      msize_t inrows = (msize_t)in.size(), incols;
+      if (inrows)
+      {
+         incols = (msize_t)in[0].size();
+         if (incols == inrows)
+         {
+            vector<vector<T> > inter, inter2;
+            identity(incols, inter2);
+            horizCat(in, inter2, inter);
+            rref(inter);
+            getChunk(0, inrows, incols, incols * 2, inter, result);//already using a local variable, doesn't need to check for reference duplicity
+         } else {
+            result.resize(0);
+            return;
+         }
+      } else {
+         result.resize(0);
+         return;
+      }
+   }
+
+   template <typename T1, typename T2, typename T3>
+   void MatrixFunctions::add(const vector<vector<T1> >& left, const vector<vector<T2> >& right, vector<vector<T3> >& result)
+   {
+      msize_t inrows = (msize_t)left.size(), incols;
+      bool doresize = true;
+      if (&left == &result || &right == &result)
+      {
+         doresize = false;//don't resize if an input is an output - this is ok for addition, don't need a copy
+      }
+      if (inrows)
+      {
+         incols = (msize_t)left[0].size();
+         if (inrows == (msize_t)right.size() && incols == (msize_t)right[0].size())
+         {
+            if (doresize) resize(inrows, incols, result, true);
+            for (msize_t i = 0; i < inrows; ++i)
+            {
+               for (msize_t j = 0; j < incols; ++j)
+               {
+                  result[i][j] = left[i][j] + right[i][j];
+               }
+            }
+         } else {
+            result.resize(0);//use empty matrix for error condition
+            return;
+         }
+      } else {
+         result.resize(0);
+         return;
+      }
+   }
+
+   template <typename T1, typename T2, typename T3>
+   void MatrixFunctions::add(const vector<vector<T1> >& left, const T2 right, vector<vector<T3> >& result)
+   {
+      msize_t inrows = (msize_t)left.size(), incols;
+      bool doresize = true;
+      if (&left == &result)
+      {
+         doresize = false;//don't resize if an input is an output - this is ok for addition, don't need a copy
+      }
+      if (inrows)
+      {
+         incols = (msize_t)left[0].size();
+         if (doresize) resize(inrows, incols, result, true);
+         for (msize_t i = 0; i < inrows; ++i)
+         {
+            for (msize_t j = 0; j < incols; ++j)
+            {
+               result[i][j] = left[i][j] + right;
+            }
+         }
+      } else {
+         result.resize(0);
+         return;
+      }
+   }
+
+   template <typename T1, typename T2, typename T3>
+   void MatrixFunctions::subtract(const vector<vector<T1> >& left, const vector<vector<T2> >& right, vector<vector<T3> >& result)
+   {
+      msize_t inrows = (msize_t)left.size(), incols;
+      bool doresize = true;
+      if (&left == &result || &right == &result)
+      {
+         doresize = false;//don't resize if an input is an output
+      }
+      if (inrows)
+      {
+         incols = (msize_t)left[0].size();
+         if (inrows == (msize_t)right.size() && incols == (msize_t)right[0].size())
+         {
+            if (doresize) resize(inrows, incols, result, true);
+            for (msize_t i = 0; i < inrows; ++i)
+            {
+               for (msize_t j = 0; j < incols; ++j)
+               {
+                  result[i][j] = left[i][j] - right[i][j];
+               }
+            }
+         } else {
+            result.resize(0);
+            return;
+         }
+      } else {
+         result.resize(0);
+         return;
+      }
+   }
+
+   template<typename T>
+   void MatrixFunctions::transpose(const vector<vector<T> > &in, vector<vector<T> > &result)
+   {
+      msize_t inrows = (msize_t)in.size(), incols;
+      vector<vector<T> > tempstorage, *tresult = &result;
+      bool copyout = false;
+      if (&in == &result)
+      {
+         copyout = true;
+         tresult = &tempstorage;
+      }
+      if (inrows)
+      {
+         incols = (msize_t)in[0].size();
+         resize(incols, inrows, (*tresult), true);
+         for (msize_t i = 0; i < inrows; ++i)
+         {
+            for (msize_t j = 0; j < incols; ++j)
+            {
+               (*tresult)[j][i] = in[i][j];
+            }
+         }
+      } else {
+         result.resize(0);
+      }
+      if (copyout)
+      {
+         result = tempstorage;
+      }
+   }
+
+   template<typename T>
+   bool MatrixFunctions::checkDim(const vector<vector<T> > &in)
+   {
+      bool ret = true;
+      msize_t rows = (msize_t)in.size(), columns;
+      if (rows)
+      {
+         columns = (msize_t)in[0].size();
+         for (msize_t i = 1; i < rows; ++i)
+         {
+            if (in[i].size() != columns)
+            {
+               ret = false;
+            }
+         }
+      }
+      return ret;
+   }
+
+   template<typename T>
+   void MatrixFunctions::resize(const msize_t rows, const msize_t columns, vector<vector<T> >& result, bool destructive)
+   {
+      if (destructive && result.size() && ((msize_t)result.capacity() < rows || (msize_t)result[0].capacity() < columns))
+      {//for large matrices, copying to preserve contents is slow
+         result.resize(0);//not intended to dealloc, just to set number of items to copy to zero
+      }//default is nondestructive resize, copies everything
+      result.resize(rows);
+      for (msize_t i = 0; i < (const msize_t)rows; ++i)
+      {//naive method, may end up copying everything twice if both row and col resizes require realloc
+         result[i].resize(columns);
+      }
+   }
+
+   template<typename T>
+   void MatrixFunctions::zeros(const msize_t rows, const msize_t columns, vector<vector<T> >& result)
+   {
+      resize(rows, columns, result, true);
+      for (msize_t i = 0; i < rows; ++i)
+      {
+         for (msize_t j = 0; j < columns; ++j)
+         {
+            result[i][j] = 0;//should cast to float or double fine
+         }
+      }
+   }
+
+   template<typename T>
+   void MatrixFunctions::ones(const msize_t rows, const msize_t columns, vector<vector<T> >& result)
+   {
+      resize(rows, columns, result, true);
+      for (msize_t i = 0; i < rows; ++i)
+      {
+         for (msize_t j = 0; j < columns; ++j)
+         {
+            result[i][j] = 1;//should cast to float or double fine
+         }
+      }
+   }
+
+   template<typename T>
+   void MatrixFunctions::identity(const msize_t size, vector<vector<T> >& result)
+   {
+      resize(size, size, result, true);
+      for (msize_t i = 0; i < (const msize_t)size; ++i)
+      {
+         for (msize_t j = 0; j < (const msize_t)size; ++j)
+         {
+            result[i][j] = ((i == j) ? 1 : 0);//ditto, forgive the ternary
+         }
+      }
+   }
+
+   template <typename T1, typename T2, typename T3>
+   void MatrixFunctions::horizCat(const vector<vector<T1> >& left, const vector<vector<T2> >& right, vector<vector<T3> >& result)
+   {
+      msize_t inrows = (msize_t)left.size(), leftcols, rightcols;
+      vector<vector<T3> > tempstorage, *tresult = &result;
+      bool copyout = false;
+      if (&left == &result || &right == &result)
+      {
+         copyout = true;
+         tresult = &tempstorage;
+      }
+      if (inrows && inrows == (msize_t)right.size())
+      {
+         leftcols = (msize_t)left[0].size();
+         rightcols = (msize_t)right[0].size();
+         (*tresult) = left;//use STL copy to start
+         resize(inrows, leftcols + rightcols, (*tresult));//values survive nondestructive resize
+         for (msize_t i = 0; i < inrows; ++i)
+         {
+            for (msize_t j = 0; j < rightcols; ++j)
+            {
+               (*tresult)[i][j + leftcols] = right[i][j];
+            }
+         }
+      } else {
+         result.resize(0);
+         return;
+      }
+      if (copyout)
+      {
+         result = tempstorage;
+      }
+   }
+
+   template <typename T1, typename T2, typename T3>
+   void MatrixFunctions::vertCat(const vector<vector<T1> >& top, const vector<vector<T2> >& bottom, vector<vector<T3> >& result)
+   {
+      msize_t toprows = (msize_t)top.size(), botrows = (msize_t)bottom.size(), incols;
+      vector<vector<T3> > tempstorage, *tresult = &result;
+      bool copyout = false;
+      if (&top == &result || &bottom == &result)
+      {
+         copyout = true;
+         tresult = &tempstorage;
+      }
+      if (toprows && botrows)
+      {
+         incols = (msize_t)top[0].size();
+         if (incols == (msize_t)bottom[0].size())
+         {
+            (*tresult) = top;
+            resize(toprows + botrows, incols, (*tresult));//nondestructive resize
+            for (msize_t i = 0; i < botrows; ++i)
+            {
+               for (msize_t j = 0; j < incols; ++j)
+               {
+                  (*tresult)[i + toprows][j] = bottom[i][j];
+               }
+            }
+         } else {
+            result.resize(0);
+            return;
+         }
+      } else {
+         result.resize(0);
+         return;
+      }
+      if (copyout)
+      {
+         result = tempstorage;
+      }
+   }
+
+   template<typename T>
+   void MatrixFunctions::getChunk(const msize_t firstrow, const msize_t lastrow, const msize_t firstcol, const msize_t lastcol, const vector<vector<T> >& in, vector<vector<T> >& result)
+   {
+      msize_t outrows = lastrow - firstrow;
+      msize_t outcols = lastcol - firstcol;
+      if (lastrow <= firstrow || lastcol <= firstcol || firstrow < 0 || firstcol < 0 || lastrow > (msize_t)in.size() || lastcol > (msize_t)in[0].size())
+      {
+         result.resize(0);
+         return;
+      }
+      vector<vector<T> > tempstorage, *tresult = &result;
+      bool copyout = false;
+      if (&in == &result)
+      {
+         copyout = true;
+         tresult = &tempstorage;
+      }
+      resize(outrows, outcols, (*tresult), true);
+      for (msize_t i = 0; i < outrows; ++i)
+      {
+         for (msize_t j = 0; j < outcols; ++j)
+         {
+            (*tresult)[i][j] = in[i + firstrow][j + firstcol];
+         }
+      }
+      if (copyout)
+      {
+         result = tempstorage;
+      }
+   }
+
+}
+
+#endif
+
diff --git a/src/Common/MultiDimArray.h b/src/Common/MultiDimArray.h
new file mode 100644
index 0000000..d2c4bda
--- /dev/null
+++ b/src/Common/MultiDimArray.h
@@ -0,0 +1,124 @@
+#ifndef __MULTI_DIM_ARRAY_H__
+#define __MULTI_DIM_ARRAY_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiAssert.h"
+
+#include "stdint.h"
+#include <vector>
+
+namespace cifti
+{
+    
+    template<typename T>
+    class MultiDimArray
+    {
+        std::vector<int64_t> m_dims, m_skip;//always use int64_t for indexes internally
+        std::vector<T> m_data;
+        template<typename I>
+        int64_t index(const int& fullDims, const std::vector<I>& indexSelect) const;//assume we never need over 2 billion dimensions
+    public:
+        const std::vector<int64_t>& getDimensions() const { return m_dims; }
+        template<typename I>
+        void resize(const std::vector<I>& dims);//destructive resize
+        template<typename I>
+        T& at(const std::vector<I>& pos);
+        template<typename I>
+        const T& at(const std::vector<I>& pos) const;
+        template<typename I>
+        T* get(const int& fullDims, const std::vector<I>& indexSelect);//subarray reference selection
+        template<typename I>
+        const T* get(const int& fullDims, const std::vector<I>& indexSelect) const;
+    };
+    
+    template<typename T>
+    template<typename I>
+    void MultiDimArray<T>::resize(const std::vector<I>& dims)
+    {
+        m_dims = std::vector<int64_t>(dims.begin(), dims.end());
+        m_skip.resize(m_dims.size());
+        if (dims.size() == 0)
+        {
+            m_data.clear();
+            return;
+        }
+        int64_t numElems = 1;
+        for (int i = 0; i < (int)m_dims.size(); ++i)
+        {
+            CiftiAssert(m_dims[i] > 0);
+            m_skip[i] = numElems;
+            numElems *= m_dims[i];
+        }
+        m_data.resize(numElems);
+    }
+    
+    template<typename T>
+    template<typename I>
+    int64_t MultiDimArray<T>::index(const int& fullDims, const std::vector<I>& indexSelect) const
+    {
+        CiftiAssert(fullDims + indexSelect.size() == m_dims.size());
+        int64_t ret = 0;
+        for (int i = fullDims; i < (int)m_dims.size(); ++i)
+        {
+            CiftiAssert(indexSelect[i - fullDims] >= 0 && indexSelect[i - fullDims] < m_dims[i]);
+            ret += m_skip[i] * indexSelect[i - fullDims];
+        }
+        return ret;
+    }
+    
+    template<typename T>
+    template<typename I>
+    T& MultiDimArray<T>::at(const std::vector<I>& pos)
+    {
+        return m_data[index(0, pos)];
+    }
+    
+    template<typename T>
+    template<typename I>
+    const T& MultiDimArray<T>::at(const std::vector<I>& pos) const
+    {
+        return m_data[index(0, pos)];
+    }
+    
+    template<typename T>
+    template<typename I>
+    T* MultiDimArray<T>::get(const int& fullDims, const std::vector<I>& indexSelect)
+    {
+        return m_data.data() + index(fullDims, indexSelect);
+    }
+    
+    template<typename T>
+    template<typename I>
+    const T* MultiDimArray<T>::get(const int& fullDims, const std::vector<I>& indexSelect) const
+    {
+        return m_data.data() + index(fullDims, indexSelect);
+    }
+}
+
+#endif //__MULTI_DIM_ARRAY_H__
diff --git a/src/Common/MultiDimIterator.h b/src/Common/MultiDimIterator.h
new file mode 100644
index 0000000..e95b2a7
--- /dev/null
+++ b/src/Common/MultiDimIterator.h
@@ -0,0 +1,157 @@
+#ifndef __MULTI_DIM_ITERATOR_H__
+#define __MULTI_DIM_ITERATOR_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "stdint.h"
+#include <vector>
+
+namespace cifti
+{
+    
+    template<typename T>
+    class MultiDimIterator
+    {
+        std::vector<T> m_dims, m_pos;
+        bool m_atEnd;
+        void gotoBegin();
+        void gotoLast();
+    public:
+        explicit MultiDimIterator(const std::vector<T>& dimensions);
+        void operator++();
+        void operator++(int);
+        void operator--();
+        void operator--(int);
+        const std::vector<T>& operator*() const { return m_pos; }
+        bool atEnd() const { return m_atEnd; }
+    };
+    
+    template<typename T>
+    MultiDimIterator<T>::MultiDimIterator(const std::vector<T>& dimensions)
+    {
+        m_dims = dimensions;
+        gotoBegin();
+    }
+    
+    template<typename T>
+    void MultiDimIterator<T>::gotoBegin()
+    {
+        m_pos = std::vector<T>(m_dims.size(), 0);
+        m_atEnd = false;
+        size_t numDims = m_dims.size();
+        for (size_t i = 0; i < numDims; ++i)
+        {
+            if (m_dims[i] < 1)
+            {
+                m_atEnd = true;
+                break;
+            }
+        }
+    }
+    
+    template<typename T>
+    void MultiDimIterator<T>::gotoLast()
+    {
+        m_pos = std::vector<T>(m_dims.size());
+        m_atEnd = false;
+        size_t numDims = m_dims.size();
+        for (size_t i = 0; i < numDims; ++i)
+        {
+            m_pos[i] = m_dims[i] - 1;
+            if (m_dims[i] < 1)
+            {
+                m_atEnd = true;
+            }
+        }
+    }
+
+    template<typename T>
+    void MultiDimIterator<T>::operator++()
+    {
+        if (atEnd())//wrap around
+        {
+            gotoBegin();
+            return;
+        }
+        if (m_dims.size() == 0)
+        {
+            m_atEnd = true;//special case: no dimensions works the same as 1 dimension of length 1
+            return;
+        }
+        size_t numDims = m_dims.size();
+        for (size_t i = 0; i < numDims; ++i)
+        {
+            ++m_pos[i];
+            if (m_pos[i] < m_dims[i]) return;
+            m_pos[i] = 0;
+        }
+        m_atEnd = true;//if we didn't return already, all of them wrapped, so we are at the end
+    }
+    
+    template<typename T>
+    void MultiDimIterator<T>::operator++(int)
+    {
+        ++(*this);
+    }
+    
+    template<typename T>
+    void MultiDimIterator<T>::operator--()
+    {
+        if (atEnd())//wrap around
+        {
+            gotoLast();
+            return;
+        }
+        if (m_dims.size() == 0)
+        {
+            m_atEnd = true;//special case: no dimensions works the same as 1 dimension of length 1
+            return;
+        }
+        size_t numDims = m_dims.size();
+        for (size_t i = 0; i < numDims; ++i)
+        {
+            if (m_pos[i] > 0)
+            {
+                --m_pos[i];
+                return;
+            } else {
+                m_pos[i] = m_dims[i] - 1;
+            }
+        }
+        m_atEnd = true;//if we didn't return already, all of them wrapped, so we are at the end
+    }
+    
+    template<typename T>
+    void MultiDimIterator<T>::operator--(int)
+    {
+        --(*this);
+    }
+    
+}
+
+#endif //__MULTI_DIM_ITERATOR_H__
diff --git a/src/Common/Vector3D.cxx b/src/Common/Vector3D.cxx
new file mode 100644
index 0000000..3b64557
--- /dev/null
+++ b/src/Common/Vector3D.cxx
@@ -0,0 +1,197 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CiftiAssert.h"
+#include "Vector3D.h"
+
+#include <cmath>
+
+using namespace std;
+using namespace cifti;
+
+Vector3D Vector3D::cross(const Vector3D& right) const
+{
+    Vector3D ret;
+    ret[0] = m_vec[1] * right[2] - m_vec[2] * right[1];
+    ret[1] = m_vec[2] * right[0] - m_vec[0] * right[2];
+    ret[2] = m_vec[0] * right[1] - m_vec[1] * right[0];
+    return ret;
+}
+
+float Vector3D::dot(const Vector3D& right) const
+{
+    return m_vec[0] * right[0] + m_vec[1] * right[1] + m_vec[2] * right[2];
+}
+
+float Vector3D::length() const
+{
+    return sqrt(lengthsquared());
+}
+
+float Vector3D::lengthsquared() const
+{
+    return m_vec[0] * m_vec[0] + m_vec[1] * m_vec[1] + m_vec[2] * m_vec[2];
+}
+
+Vector3D Vector3D::normal(float* origLength) const
+{
+    Vector3D ret = *this;
+    float mylength = length();
+    if (mylength != 0.0f) ret /= mylength;
+    if (origLength != NULL)
+    {
+        *origLength = mylength;
+    }
+    return ret;
+}
+
+Vector3D::Vector3D()
+{
+    m_vec[0] = 0.0f;
+    m_vec[1] = 0.0f;
+    m_vec[2] = 0.0f;
+}
+
+Vector3D::Vector3D(const float& x, const float& y, const float& z)
+{
+    m_vec[0] = x;
+    m_vec[1] = y;
+    m_vec[2] = z;
+}
+
+Vector3D::Vector3D(const float* right)
+{
+    m_vec[0] = right[0];
+    m_vec[1] = right[1];
+    m_vec[2] = right[2];
+}
+
+float& Vector3D::operator[](const int64_t& index)
+{
+    CiftiAssert(index > -1 && index < 3);
+    return m_vec[index];
+}
+
+const float& Vector3D::operator[](const int64_t& index) const
+{
+    CiftiAssert(index > -1 && index < 3);
+    return m_vec[index];
+}
+
+float& Vector3D::operator[](const int32_t& index)
+{
+    CiftiAssert(index > -1 && index < 3);
+    return m_vec[index];
+}
+
+const float& Vector3D::operator[](const int32_t& index) const
+{
+    CiftiAssert(index > -1 && index < 3);
+    return m_vec[index];
+}
+
+Vector3D Vector3D::operator*(const float& right) const
+{
+    Vector3D ret = *this;
+    ret *= right;
+    return ret;
+}
+
+Vector3D& Vector3D::operator*=(const float& right)
+{
+    m_vec[0] *= right;
+    m_vec[1] *= right;
+    m_vec[2] *= right;
+    return *this;
+}
+
+Vector3D cifti::operator*(const float& left, const Vector3D& right)
+{
+    return right * left;
+}
+
+Vector3D Vector3D::operator+(const Vector3D& right) const
+{
+    Vector3D ret = *this;
+    ret += right;
+    return ret;
+}
+
+Vector3D& Vector3D::operator+=(const Vector3D& right)
+{
+    m_vec[0] += right.m_vec[0];
+    m_vec[1] += right.m_vec[1];
+    m_vec[2] += right.m_vec[2];
+    return *this;
+}
+
+Vector3D Vector3D::operator-(const Vector3D& right) const
+{
+    Vector3D ret = *this;
+    ret -= right;
+    return ret;
+}
+
+Vector3D Vector3D::operator-() const
+{
+    Vector3D ret;
+    ret.m_vec[0] = -m_vec[0];
+    ret.m_vec[1] = -m_vec[1];
+    ret.m_vec[2] = -m_vec[2];
+    return ret;
+}
+
+Vector3D& Vector3D::operator-=(const Vector3D& right)
+{
+    m_vec[0] -= right.m_vec[0];
+    m_vec[1] -= right.m_vec[1];
+    m_vec[2] -= right.m_vec[2];
+    return *this;
+}
+
+Vector3D Vector3D::operator/(const float& right) const
+{
+    Vector3D ret = *this;
+    ret /= right;
+    return ret;
+}
+
+Vector3D& Vector3D::operator/=(const float& right)
+{
+    m_vec[0] /= right;
+    m_vec[1] /= right;
+    m_vec[2] /= right;
+    return *this;
+}
+
+Vector3D& Vector3D::operator=(const float* right)
+{
+    m_vec[0] = right[0];
+    m_vec[1] = right[1];
+    m_vec[2] = right[2];
+    return *this;
+}
diff --git a/src/Common/Vector3D.h b/src/Common/Vector3D.h
new file mode 100644
index 0000000..98473b3
--- /dev/null
+++ b/src/Common/Vector3D.h
@@ -0,0 +1,72 @@
+#ifndef __VECTOR_3D_H__
+#define __VECTOR_3D_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <cstddef>
+#include "stdint.h"
+
+namespace cifti {
+    
+    class Vector3D
+    {
+        float m_vec[3];
+    public:
+        //vector functions
+        float dot(const Vector3D& right) const;
+        Vector3D cross(const Vector3D& right) const;
+        Vector3D normal(float* origLength = NULL) const;
+        float length() const;
+        float lengthsquared() const;
+        //constructors
+        Vector3D();
+        Vector3D(const float& x, const float& y, const float& z);
+        Vector3D(const float* right);
+        //compatibility operators
+        float& operator[](const int64_t& index);
+        const float& operator[](const int64_t& index) const;
+        float& operator[](const int32_t& index);
+        const float& operator[](const int32_t& index) const;
+        Vector3D& operator=(const float* right);
+        //numerical operators
+        Vector3D& operator+=(const Vector3D& right);
+        Vector3D& operator-=(const Vector3D& right);
+        Vector3D& operator*=(const float& right);
+        Vector3D& operator/=(const float& right);
+        Vector3D operator+(const Vector3D& right) const;
+        Vector3D operator-(const Vector3D& right) const;
+        Vector3D operator-() const;
+        Vector3D operator*(const float& right) const;
+        Vector3D operator/(const float& right) const;//NOTE: doesn't really make sense to have the other division, unlike multiplication
+        inline operator float*() { return m_vec; }
+    };
+    
+    Vector3D operator*(const float& left, const Vector3D& right);
+
+}
+#endif //__VECTOR_3D_H__
diff --git a/src/Common/VoxelIJK.h b/src/Common/VoxelIJK.h
new file mode 100644
index 0000000..589d85f
--- /dev/null
+++ b/src/Common/VoxelIJK.h
@@ -0,0 +1,65 @@
+#ifndef __VOXEL_IJK_H__
+#define __VOXEL_IJK_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "stdint.h"
+
+namespace cifti {
+    
+    struct VoxelIJK
+    {
+        int64_t m_ijk[3];
+        VoxelIJK() { }
+        VoxelIJK(int64_t i, int64_t j, int64_t k) { m_ijk[0] = i; m_ijk[1] = j; m_ijk[2] = k; }
+        template<typename T>
+        VoxelIJK(const T ijk[3]) {
+            m_ijk[0] = ijk[0];
+            m_ijk[1] = ijk[1];
+            m_ijk[2] = ijk[2];
+        }
+        bool operator<(const VoxelIJK& rhs) const//so it kan be the key of a map
+        {
+            if (m_ijk[2] < rhs.m_ijk[2]) return true;//compare such that when sorted, m_ijk[0] moves fastest
+            if (m_ijk[2] > rhs.m_ijk[2]) return false;
+            if (m_ijk[1] < rhs.m_ijk[1]) return true;
+            if (m_ijk[1] > rhs.m_ijk[1]) return false;
+            return (m_ijk[0] < rhs.m_ijk[0]);
+        }
+        bool operator==(const VoxelIJK& rhs) const
+        {
+            return (m_ijk[0] == rhs.m_ijk[0] &&
+                    m_ijk[1] == rhs.m_ijk[1] &&
+                    m_ijk[2] == rhs.m_ijk[2]);
+        }
+        bool operator!=(const VoxelIJK& rhs) const { return !((*this) == rhs); }
+    };
+    
+}
+
+#endif //__VOXEL_IJK_H__
diff --git a/src/Common/XmlAdapter.cxx b/src/Common/XmlAdapter.cxx
new file mode 100644
index 0000000..6b3ddcc
--- /dev/null
+++ b/src/Common/XmlAdapter.cxx
@@ -0,0 +1,194 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "XmlAdapter.h"
+
+#include "CiftiAssert.h"
+
+using namespace std;
+using namespace cifti;
+
+XmlAttributesResult cifti::XmlReader_parseAttributes(XmlReader& xml, const vector<AString>& mandatoryNames, const vector<AString>& optionalNames)
+{
+    XmlAttributesResult ret;
+    int numMandatory = (int)mandatoryNames.size();//if you require more than 2 billion attributes, w3c would like a word with you
+    int numOptional = (int)optionalNames.size();
+    ret.mandatoryVals.resize(numMandatory);
+    ret.optionalVals.resize(numOptional);
+    AString elemName;
+#ifdef CIFTILIB_USE_QT
+    vector<bool> mandatoryPresent(numMandatory, false);
+    map<AString, int> mandatoryMap, optionalMap;
+    for (int i = 0; i < numMandatory; ++i)//set up map lookups so it isn't n^2
+    {
+        mandatoryMap[mandatoryNames[i]] = i;
+    }
+    for (int i = 0; i < numOptional; ++i)
+    {
+        optionalMap[optionalNames[i]] = i;
+    }
+    CiftiAssert(xml.isStartElement());
+    elemName = xml.name().toString();
+    QXmlStreamAttributes myAttrs = xml.attributes();
+    int numAttrs = myAttrs.size();
+    for (int i = 0; i < numAttrs; ++i)
+    {
+        QString name = myAttrs[i].name().toString();
+        map<AString, int>::iterator iter = mandatoryMap.find(name);
+        if (iter == mandatoryMap.end())
+        {
+            iter = optionalMap.find(name);
+            if (iter != optionalMap.end())//NOTE: ignore unrecognized attributes for now, because MatrixIndicesMap has attributes that are used by different objects
+            {//other option is to include such attributes in both calls, even if not used there
+                ret.optionalVals[iter->second].present = true;
+                ret.optionalVals[iter->second].value = myAttrs[i].value().toString();
+            }
+        } else {
+            ret.mandatoryVals[iter->second] = myAttrs[i].value().toString();
+            mandatoryPresent[iter->second] = true;
+        }
+    }
+    for (int i = 0; i < numMandatory; ++i)
+    {
+        if (mandatoryPresent[i] == false)
+        {
+            throw CiftiException(elemName + " element is missing the " + mandatoryNames[i] + " attribute");
+        }
+    }
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    CiftiAssert(xml.get_node_type() == XmlReader::Element);
+    elemName = xml.get_local_name();
+    for (int i = 0; i < numMandatory; ++i)//NOTE: libxml++ (and even libxml2 to some extent) don't have a good interface for iterating through all attributes efficiently
+    {//you have to iterate through a linked list of attributes exposed through a "hacking interface"
+        ret.mandatoryVals[i] = xml.get_attribute(mandatoryNames[i]);
+        if (ret.mandatoryVals[i] == "")//HACK: treat empty value same as missing attribute
+        {
+            throw CiftiException(elemName + " element is missing the " + mandatoryNames[i] + " attribute");
+        }
+    }
+    for (int i = 0; i < numOptional; ++i)
+    {
+        AString value = xml.get_attribute(optionalNames[i]);
+        if (value != "")//HACK: and again
+        {
+            ret.optionalVals[i].present = true;
+            ret.optionalVals[i].value = value;
+        }
+    }
+#else
+#error "not implemented"
+#endif
+#endif
+    return ret;
+}
+
+bool cifti::XmlReader_checkEndElement(XmlReader& xml, const AString& elementName)
+{
+#ifdef CIFTILIB_USE_QT
+    return xml.hasError() || (xml.isEndElement() && xml.name() == elementName);//if it has an xml error, don't trip an assert
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    return (xml.get_node_type() == XmlReader::EndElement || xml.is_empty_element()) && xml.get_local_name() == elementName;
+#else
+#error "not implemented"
+#endif
+#endif
+}
+
+AString cifti::XmlReader_readElementText(XmlReader& xml)
+{
+#ifdef CIFTILIB_USE_QT
+    return xml.readElementText();//NOTE: requires calling code to check for xml.hasError() when using QT
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    AString ret;
+    CiftiAssert(xml.get_node_type() == XmlReader::Element);
+    AString elemName = xml.get_local_name();
+    bool done = xml.is_empty_element();//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while(!done && xml.read())
+    {
+        switch(xml.get_node_type())
+        {
+            case XmlReader::Element:
+                throw CiftiException("unexpected element inside " + elemName + " element: " + xml.get_local_name());
+            case XmlReader::Text:
+            case XmlReader::CDATA:
+                ret += xml.get_value();
+                break;
+            case XmlReader::EndElement:
+                done = true;
+                break;
+            default:
+                break;
+        }
+    }
+    return ret;
+#else
+#error "not implemented"
+#endif
+#endif
+}
+
+bool cifti::XmlReader_readNextStartElement(XmlReader& xml)
+{
+#ifdef CIFTILIB_USE_QT
+    return xml.readNextStartElement();//NOTE: requires calling code to check for xml.hasError() when using QT
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    if (xml.is_empty_element()) return false;//NOTE: a <blah/> element does NOT give a separate end element state!!!
+    while (xml.read())
+    {
+        switch (xml.get_node_type())
+        {
+            case XmlReader::Element:
+                return true;
+            case XmlReader::EndElement:
+                return false;
+            default:
+                break;
+        }
+    }
+    return false;
+#else
+#error "not implemented"
+#endif
+#endif
+}
+
+AString cifti::XmlReader_elementName(XmlReader& xml)
+{
+#ifdef CIFTILIB_USE_QT
+    return xml.name().toString();
+#else
+#ifdef CIFTILIB_USE_XMLPP
+    return xml.get_local_name();
+#else
+#error "not implemented"
+#endif
+#endif
+}
\ No newline at end of file
diff --git a/src/Common/XmlAdapter.h b/src/Common/XmlAdapter.h
new file mode 100644
index 0000000..97c9d4a
--- /dev/null
+++ b/src/Common/XmlAdapter.h
@@ -0,0 +1,166 @@
+#ifndef __XML_ADAPTER_H__
+#define __XML_ADAPTER_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "AString.h"
+#include "CiftiException.h"
+
+#include <map>
+#include <vector>
+
+#ifdef __XML_ADAPTER_H_HAVE_IMPL__
+#undef __XML_ADAPTER_H_HAVE_IMPL__
+#endif
+
+#ifdef CIFTILIB_USE_QT
+#define __XML_ADAPTER_H_HAVE_IMPL__
+#include <QXmlStreamReader>
+#include <QXmlStreamWriter>
+namespace cifti
+{
+    typedef QXmlStreamReader XmlReader;
+    typedef QXmlStreamWriter XmlWriter;
+}
+#endif //CIFTILIB_USE_QT
+
+#ifdef CIFTILIB_USE_XMLPP
+#define __XML_ADAPTER_H_HAVE_IMPL__
+#include "libxml++/libxml++.h"
+#include "libxml++/parsers/textreader.h"
+#include "libxml/xmlwriter.h"
+#include "libxml/xmlstring.h"
+namespace cifti
+{
+    typedef xmlpp::TextReader XmlReader;
+    class XmlWriter
+    {//write our own wrapper for the C writing API, as libxml++ doesn't wrap it
+        xmlTextWriterPtr m_xmlPtr;
+        xmlBufferPtr m_bufPtr;
+        std::vector<AString> m_elementStack;//track element names for better error messages
+    public:
+        XmlWriter()
+        {//only support writing to memory
+            m_bufPtr = xmlBufferCreate();
+            if (m_bufPtr == NULL) throw CiftiException("error creating xml buffer");
+            m_xmlPtr = xmlNewTextWriterMemory(m_bufPtr, 0);
+            if (m_xmlPtr == NULL)
+            {
+                xmlBufferFree(m_bufPtr);
+                throw CiftiException("error creating xml writer");
+            }
+            if (xmlTextWriterSetIndent(m_xmlPtr, 1) != 0 || xmlTextWriterSetIndentString(m_xmlPtr, BAD_CAST "    ") != 0)
+            {
+                throw CiftiException("error setting xml writer indentation");
+            }
+        }
+        ~XmlWriter()
+        {
+            xmlFreeTextWriter(m_xmlPtr);
+            xmlBufferFree(m_bufPtr);
+        }
+        void writeStartDocument()//copy a subset of the QXmlStreamWriter interface, so we don't have to rewrite much (any?) xml writing code
+        {
+            if (xmlTextWriterStartDocument(m_xmlPtr, NULL, NULL, NULL) == -1) throw CiftiException("error writing document start");
+        }
+        void writeEndDocument()
+        {
+            if (xmlTextWriterEndDocument(m_xmlPtr) == -1) throw CiftiException("error writing document end");
+            m_elementStack.clear();
+        }
+        void writeStartElement(const AString& name)
+        {
+            if (xmlTextWriterStartElement(m_xmlPtr, BAD_CAST ASTRING_UTF8_RAW(name)) == -1) throw CiftiException("error writing " + name + " element");
+            m_elementStack.push_back(name);
+        }
+        void writeEndElement()
+        {
+            if (m_elementStack.empty()) throw CiftiException("internal error: attempted writing end element outside root element");
+            if (xmlTextWriterEndElement(m_xmlPtr) == -1) throw CiftiException("error writing end element for " + m_elementStack.back());
+            m_elementStack.pop_back();
+        }
+        void writeCharacters(const AString& text)
+        {
+            if (xmlTextWriterWriteString(m_xmlPtr, BAD_CAST ASTRING_UTF8_RAW(text)) == -1) throw CiftiException("error writing element text");
+        }
+        void writeTextElement(const AString& name, const AString& text)
+        {
+            if (xmlTextWriterWriteElement(m_xmlPtr, BAD_CAST ASTRING_UTF8_RAW(name), BAD_CAST ASTRING_UTF8_RAW(text)) == -1)
+            {
+                throw CiftiException("error writing " + name + " element");
+            }
+        }
+        void writeAttribute(const AString& name, const AString& text)
+        {
+            if (m_elementStack.empty()) throw CiftiException("internal error: attempted writing attribute outside root element");
+            if (xmlTextWriterWriteAttribute(m_xmlPtr, BAD_CAST ASTRING_UTF8_RAW(name), BAD_CAST ASTRING_UTF8_RAW(text)) == -1)
+            {
+                throw CiftiException("error writing " + name + " attribute of " + m_elementStack.back() + " element");
+            }
+        }
+        std::vector<char> getXmlData() const
+        {
+            std::vector<char> ret(m_bufPtr->use);//this includes the null terminator?
+            for (unsigned int i = 0; i < m_bufPtr->use; ++i)
+            {
+                ret[i] = m_bufPtr->content[i];
+            }
+            return ret;
+        }
+    };
+    
+}
+#endif //CIFTILIB_USE_XMLPP
+
+#ifndef __XML_ADAPTER_H_HAVE_IMPL__
+#error "you must define either CIFTILIB_USE_QT or CIFTILIB_USE_XMLPP to select what XML implementation to use"
+#endif
+
+namespace cifti
+{
+    //helper functions that exist for all xml libraries
+    struct XmlAttributesResult
+    {
+        struct OptionalStatus
+        {
+            OptionalStatus() { present = false; }
+            bool present;
+            AString value;
+        };
+        std::vector<AString> mandatoryVals;
+        std::vector<OptionalStatus> optionalVals;
+    };
+    
+    AString XmlReader_readElementText(XmlReader& xml);
+    bool XmlReader_readNextStartElement(XmlReader& xml);
+    AString XmlReader_elementName(XmlReader& xml);
+    XmlAttributesResult XmlReader_parseAttributes(XmlReader& xml, const std::vector<AString>& mandatoryNames, const std::vector<AString>& optionalNames = std::vector<AString>());
+    bool XmlReader_checkEndElement(XmlReader& xml, const AString& elementName);//for use in asserts at end of element parsing functions
+}
+
+#endif //__XML_ADAPTER_H__
diff --git a/src/Nifti/CMakeLists.txt b/src/Nifti/CMakeLists.txt
new file mode 100644
index 0000000..982231a
--- /dev/null
+++ b/src/Nifti/CMakeLists.txt
@@ -0,0 +1,10 @@
+
+project (Nifti)
+
+SET(HEADERS
+NiftiHeader.h
+)
+
+SET(SOURCES
+NiftiHeader.cxx
+)
diff --git a/src/Nifti/NiftiHeader.cxx b/src/Nifti/NiftiHeader.cxx
new file mode 100644
index 0000000..3b58f6f
--- /dev/null
+++ b/src/Nifti/NiftiHeader.cxx
@@ -0,0 +1,825 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "NiftiHeader.h"
+
+#include "Common/ByteSwapping.h"
+#include "Common/CiftiAssert.h"
+#include "Common/CiftiException.h"
+#include "Common/FloatMatrix.h"
+#include "Common/MathFunctions.h"
+
+#include <cmath>
+#include <cstring>
+#include <iostream>
+#include <limits>
+
+using namespace std;
+using namespace boost;
+using namespace cifti;
+
+NiftiHeader::NiftiHeader()
+{
+    if (sizeof(nifti_1_header) != 348 || sizeof(nifti_2_header) != 540)//these should be made into static asserts when we move to c++11 or decide to use boost
+    {
+        throw CiftiException("internal error: nifti header structs are the wrong size");//this is not a runtime assert, because we want this checked in release builds too
+    }
+    memset(&m_header, 0, sizeof(m_header));
+    for (int i = 0; i < 8; ++i)
+    {
+        m_header.dim[i] = 1;//we maintain 1s on unused dimensions to make some stupid nifti readers happy
+        m_header.pixdim[i] = 1.0f;//also set pixdims to 1 by default, to make some other nifti readers happy when reading cifti headers
+    }//note that we still warn when asking for spacing info and qform and sform codes are both 0, so this doesn't prevent us from catching non-volume files loaded as volumes
+    m_header.xyzt_units = SPACE_TIME_TO_XYZT(NIFTI_UNITS_MM, NIFTI_UNITS_SEC);
+    m_header.scl_slope = 1.0;//default to identity scaling
+    m_header.scl_inter = 0.0;
+    m_header.datatype = NIFTI_TYPE_FLOAT32;
+    m_header.bitpix = typeToNumBits(m_header.datatype);
+    m_version = 0;
+    m_isSwapped = false;
+}
+
+bool NiftiHeader::canWriteVersion(const int& version) const
+{
+    if (computeVoxOffset(version) < 0) return false;//error condition, can happen if an extension is longer than 2^31
+    if (version == 2) return true;//our internal state is nifti-2, return early
+    if (version != 1) return false;//we can only write 1 and 2
+    vector<int64_t> dims = getDimensions();
+    for (int i = 0; i < (int)dims.size(); ++i)
+    {
+        if (dims[i] > numeric_limits<int16_t>::max()) return false;
+    }
+    if (m_header.intent_code > numeric_limits<int16_t>::max() || m_header.intent_code < numeric_limits<int16_t>::min()) return false;
+    if (m_header.slice_code > numeric_limits<char>::max() || m_header.slice_code < numeric_limits<char>::min()) return false;
+    if (m_header.xyzt_units > numeric_limits<char>::max() || m_header.xyzt_units < numeric_limits<char>::min()) return false;
+    if (m_header.qform_code > numeric_limits<int16_t>::max() || m_header.qform_code < numeric_limits<int16_t>::min()) return false;
+    if (m_header.sform_code > numeric_limits<int16_t>::max() || m_header.sform_code < numeric_limits<int16_t>::min()) return false;
+    return true;
+}
+
+int64_t NiftiHeader::computeVoxOffset(const int& version) const
+{
+    int64_t ret;
+    switch (version)
+    {
+        case 1:
+            ret = 4 + sizeof(nifti_1_header);//the 4 is the extender bytes
+            break;
+        case 2:
+            ret = 4 + sizeof(nifti_2_header);
+            break;
+        default:
+            return -1;
+    }
+    int numExtensions = (int)m_extensions.size();
+    for (int i = 0; i < numExtensions; ++i)
+    {
+        CiftiAssert(m_extensions[i] != NULL);
+        int64_t thisSize = 8 + m_extensions[i]->m_bytes.size();//8 is for the int32_t size and ecode for nifti-1 style extensions
+        if (thisSize % 16 != 0)//round up to nearest multiple of 16
+        {
+            int paddingBytes = 16 - (thisSize % 16);
+            thisSize += paddingBytes;
+        }
+        if (thisSize > numeric_limits<int32_t>::max()) return -1;//since we don't have nifti-2 style extensions yet, always fail
+        ret += thisSize;
+    }
+    if (version == 1)//need to put it into a float exactly (yes, really)
+    {
+        float temp = ret;
+        if (ret != (int64_t)temp) return -1;//for now, just fail, until it actually becomes a problem
+    }
+    return ret;
+}
+
+bool NiftiHeader::getDataScaling(double& mult, double& offset) const
+{
+    if (m_header.datatype == NIFTI_TYPE_RGB24 || m_header.scl_slope == 0.0 || (m_header.scl_slope == 1.0 && m_header.scl_inter == 0.0))//the "if slope is zero" case is in the nifti spec
+    {
+        mult = 1.0;//in case someone ignores the boolean
+        offset = 0.0;
+        return false;
+    }
+    mult = m_header.scl_slope;
+    offset = m_header.scl_inter;
+    return true;
+}
+
+vector<int64_t> NiftiHeader::getDimensions() const
+{
+    CiftiAssert(m_header.dim[0] >= 0 && m_header.dim[0] <= 7);//because storage is private and initialized to zero, so it should never be invalid
+    vector<int64_t> ret(m_header.dim[0]);
+    for (int i = 0; i < m_header.dim[0]; ++i)
+    {
+        ret[i] = m_header.dim[i + 1];
+    }
+    return ret;
+}
+
+vector<std::vector<float> > NiftiHeader::getFSLSpace() const
+{//don't look at me, blame analyze and flirt
+    vector<int64_t> dimensions = getDimensions();
+    if (dimensions.size() < 3) throw CiftiException("NiftiHeaderIO has less than 3 dimensions, can't generate the FSL space for it");
+    FloatMatrix ret;
+    vector<vector<float> > sform = getSForm();
+    float determinant = sform[0][0] * sform[1][1] * sform[2][2] +
+                        sform[0][1] * sform[1][2] * sform[2][0] +
+                        sform[0][2] * sform[1][0] * sform[2][1] -
+                        sform[0][2] * sform[1][1] * sform[2][0] -
+                        sform[0][0] * sform[1][2] * sform[2][1] -
+                        sform[0][1] * sform[1][0] * sform[2][2];//just write out the 3x3 determinant rather than packing it into a FloatMatrix first - and I haven't put a determinant function in yet
+    ret = FloatMatrix::identity(4);//generate a 4x4 with 0 0 0 1 last row via FloatMatrix for convenience
+    if (determinant > 0.0f)
+    {
+        ret[0][0] = -m_header.pixdim[1];//yes, they really use pixdim, despite checking the SForm/QForm for flipping - ask them, not me
+        ret[0][3] = (dimensions[0] - 1) * m_header.pixdim[1];//note - pixdim[1] is for i, pixdim[0] is qfac
+    } else {
+        ret[0][0] = m_header.pixdim[1];
+    }
+    ret[1][1] = m_header.pixdim[2];
+    ret[2][2] = m_header.pixdim[3];
+    int32_t spaceUnit = XYZT_TO_SPACE(m_header.xyzt_units);
+    switch (spaceUnit)
+    {
+        case NIFTI_UNITS_METER:
+            ret *= 1000.0f;
+            ret[3][3] = 1.0f;
+            break;
+        case NIFTI_UNITS_MICRON:
+            ret *= 0.001f;
+            ret[3][3] = 1.0f;
+            break;
+        case NIFTI_UNITS_MM:
+            break;
+        default:
+            break;//will already have warned in getSForm()
+    }
+    return ret.getMatrix();
+}
+
+bool NiftiHeader::operator==(const NiftiHeader& rhs) const
+{
+    if (m_version != rhs.m_version) return false;//this is to test for consistency, not to test if two headers mean the same thing
+    if (m_isSwapped != rhs.m_isSwapped) return false;
+    return memcmp(&m_header, &(rhs.m_header), sizeof(m_header)) == 0;
+}
+
+vector<vector<float> > NiftiHeader::getSForm() const
+{
+    FloatMatrix ret = FloatMatrix::zeros(4, 4);
+    ret[3][3] = 1.0f;//force 0 0 0 1 last row
+    if (m_header.sform_code != 0)//prefer sform
+    {
+        for(int i = 0; i < 4; i++)
+        {
+            ret[0][i] = m_header.srow_x[i];
+            ret[1][i] = m_header.srow_y[i];
+            ret[2][i] = m_header.srow_z[i];
+        }
+    } else if (m_header.qform_code != 0) {//fall back to qform
+        float rotmat[3][3], quat[4];
+        quat[1] = m_header.quatern_b;
+        quat[2] = m_header.quatern_c;
+        quat[3] = m_header.quatern_d;
+        float checkquat = quat[1] * quat[1] + quat[2] * quat[2] + quat[3] * quat[3];
+        if (checkquat <= 1.01f)//make sure qform is sane
+        {
+            if (checkquat > 1.0f)
+            {
+                quat[0] = 0.0f;
+            } else {
+                quat[0] = sqrt(1.0f - checkquat);
+            }
+            MathFunctions::quaternToMatrix(quat, rotmat);
+            for (int i = 0; i < 3; ++i)
+            {
+                for (int j = 0; j < 3; ++j)
+                {
+                    rotmat[i][j] *= m_header.pixdim[i + 1];
+                }
+            }
+            if (m_header.pixdim[0] < 0.0f)//left handed coordinate system, flip the kvec
+            {
+                rotmat[0][2] = -rotmat[0][2];
+                rotmat[1][2] = -rotmat[1][2];
+                rotmat[2][2] = -rotmat[2][2];
+            }
+            for (int i = 0; i < 3; ++i)
+            {
+                for (int j = 0; j < 3; ++j)
+                {
+                    ret[i][j] = rotmat[i][j];
+                }
+            }
+            ret[0][3] = m_header.qoffset_x;
+            ret[1][3] = m_header.qoffset_y;
+            ret[2][3] = m_header.qoffset_z;
+        } else {
+            cerr << "found quaternion with length greater than 1 in nifti header" << endl;
+            ret[0][0] = m_header.pixdim[1];
+            ret[1][1] = m_header.pixdim[2];
+            ret[2][2] = m_header.pixdim[3];
+        }
+    } else {//fall back to analyze and complain
+        cerr << "no sform or qform code found, using ANALYZE coordinates!" << endl;
+        ret[0][0] = m_header.pixdim[1];
+        ret[1][1] = m_header.pixdim[2];
+        ret[2][2] = m_header.pixdim[3];
+    }
+    int32_t spaceUnit = XYZT_TO_SPACE(m_header.xyzt_units);
+    switch (spaceUnit)
+    {
+        case NIFTI_UNITS_METER:
+            ret *= 1000.0f;
+            ret[3][3] = 1.0f;
+            break;
+        case NIFTI_UNITS_MICRON:
+            ret *= 0.001f;
+            ret[3][3] = 1.0f;
+            break;
+        case 0://assume millimeters if unspecified, could give a warning
+        case NIFTI_UNITS_MM:
+            break;
+        default:
+            cerr << "unrecognized spatial unit in nifti header" << endl;
+    }
+    return ret.getMatrix();
+}
+
+AString NiftiHeader::toString() const
+{
+    AString ret;
+    if (isSwapped())
+    {
+        ret += "native endian: false\n";
+    } else {
+        ret += "native endian: true\n";
+    }
+    ret += "sizeof_hdr: " + AString_number(m_header.sizeof_hdr) + "\n";//skip the fields that aren't important, like intent_p1, cal_max, etc
+    ret += "magic: " + AString_from_latin1(m_header.magic, 8) + "\n";
+    ret += "datatype: " + AString_number(m_header.datatype) + "\n";
+    ret += "bitpix: " + AString_number(m_header.bitpix) + "\n";
+    CiftiAssert(m_header.dim[0] < 8);
+    for (int i = 0; i <= m_header.dim[0]; ++i)
+    {
+        ret += "dim[" + AString_number(i) + "]: " + AString_number(m_header.dim[i]) + "\n";
+    }
+    for (int i = 0; i <= m_header.dim[0]; ++i)
+    {
+        ret += "pixdim[" + AString_number(i) + "]: " + AString_number(m_header.pixdim[i]) + "\n";
+    }
+    ret += "vox_offset: " + AString_number(m_header.vox_offset) + "\n";
+    ret += "scl_slope: " + AString_number(m_header.scl_slope) + "\n";
+    ret += "scl_inter: " + AString_number(m_header.scl_inter) + "\n";
+    ret += "sform_code: " + AString_number(m_header.sform_code) + "\n";
+    if (m_header.sform_code != NIFTI_XFORM_UNKNOWN)
+    {
+        ret += "srow_x:";
+        for (int i = 0; i < 4; ++i)
+        {
+            ret += " " + AString_number(m_header.srow_x[i]);
+        }
+        ret += "\nsrow_y:";
+        for (int i = 0; i < 4; ++i)
+        {
+            ret += " " + AString_number(m_header.srow_y[i]);
+        }
+        ret += "\nsrow_z:";
+        for (int i = 0; i < 4; ++i)
+        {
+            ret += " " + AString_number(m_header.srow_z[i]);
+        }
+        ret += "\n";
+    }
+    ret += "qform_code: " + AString_number(m_header.qform_code) + "\n";
+    if (m_header.qform_code != NIFTI_XFORM_UNKNOWN)
+    {
+        ret += "quatern_b: " + AString_number(m_header.quatern_b) + "\n";
+        ret += "quatern_c: " + AString_number(m_header.quatern_c) + "\n";
+        ret += "quatern_d: " + AString_number(m_header.quatern_d) + "\n";
+        ret += "qoffset_x: " + AString_number(m_header.qoffset_x) + "\n";
+        ret += "qoffset_y: " + AString_number(m_header.qoffset_y) + "\n";
+        ret += "qoffset_z: " + AString_number(m_header.qoffset_z) + "\n";
+    }
+    ret += "xyzt_units: " + AString_number(m_header.xyzt_units) + "\n";
+    ret += "intent_code: " + AString_number(m_header.intent_code) + "\n";
+    ret += "intent_name: " + AString_from_latin1(m_header.intent_name, 16) + "\n";
+    int numExts = (int)m_extensions.size();
+    ret += AString_number(numExts) + " extension";
+    if (numExts != 1) ret += "s";
+    if (numExts == 0)
+    {
+        ret += "\n";
+    } else {
+        ret += ":\n";
+        for (int i = 0; i < numExts; ++i)
+        {
+            CiftiAssert(m_extensions[i] != NULL);
+            ret += "\n";
+            ret += "code: " + AString_number(m_extensions[i]->m_ecode) + "\n";
+            ret += "length: " + AString_number(m_extensions[i]->m_bytes.size()) + "\n";
+        }
+    }
+    return ret;
+}
+
+void NiftiHeader::setDataType(const int16_t& type)
+{
+    m_header.bitpix = typeToNumBits(m_header.datatype);//to check for errors
+    m_header.datatype = type;
+}
+
+void NiftiHeader::setDimensions(const vector<int64_t>& dimsIn)
+{
+    if (dimsIn.size() > 7 || dimsIn.empty()) throw CiftiException("Number of dimensions must be between 1 and 7, inclusive.");
+    m_header.dim[0] = dimsIn.size();
+    int i = 0;
+    for(; i < (int)dimsIn.size(); i++)
+    {
+        if (dimsIn[i] < 1) throw CiftiException("all dimension lengths must be positive");//maybe these should be asserts?
+        m_header.dim[i + 1] = dimsIn[i];
+    }
+    for (; i < 7; ++i)
+    {
+        m_header.dim[i + 1] = 1;//we maintain 1s on unused dimensions to make some stupid nifti readers happy
+    }
+}
+
+void NiftiHeader::setIntent(const int32_t& code, const char name[16])
+{
+    m_header.intent_code = code;
+    int i;//custom strncpy-like code to fill nulls to the end
+    for (i = 0; i < 16 && name[i] != '\0'; ++i) m_header.intent_name[i] = name[i];
+    for (; i < 16; ++i) m_header.intent_name[i] = '\0';
+}
+
+void NiftiHeader::setSForm(const vector<vector<float> >& sForm)
+{
+    CiftiAssert(sForm.size() >= 3);//programmer error to pass badly sized matrix
+    if (sForm.size() < 3) throw CiftiException("internal error: setSForm matrix badly sized");//but make release also throw
+    for (int i = 0; i < (int)sForm.size(); i++)
+    {
+        CiftiAssert(sForm[i].size() >= 4);//ditto
+        if (sForm[i].size() < 4) throw CiftiException("internal error: setSForm matrix badly sized");
+    }
+    m_header.xyzt_units = SPACE_TIME_TO_XYZT(NIFTI_UNITS_MM, NIFTI_UNITS_SEC);//overwrite whatever units we read in
+    for (int i = 0; i < 4; i++)
+    {
+        m_header.srow_x[i] = sForm[0][i];
+        m_header.srow_y[i] = sForm[1][i];
+        m_header.srow_z[i] = sForm[2][i];
+    }
+    m_header.sform_code = NIFTI_XFORM_SCANNER_ANAT;
+    Vector3D ivec, jvec, kvec;
+    ivec[0] = sForm[0][0]; ivec[1] = sForm[1][0]; ivec[2] = sForm[2][0];
+    jvec[0] = sForm[0][1]; jvec[1] = sForm[1][1]; jvec[2] = sForm[2][1];
+    kvec[0] = sForm[0][2]; kvec[1] = sForm[1][2]; kvec[2] = sForm[2][2];
+    m_header.pixdim[0] = 1.0f;
+    m_header.pixdim[1] = ivec.length();
+    m_header.pixdim[2] = jvec.length();
+    m_header.pixdim[3] = kvec.length();
+    ivec = ivec.normal();
+    jvec = jvec.normal();
+    kvec = kvec.normal();
+    if (kvec.dot(ivec.cross(jvec)) < 0.0f)//left handed sform!
+    {
+        m_header.pixdim[0] = -1.0f;
+        kvec = -kvec;//because to nifti, "left handed" apparently means "up is down", not "left is right"
+    }
+    float rotmat[3][3];
+    rotmat[0][0] = ivec[0]; rotmat[1][0] = jvec[0]; rotmat[2][0] = kvec[0];
+    rotmat[0][1] = ivec[1]; rotmat[1][1] = jvec[1]; rotmat[2][1] = kvec[1];
+    rotmat[0][2] = ivec[2]; rotmat[1][2] = jvec[2]; rotmat[2][2] = kvec[2];
+    float quat[4];
+    if (!MathFunctions::matrixToQuatern(rotmat, quat))
+    {
+        m_header.qform_code = NIFTI_XFORM_UNKNOWN;//0, implies that there is no qform
+        m_header.quatern_b = 0.0;//set dummy values anyway
+        m_header.quatern_c = 0.0;
+        m_header.quatern_d = 0.0;
+        m_header.qoffset_x = sForm[0][3];
+        m_header.qoffset_y = sForm[1][3];
+        m_header.qoffset_z = sForm[2][3];
+    } else {
+        m_header.qform_code = NIFTI_XFORM_SCANNER_ANAT;
+        m_header.quatern_b = quat[1];
+        m_header.quatern_c = quat[2];
+        m_header.quatern_d = quat[3];
+        m_header.qoffset_x = sForm[0][3];
+        m_header.qoffset_y = sForm[1][3];
+        m_header.qoffset_z = sForm[2][3];
+    }
+}
+
+void NiftiHeader::clearDataScaling()
+{
+    m_header.scl_slope = 1.0;
+    m_header.scl_inter = 0.0;
+}
+
+void NiftiHeader::setDataScaling(const double& mult, const double& offset)
+{
+    m_header.scl_slope = mult;
+    m_header.scl_inter = offset;
+}
+
+void NiftiHeader::read(BinaryFile& inFile)
+{
+    nifti_1_header buffer1;
+    nifti_2_header buffer2;
+    inFile.read(&buffer1, sizeof(nifti_1_header));
+    int version = NIFTI2_VERSION(buffer1);
+    bool swapped = false;
+    try
+    {
+        if (version == 2)
+        {
+            memcpy(&buffer2, &buffer1, sizeof(nifti_1_header));
+            inFile.read(((char*)&buffer2) + sizeof(nifti_1_header), sizeof(nifti_2_header) - sizeof(nifti_1_header));
+            if (NIFTI2_NEEDS_SWAP(buffer2))
+            {
+                swapped = true;
+                swapHeaderBytes(buffer2);
+            }
+            setupFrom(buffer2);
+        } else if (version == 1) {
+            if (NIFTI2_NEEDS_SWAP(buffer1))//yes, this works on nifti-1 also
+            {
+                swapped = true;
+                swapHeaderBytes(buffer1);
+            }
+            setupFrom(buffer1);
+        } else {
+            throw CiftiException(inFile.getFilename() + " is not a valid NIfTI file");
+        }
+    } catch (CiftiException& e) {//catch and throw in order to add filename info
+        throw CiftiException("error reading NIfTI file " + inFile.getFilename() + ": " + e.whatString());
+    }
+    m_extensions.clear();
+    char extender[4];
+    inFile.read(extender, 4);
+    int extensions = 0;//if it has extensions in a format we don't know about, don't try to read them
+    if (version == 1 && extender[0] != 0) extensions = 1;//sadly, this is the only thing nifti-1 says about the extender bytes
+    if (version == 2 && extender[0] == 1 && extender[1] == 0 && extender[2] == 0 && extender[3] == 0) extensions = 1;//from http://nifti.nimh.nih.gov/nifti-2 as of 4/4/2014:
+    if (extensions == 1)//"extentions match those of NIfTI-1.1 when the extender bytes are 1 0 0 0"
+    {
+        int64_t extStart;
+        if (version == 1)
+        {
+            extStart = 352;
+        } else {
+            CiftiAssert(version == 2);
+            extStart = 544;
+        }
+        CiftiAssert(inFile.pos() == extStart);
+        while(extStart + 2 * sizeof(int32_t) <= (size_t)m_header.vox_offset)
+        {
+            int32_t esize, ecode;
+            inFile.read(&esize, sizeof(int32_t));
+            if (swapped) ByteSwapping::swap(esize);
+            inFile.read(&ecode, sizeof(int32_t));
+            if (swapped) ByteSwapping::swap(ecode);
+            if (esize < 8 || esize + extStart > m_header.vox_offset) break;
+            boost::shared_ptr<NiftiExtension> tempExtension(new NiftiExtension());
+            if ((size_t)esize > 2 * sizeof(int32_t))//don't try to read 0 bytes
+            {
+                tempExtension->m_bytes.resize(esize - 2 * sizeof(int32_t));
+                inFile.read(tempExtension->m_bytes.data(), esize - 2 * sizeof(int32_t));
+            }
+            tempExtension->m_ecode = ecode;
+            m_extensions.push_back(tempExtension);
+            extStart += esize;//esize includes the two int32_ts
+        }
+    }
+    m_isSwapped = swapped;//now that we know there were no errors (because they throw), complete the internal state
+    m_version = version;
+}
+
+void NiftiHeader::setupFrom(const nifti_1_header& header)
+{
+    if (header.sizeof_hdr != sizeof(nifti_1_header)) throw CiftiException("incorrect sizeof_hdr");
+    const char magic[] = "n+1\0";//only support single-file nifti
+    if (strncmp(header.magic, magic, 4) != 0) throw CiftiException("incorrect magic");
+    if (header.dim[0] < 1 || header.dim[0] > 7) throw CiftiException("incorrect dim[0]");
+    for (int i = 0; i < header.dim[0]; ++i)
+    {
+        if (header.dim[i + 1] < 1) throw CiftiException("dim[" + AString_number(i + 1) + "] < 1");
+    }
+    if (header.vox_offset < 352) throw CiftiException("incorrect vox_offset");
+    int numBits = typeToNumBits(header.datatype);
+    if (header.bitpix != numBits) throw CiftiException("datatype disagrees with bitpix");
+    m_header.sizeof_hdr = header.sizeof_hdr;//copy in everything, so we don't have to fake anything to print the header as read
+    for (int i = 0; i < 4; ++i)//mostly using nifti-2 field order to make it easier to find if things are missed
+    {
+        m_header.magic[i] = header.magic[i];
+        m_header.srow_x[i] = header.srow_x[i];//slight hack - nifti-1 magic and srows both happen to be 4 long
+        m_header.srow_y[i] = header.srow_y[i];
+        m_header.srow_z[i] = header.srow_z[i];
+    }
+    m_header.datatype = header.datatype;
+    m_header.bitpix = header.bitpix;
+    for (int i = 0; i < 8; ++i)
+    {
+        m_header.dim[i] = header.dim[i];
+        m_header.pixdim[i] = header.pixdim[i];
+    }
+    m_header.intent_p1 = header.intent_p1;
+    m_header.intent_p2 = header.intent_p2;
+    m_header.intent_p3 = header.intent_p3;
+    m_header.vox_offset = header.vox_offset;//technically, this could result in integer overflow, if the header extensions total exabytes in size
+    m_header.scl_slope = header.scl_slope;
+    m_header.scl_inter = header.scl_inter;
+    m_header.cal_max = header.cal_max;
+    m_header.cal_min = header.cal_min;
+    m_header.slice_duration = header.slice_duration;
+    m_header.toffset = header.toffset;
+    m_header.slice_start = header.slice_start;
+    m_header.slice_end = header.slice_end;
+    for (int i = 0; i < 80; ++i) m_header.descrip[i] = header.descrip[i];
+    for (int i = 0; i < 24; ++i) m_header.aux_file[i] = header.aux_file[i];
+    m_header.qform_code = header.qform_code;
+    m_header.sform_code = header.sform_code;
+    m_header.quatern_b = header.quatern_b;
+    m_header.quatern_c = header.quatern_c;
+    m_header.quatern_d = header.quatern_d;
+    m_header.qoffset_x = header.qoffset_x;
+    m_header.qoffset_y = header.qoffset_y;
+    m_header.qoffset_z = header.qoffset_z;
+    m_header.slice_code = header.slice_code;
+    m_header.xyzt_units = header.xyzt_units;
+    m_header.intent_code = header.intent_code;
+    for (int i = 0; i < 16; ++i) m_header.intent_name[i] = header.intent_name[i];
+    m_header.dim_info = header.dim_info;
+}
+
+void NiftiHeader::setupFrom(const nifti_2_header& header)
+{
+    if (header.sizeof_hdr != sizeof(nifti_2_header)) throw CiftiException("incorrect sizeof_hdr");
+    const char magic[] = "n+2\0\r\n\032\n";//only support single-file nifti
+    for (int i = 0; i < 8; ++i)
+    {
+        if (header.magic[i] != magic[i]) throw CiftiException("incorrect magic");
+    }
+    if (header.dim[0] < 1 || header.dim[0] > 7) throw CiftiException("incorrect dim[0]");
+    for (int i = 0; i < header.dim[0]; ++i)
+    {
+        if (header.dim[i + 1] < 1) throw CiftiException("dim[" + AString_number(i + 1) + "] < 1");
+    }
+    if (header.vox_offset < 352) throw CiftiException("incorrect vox_offset");
+    if (header.bitpix != typeToNumBits(header.datatype)) throw CiftiException("datatype disagrees with bitpix");
+    memcpy(&m_header, &header, sizeof(nifti_2_header));
+}
+
+int NiftiHeader::typeToNumBits(const int64_t& type)
+{
+    switch (type)
+    {
+        case DT_BINARY:
+            return 1;
+            break;
+        case NIFTI_TYPE_INT8:
+        case NIFTI_TYPE_UINT8:
+            return 8;
+            break;
+        case NIFTI_TYPE_INT16:
+        case NIFTI_TYPE_UINT16:
+            return 16;
+            break;
+        case NIFTI_TYPE_RGB24:
+            return 24;
+            break;
+        case NIFTI_TYPE_INT32:
+        case NIFTI_TYPE_UINT32:
+        case NIFTI_TYPE_FLOAT32:
+            return 32;
+            break;
+        case NIFTI_TYPE_INT64:
+        case NIFTI_TYPE_UINT64:
+        case NIFTI_TYPE_FLOAT64:
+        case NIFTI_TYPE_COMPLEX64:
+            return 64;
+            break;
+        case NIFTI_TYPE_FLOAT128:
+        case NIFTI_TYPE_COMPLEX128:
+            return 128;
+            break;
+        case NIFTI_TYPE_COMPLEX256:
+            return 256;
+            break;
+        default:
+            throw CiftiException("incorrect datatype code");
+    }
+}
+
+void NiftiHeader::swapHeaderBytes(nifti_1_header& header)
+{
+    ByteSwapping::swap(header.sizeof_hdr);//by order of fields in nifti-1 header, skip unused because we don't store their data
+    ByteSwapping::swapArray(header.dim, 8);
+    ByteSwapping::swap(header.intent_p1);
+    ByteSwapping::swap(header.intent_p2);
+    ByteSwapping::swap(header.intent_p3);
+    ByteSwapping::swap(header.intent_code);
+    ByteSwapping::swap(header.datatype);
+    ByteSwapping::swap(header.bitpix);
+    ByteSwapping::swap(header.slice_start);
+    ByteSwapping::swapArray(header.pixdim, 8);
+    ByteSwapping::swap(header.vox_offset);
+    ByteSwapping::swap(header.scl_slope);
+    ByteSwapping::swap(header.scl_inter);
+    ByteSwapping::swap(header.slice_end);
+    ByteSwapping::swap(header.cal_max);
+    ByteSwapping::swap(header.cal_min);
+    ByteSwapping::swap(header.slice_duration);
+    ByteSwapping::swap(header.toffset);
+    ByteSwapping::swap(header.qform_code);
+    ByteSwapping::swap(header.sform_code);
+    ByteSwapping::swap(header.quatern_b);
+    ByteSwapping::swap(header.quatern_c);
+    ByteSwapping::swap(header.quatern_d);
+    ByteSwapping::swap(header.qoffset_x);
+    ByteSwapping::swap(header.qoffset_y);
+    ByteSwapping::swap(header.qoffset_z);
+    ByteSwapping::swapArray(header.srow_x, 4);
+    ByteSwapping::swapArray(header.srow_y, 4);
+    ByteSwapping::swapArray(header.srow_z, 4);
+}
+
+void NiftiHeader::swapHeaderBytes(nifti_2_header& header)
+{
+    ByteSwapping::swap(header.sizeof_hdr);//by order of fields in nifti-2 header
+    ByteSwapping::swap(header.datatype);
+    ByteSwapping::swap(header.bitpix);
+    ByteSwapping::swapArray(header.dim, 8);
+    ByteSwapping::swap(header.intent_p1);
+    ByteSwapping::swap(header.intent_p2);
+    ByteSwapping::swap(header.intent_p3);
+    ByteSwapping::swapArray(header.pixdim, 8);
+    ByteSwapping::swap(header.vox_offset);
+    ByteSwapping::swap(header.scl_slope);
+    ByteSwapping::swap(header.scl_inter);
+    ByteSwapping::swap(header.cal_max);
+    ByteSwapping::swap(header.cal_min);
+    ByteSwapping::swap(header.slice_duration);
+    ByteSwapping::swap(header.toffset);
+    ByteSwapping::swap(header.slice_start);
+    ByteSwapping::swap(header.slice_end);
+    ByteSwapping::swap(header.qform_code);
+    ByteSwapping::swap(header.sform_code);
+    ByteSwapping::swap(header.quatern_b);
+    ByteSwapping::swap(header.quatern_c);
+    ByteSwapping::swap(header.quatern_d);
+    ByteSwapping::swap(header.qoffset_x);
+    ByteSwapping::swap(header.qoffset_y);
+    ByteSwapping::swap(header.qoffset_z);
+    ByteSwapping::swapArray(header.srow_x, 4);
+    ByteSwapping::swapArray(header.srow_y, 4);
+    ByteSwapping::swapArray(header.srow_z, 4);
+    ByteSwapping::swap(header.slice_code);
+    ByteSwapping::swap(header.xyzt_units);
+    ByteSwapping::swap(header.intent_code);
+}
+
+void NiftiHeader::write(BinaryFile& outFile, const int& version, const bool& swapEndian)
+{
+    if (!canWriteVersion(version)) throw CiftiException("unable to write NIfTI version " + AString_number(version) + " for file " + outFile.getFilename());
+    const char padding[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+    int64_t voxOffset;
+    if (version == 2)
+    {
+        nifti_2_header outHeader;
+        prepareHeader(outHeader);
+        voxOffset = outHeader.vox_offset;
+        if (swapEndian) swapHeaderBytes(outHeader);
+        outFile.write(&outHeader, sizeof(nifti_2_header));
+    } else if (version == 1) {
+        nifti_1_header outHeader;
+        prepareHeader(outHeader);
+        voxOffset = outHeader.vox_offset;
+        if (swapEndian) swapHeaderBytes(outHeader);
+        outFile.write(&outHeader, sizeof(nifti_1_header));
+    } else {
+        CiftiAssert(0);//canWriteVersion should have said no
+        throw CiftiException("internal error: NiftiHeader::canWriteVersion() returned true for unimplemented writing version");
+    }
+    char extender[] = { 0, 0, 0, 0 };//at least until nifti-2 gets a new extension format, use the same code for both
+    int numExtensions = (int)m_extensions.size();
+    if (numExtensions != 0) extender[0] = 1;
+    outFile.write(extender, 4);
+    for (int i = 0; i < numExtensions; ++i)
+    {
+        CiftiAssert(m_extensions[i] != NULL);
+        int64_t thisSize = 8 + m_extensions[i]->m_bytes.size();//8 is for the int32_t size and ecode for nifti-1 style extensions
+        int paddingBytes = 0;
+        if (thisSize % 16 != 0)//round up to nearest multiple of 16
+        {
+            paddingBytes = 16 - (thisSize % 16);
+            thisSize += paddingBytes;
+        }
+        CiftiAssert(thisSize <= numeric_limits<int32_t>::max());
+        CiftiAssert(thisSize + outFile.pos() <= voxOffset);
+        int32_t outSize = thisSize;
+        int32_t outEcode = m_extensions[i]->m_ecode;
+        if (swapEndian)
+        {
+            ByteSwapping::swap(outSize);
+            ByteSwapping::swap(outEcode);
+        }
+        outFile.write(&outSize, sizeof(int32_t));
+        outFile.write(&outEcode, sizeof(int32_t));
+        outFile.write(m_extensions[i]->m_bytes.data(), m_extensions[i]->m_bytes.size());
+        if (paddingBytes != 0) outFile.write(padding, paddingBytes);
+    }
+    CiftiAssert(outFile.pos() == voxOffset);
+    m_header.vox_offset = voxOffset;//update internal state to reflect the state that was written to the file
+    m_version = version;
+    m_isSwapped = swapEndian;
+}
+
+void NiftiHeader::prepareHeader(nifti_1_header& header) const
+{
+    CiftiAssert(canWriteVersion(1));//programmer error to call this if it isn't possible
+    header.sizeof_hdr = sizeof(nifti_1_header);//do static things first
+    const char magic[] = "n+1\0";//only support single-file nifti
+    for (int i = 0; i < 4; ++i) header.magic[i] = magic[i];
+    for (int i = 0; i < 10; ++i) header.data_type[i] = 0;//then zero unused things
+    for (int i = 0; i < 18; ++i) header.db_name[i] = 0;
+    header.extents = 0;
+    header.session_error = 0;
+    header.regular = 0;
+    header.glmax = 0;
+    header.glmin = 0;
+    header.dim_info = m_header.dim_info;//by order of fields in nifti-1 header, skipping unused and static
+    for (int i = 0; i < 8; ++i) header.dim[i] = m_header.dim[i];//canWriteVersion should have already checked that this is okay, first in write(), then asserted above
+    header.intent_p1 = m_header.intent_p1;//theoretically, this could be a problem wih large exponents, or if extremely high precision is required
+    header.intent_p2 = m_header.intent_p2;//but we don't use them at all currently, so we don't care
+    header.intent_p3 = m_header.intent_p3;
+    header.intent_code = m_header.intent_code;
+    header.datatype = m_header.datatype;
+    header.bitpix = typeToNumBits(m_header.datatype);//in case we ever accept wrong bitpix with a warning, NEVER write wrong bitpix
+    header.slice_start = m_header.slice_start;
+    for (int i = 0; i < 8; ++i) header.pixdim[i] = m_header.pixdim[i];//more double to float conversion
+    header.vox_offset = computeVoxOffset(1);//again, canWriteVersion should have checked that this, and later conversions, are okay
+    CiftiAssert(header.vox_offset >= 352);
+    header.scl_slope = m_header.scl_slope;
+    header.scl_inter = m_header.scl_inter;
+    header.slice_end = m_header.slice_end;
+    header.slice_code = m_header.slice_code;
+    header.xyzt_units = m_header.xyzt_units;
+    header.cal_min = m_header.cal_min;
+    header.cal_max = m_header.cal_max;
+    header.slice_duration = m_header.slice_duration;
+    header.toffset = m_header.toffset;
+    for (int i = 0; i < 80; ++i) header.descrip[i] = m_header.descrip[i];
+    for (int i = 0; i < 24; ++i) header.aux_file[i] = m_header.aux_file[i];
+    header.qform_code = m_header.qform_code;
+    header.sform_code = m_header.sform_code;
+    header.quatern_b = m_header.quatern_b;
+    header.quatern_c = m_header.quatern_c;
+    header.quatern_d = m_header.quatern_d;
+    header.qoffset_x = m_header.qoffset_x;
+    header.qoffset_y = m_header.qoffset_y;
+    header.qoffset_z = m_header.qoffset_z;
+    for (int i = 0; i < 4; ++i)
+    {
+        header.srow_x[i] = m_header.srow_x[i];
+        header.srow_y[i] = m_header.srow_y[i];
+        header.srow_z[i] = m_header.srow_z[i];
+    }
+    for (int i = 0; i < 16; ++i) header.intent_name[i] = m_header.intent_name[i];
+}
+
+void NiftiHeader::prepareHeader(nifti_2_header& header) const
+{
+    CiftiAssert(canWriteVersion(2));
+    memcpy(&header, &m_header, sizeof(nifti_2_header));//first copy everything, then fix static and computed fields
+    header.sizeof_hdr = sizeof(nifti_2_header);
+    const char magic[] = "n+2\0\r\n\032\n";
+    for (int i = 0; i < 8; ++i) header.magic[i] = magic[i];
+    header.bitpix = typeToNumBits(header.datatype);
+    header.vox_offset = computeVoxOffset(2);
+    for (int i = 0; i < 15; ++i) header.unused_str[i] = 0;//in case we read in a header where these bytes weren't zero
+}
diff --git a/src/Nifti/NiftiHeader.h b/src/Nifti/NiftiHeader.h
new file mode 100644
index 0000000..910a2bb
--- /dev/null
+++ b/src/Nifti/NiftiHeader.h
@@ -0,0 +1,95 @@
+#ifndef __NIFTI_HEADER_H__
+#define __NIFTI_HEADER_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Common/BinaryFile.h"
+
+#include "nifti1.h"
+#include "nifti2.h"
+
+#include "boost/shared_ptr.hpp"
+#include <vector>
+
+namespace cifti
+{
+    
+    struct NiftiExtension
+    {
+        int32_t m_ecode;
+        std::vector<char> m_bytes;
+    };
+    
+    struct NiftiHeader
+    {
+        std::vector<boost::shared_ptr<NiftiExtension> > m_extensions;//allow direct access to the extensions
+        
+        NiftiHeader();
+        void read(BinaryFile& inFile);
+        void write(BinaryFile& outFile, const int& version = 1, const bool& swapEndian = false);
+        bool canWriteVersion(const int& version) const;
+        bool isSwapped() const { return m_isSwapped; }
+        int version() const { return m_version; }
+        
+        std::vector<int64_t> getDimensions() const;
+        std::vector<std::vector<float> > getSForm() const;
+        int64_t getDataOffset() const { return m_header.vox_offset; }
+        int16_t getDataType() const { return m_header.datatype; }
+        int32_t getIntentCode() const { return m_header.intent_code; }
+        const char* getIntentName() const { return m_header.intent_name; }//NOTE: 16 BYTES, MAY NOT HAVE A NULL TERMINATOR
+        bool getDataScaling(double& mult, double& offset) const;//returns false if scaling not needed
+        AString toString() const;
+        
+        void setDimensions(const std::vector<int64_t>& dimsIn);
+        void setSForm(const std::vector<std::vector<float> > &sForm);
+        void setIntent(const int32_t& code, const char name[16]);
+        void setDataType(const int16_t& type);
+        void clearDataScaling();
+        void setDataScaling(const double& mult, const double& offset);
+        ///get the FSL "scale" space
+        std::vector<std::vector<float> > getFSLSpace() const;
+        
+        bool operator==(const NiftiHeader& rhs) const;//for testing purposes
+        bool operator!=(const NiftiHeader& rhs) const { return !((*this) == rhs); }
+    private:
+        nifti_2_header m_header;//storage for header values regardless of version
+        int m_version;
+        bool m_isSwapped;
+        static void swapHeaderBytes(nifti_1_header &header);
+        static void swapHeaderBytes(nifti_2_header &header);
+        void prepareHeader(nifti_1_header& header) const;//transform internal state into ready to write header struct
+        void prepareHeader(nifti_2_header& header) const;
+        void setupFrom(const nifti_1_header& header);//error check provided header, and populate members from it
+        void setupFrom(const nifti_2_header& header);
+        static int typeToNumBits(const int64_t& type);
+        int64_t computeVoxOffset(const int& version) const;
+    };
+    
+}
+
+#endif //__NIFTI_HEADER_H__
diff --git a/src/Nifti/nifti1.h b/src/Nifti/nifti1.h
new file mode 100755
index 0000000..68cde5c
--- /dev/null
+++ b/src/Nifti/nifti1.h
@@ -0,0 +1,1468 @@
+#ifndef _NIFTI_HEADER_
+#define _NIFTI_HEADER_
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * NOTE: our version is somewhat modified, turning constant #defines into const int32_t, namespacing, removal of some other #defines, to try to coexist with the original header
+*/
+namespace cifti
+{
+
+/*****************************************************************************
+      ** This file defines the "NIFTI-1" header format.               **
+      ** It is derived from 2 meetings at the NIH (31 Mar 2003 and    **
+      ** 02 Sep 2003) of the Data Format Working Group (DFWG),        **
+      ** chartered by the NIfTI (Neuroimaging Informatics Technology  **
+      ** Initiative) at the National Institutes of Health (NIH).      **
+      **--------------------------------------------------------------**
+      ** Neither the National Institutes of Health (NIH), the DFWG,   **
+      ** nor any of the members or employees of these institutions    **
+      ** imply any warranty of usefulness of this material for any    **
+      ** purpose, and do not assume any liability for damages,        **
+      ** incidental or otherwise, caused by any use of this document. **
+      ** If these conditions are not acceptable, do not use this!     **
+      **--------------------------------------------------------------**
+      ** Author:   Robert W Cox (NIMH, Bethesda)                      **
+      ** Advisors: John Ashburner (FIL, London),                      **
+      **           Stephen Smith (FMRIB, Oxford),                     **
+      **           Mark Jenkinson (FMRIB, Oxford)                     **
+******************************************************************************/
+
+/*---------------------------------------------------------------------------*/
+/* Note that the ANALYZE 7.5 file header (dbh.h) is
+         (c) Copyright 1986-1995
+         Biomedical Imaging Resource
+         Mayo Foundation
+   Incorporation of components of dbh.h are by permission of the
+   Mayo Foundation.
+
+   Changes from the ANALYZE 7.5 file header in this file are released to the
+   public domain, including the functional comments and any amusing asides.
+-----------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------*/
+/*! INTRODUCTION TO NIFTI-1:
+   ------------------------
+   The twin (and somewhat conflicting) goals of this modified ANALYZE 7.5
+   format are:
+    (a) To add information to the header that will be useful for functional
+        neuroimaging data analysis and display.  These additions include:
+        - More basic data types.
+        - Two affine transformations to specify voxel coordinates.
+        - "Intent" codes and parameters to describe the meaning of the data.
+        - Affine scaling of the stored data values to their "true" values.
+        - Optional storage of the header and image data in one file (.nii).
+    (b) To maintain compatibility with non-NIFTI-aware ANALYZE 7.5 compatible
+        software (i.e., such a program should be able to do something useful
+        with a NIFTI-1 dataset -- at least, with one stored in a traditional
+        .img/.hdr file pair).
+
+   Most of the unused fields in the ANALYZE 7.5 header have been taken,
+   and some of the lesser-used fields have been co-opted for other purposes.
+   Notably, most of the data_history substructure has been co-opted for
+   other purposes, since the ANALYZE 7.5 format describes this substructure
+   as "not required".
+
+   NIFTI-1 FLAG (MAGIC STRINGS):
+   ----------------------------
+   To flag such a struct as being conformant to the NIFTI-1 spec, the last 4
+   bytes of the header must be either the C String "ni1" or "n+1";
+   in hexadecimal, the 4 bytes
+     6E 69 31 00   or   6E 2B 31 00
+   (in any future version of this format, the '1' will be upgraded to '2',
+   etc.).  Normally, such a "magic number" or flag goes at the start of the
+   file, but trying to avoid clobbering widely-used ANALYZE 7.5 fields led to
+   putting this marker last.  However, recall that "the last shall be first"
+   (Matthew 20:16).
+
+   If a NIFTI-aware program reads a header file that is NOT marked with a
+   NIFTI magic string, then it should treat the header as an ANALYZE 7.5
+   structure.
+
+   NIFTI-1 FILE STORAGE:
+   --------------------
+   "ni1" means that the image data is stored in the ".img" file corresponding
+   to the header file (starting at file offset 0).
+
+   "n+1" means that the image data is stored in the same file as the header
+   information.  We recommend that the combined header+data filename suffix
+   be ".nii".  When the dataset is stored in one file, the first byte of image
+   data is stored at byte location (int)vox_offset in this combined file.
+   The minimum allowed value of vox_offset is 352; for compatibility with
+   some software, vox_offset should be an integral multiple of 16.
+
+   GRACE UNDER FIRE:
+   ----------------
+   Most NIFTI-aware programs will only be able to handle a subset of the full
+   range of datasets possible with this format.  All NIFTI-aware programs
+   should take care to check if an input dataset conforms to the program's
+   needs and expectations (e.g., check datatype, intent_code, etc.).  If the
+   input dataset can't be handled by the program, the program should fail
+   gracefully (e.g., print a useful warning; not crash).
+
+   SAMPLE CODES:
+   ------------
+   The associated files nifti1_io.h and nifti1_io.c provide a sample
+   implementation in C of a set of functions to read, write, and manipulate
+   NIFTI-1 files.  The file nifti1_test.c is a sample program that uses
+   the nifti1_io.c functions.
+-----------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------*/
+/* HEADER STRUCT DECLARATION:
+   -------------------------
+   In the comments below for each field, only NIFTI-1 specific requirements
+   or changes from the ANALYZE 7.5 format are described.  For convenience,
+   the 348 byte header is described as a single struct, rather than as the
+   ANALYZE 7.5 group of 3 substructs.
+
+   Further comments about the interpretation of various elements of this
+   header are after the data type definition itself.  Fields that are
+   marked as ++UNUSED++ have no particular interpretation in this standard.
+   (Also see the UNUSED FIELDS comment section, far below.)
+
+   The presumption below is that the various C types have particular sizes:
+     sizeof(int) = sizeof(float) = 4 ;  sizeof(short) = 2
+-----------------------------------------------------------------------------*/
+
+//hopefully cross-platform solution to byte padding added by some compilers
+#pragma pack(push)
+#pragma pack(1)
+
+/*! \struct nifti_1_header
+    \brief Data structure defining the fields in the nifti1 header.
+           This binary header should be found at the beginning of a valid
+           NIFTI-1 header file.
+ */
+/*************************/  /************************/
+struct nifti_1_header { /* NIFTI-1 usage         */  /* ANALYZE 7.5 field(s) */
+    /*************************/  /************************/
+
+    /*--- was header_key substruct ---*/
+    int   sizeof_hdr;    /*!< MUST be 348           */  /* int sizeof_hdr;      */ /* 0 */
+    char  data_type[10]; /*!< ++UNUSED++            */  /* char data_type[10];  */ /* 4 */
+    char  db_name[18];   /*!< ++UNUSED++            */  /* char db_name[18];    */ /* 14 */
+    int   extents;       /*!< ++UNUSED++            */  /* int extents;         */ /* 32 */
+    short session_error; /*!< ++UNUSED++            */  /* short session_error; */ /* 36 */
+    char  regular;       /*!< ++UNUSED++            */  /* char regular;        */ /* 38 */
+    char  dim_info;      /*!< MRI slice ordering.   */  /* char hkey_un0;       */ /* 39 */
+
+    /*--- was image_dimension substruct ---*/
+    short dim[8];        /*!< Data array dimensions.*/  /* short dim[8];        */ /* 40 */
+    float intent_p1 ;    /*!< 1st intent parameter. */  /* short unused8;       */ /* 56 */
+    /* short unused9;       */
+    float intent_p2 ;    /*!< 2nd intent parameter. */  /* short unused10;      */ /* 60 */
+    /* short unused11;      */
+    float intent_p3 ;    /*!< 3rd intent parameter. */  /* short unused12;      */ /* 64 */
+    /* short unused13;      */
+    short intent_code ;  /*!< NIFTI_INTENT_* code.  */  /* short unused14;      */ /* 68 */
+    short datatype;      /*!< Defines data type!    */  /* short datatype;      */ /* 70 */
+    short bitpix;        /*!< Number bits/voxel.    */  /* short bitpix;        */ /* 72 */
+    short slice_start;   /*!< First slice index.    */  /* short dim_un0;       */ /* 74 */
+    float pixdim[8];     /*!< Grid spacings.        */  /* float pixdim[8];     */ /* 76 */
+    float vox_offset;    /*!< Offset into .nii file */  /* float vox_offset;    */ /* 108 */
+    float scl_slope ;    /*!< Data scaling: slope.  */  /* float funused1;      */ /* 112 */
+    float scl_inter ;    /*!< Data scaling: offset. */  /* float funused2;      */ /* 116 */
+    short slice_end;     /*!< Last slice index.     */  /* float funused3;      */ /* 120 */
+    char  slice_code ;   /*!< Slice timing order.   */                                  /* 122 */
+    char  xyzt_units ;   /*!< Units of pixdim[1..4] */                                 /* 123 */
+    float cal_max;       /*!< Max display intensity */  /* float cal_max;       */ /* 124 */
+    float cal_min;       /*!< Min display intensity */  /* float cal_min;       */    /* 128 */
+    float slice_duration;/*!< Time for 1 slice.     */  /* float compressed;    */ /* 132 */
+    float toffset;       /*!< Time axis shift.      */  /* float verified;      */          /* 136 */
+    int   glmax;         /*!< ++UNUSED++            */  /* int glmax;           */  /* 140 */
+    int   glmin;         /*!< ++UNUSED++            */  /* int glmin;           */    /* 144 */
+
+    /*--- was data_history substruct ---*/
+    char  descrip[80];   /*!< any text you like.    */  /* char descrip[80];    */ /* 148 */
+    char  aux_file[24];  /*!< auxiliary filename.   */  /* char aux_file[24];   */ /* 228 */
+
+    short qform_code ;   /*!< NIFTI_XFORM_* code.   */  /*-- all ANALYZE 7.5 ---*/ /* 252 */
+    short sform_code ;   /*!< NIFTI_XFORM_* code.   */  /*   fields below here  */ /* 254 */
+    /*   are replaced       */
+    float quatern_b ;    /*!< Quaternion b param.   */             /* 256 */
+    float quatern_c ;    /*!< Quaternion c param.   */             /* 260 */
+    float quatern_d ;    /*!< Quaternion d param.   */            /* 264 */
+    float qoffset_x ;    /*!< Quaternion x shift.   */                 /* 268 */
+    float qoffset_y ;    /*!< Quaternion y shift.   */                 /* 272 */
+    float qoffset_z ;    /*!< Quaternion z shift.   */                 /* 276 */
+
+    float srow_x[4] ;    /*!< 1st row affine transform.   */       /* 280 */
+    float srow_y[4] ;    /*!< 2nd row affine transform.   */      /* 296 */
+    float srow_z[4] ;    /*!< 3rd row affine transform.   */      /* 312 */
+
+    char intent_name[16];/*!< 'name' or meaning of data.  */ /* 328 */
+
+    char magic[4] ;      /*!< MUST be "ni1\0" or "n+1\0". */ /* 344 */
+
+} ;                   /**** 348 bytes total ****/
+
+typedef struct nifti_1_header nifti_1_header ;
+
+/*---------------------------------------------------------------------------*/
+/* HEADER EXTENSIONS:
+   -----------------
+   After the end of the 348 byte header (e.g., after the magic field),
+   the next 4 bytes are a char array field named "extension". By default,
+   all 4 bytes of this array should be set to zero. In a .nii file, these
+   4 bytes will always be present, since the earliest start point for
+   the image data is byte #352. In a separate .hdr file, these bytes may
+   or may not be present. If not present (i.e., if the length of the .hdr
+   file is 348 bytes), then a NIfTI-1 compliant program should use the
+   default value of extension={0,0,0,0}. The first byte (extension[0])
+   is the only value of this array that is specified at present. The other
+   3 bytes are reserved for future use.
+
+   If extension[0] is nonzero, it indicates that extended header information
+   is present in the bytes following the extension array. In a .nii file,
+   this extended header data is before the image data (and vox_offset
+   must be set correctly to allow for this). In a .hdr file, this extended
+   data follows extension and proceeds (potentially) to the end of the file.
+
+   The format of extended header data is weakly specified. Each extension
+   must be an integer multiple of 16 bytes long. The first 8 bytes of each
+   extension comprise 2 integers:
+      int esize , ecode ;
+   These values may need to be byte-swapped, as indicated by dim[0] for
+   the rest of the header.
+     * esize is the number of bytes that form the extended header data
+       + esize must be a positive integral multiple of 16
+       + this length includes the 8 bytes of esize and ecode themselves
+     * ecode is a non-negative integer that indicates the format of the
+       extended header data that follows
+       + different ecode values are assigned to different developer groups
+       + at present, the "registered" values for code are
+         = 0 = unknown private format (not recommended!)
+         = 2 = DICOM format (i.e., attribute tags and values)
+         = 4 = AFNI group (i.e., ASCII XML-ish elements)
+   In the interests of interoperability (a primary rationale for NIfTI),
+   groups developing software that uses this extension mechanism are
+   encouraged to document and publicize the format of their extensions.
+   To this end, the NIfTI DFWG will assign even numbered codes upon request
+   to groups submitting at least rudimentary documentation for the format
+   of their extension; at present, the contact is mailto:rwcox at nih.gov.
+   The assigned codes and documentation will be posted on the NIfTI
+   website. All odd values of ecode (and 0) will remain unassigned;
+   at least, until the even ones are used up, when we get to 2,147,483,646.
+
+   Note that the other contents of the extended header data section are
+   totally unspecified by the NIfTI-1 standard. In particular, if binary
+   data is stored in such a section, its byte order is not necessarily
+   the same as that given by examining dim[0]; it is incumbent on the
+   programs dealing with such data to determine the byte order of binary
+   extended header data.
+
+   Multiple extended header sections are allowed, each starting with an
+   esize,ecode value pair. The first esize value, as described above,
+   is at bytes #352-355 in the .hdr or .nii file (files start at byte #0).
+   If this value is positive, then the second (esize2) will be found
+   starting at byte #352+esize1 , the third (esize3) at byte #352+esize1+esize2,
+   et cetera.  Of course, in a .nii file, the value of vox_offset must
+   be compatible with these extensions. If a malformed file indicates
+   that an extended header data section would run past vox_offset, then
+   the entire extended header section should be ignored. In a .hdr file,
+   if an extended header data section would run past the end-of-file,
+   that extended header data should also be ignored.
+
+   With the above scheme, a program can successively examine the esize
+   and ecode values, and skip over each extended header section if the
+   program doesn't know how to interpret the data within. Of course, any
+   program can simply ignore all extended header sections simply by jumping
+   straight to the image data using vox_offset.
+-----------------------------------------------------------------------------*/
+
+/*! \struct nifti1_extender
+    \brief This structure represents a 4-byte string that should follow the
+           binary nifti_1_header data in a NIFTI-1 header file.  If the char
+           values are {1,0,0,0}, the file is expected to contain extensions,
+           values of {0,0,0,0} imply the file does not contain extensions.
+           Other sequences of values are not currently defined.
+ */
+struct nifti1_extender { char extension[4] ; } ;
+typedef struct nifti1_extender nifti1_extender ;
+
+/*! \struct nifti1_extension
+    \brief Data structure defining the fields of a header extension.
+ */
+struct nifti1_extension {
+    int    esize ; /*!< size of extension, in bytes (must be multiple of 16) */
+    int    ecode ; /*!< extension code, one of the NIFTI_ECODE_ values       */
+    char * edata ; /*!< raw data, with no byte swapping                      */
+} ;
+typedef struct nifti1_extension nifti1_extension ;
+
+//and restore packing behavior
+#pragma pack(pop)
+
+/*---------------------------------------------------------------------------*/
+/* DATA DIMENSIONALITY (as in ANALYZE 7.5):
+   ---------------------------------------
+     dim[0] = number of dimensions;
+              - if dim[0] is outside range 1..7, then the header information
+                needs to be byte swapped appropriately
+              - ANALYZE supports dim[0] up to 7, but NIFTI-1 reserves
+                dimensions 1,2,3 for space (x,y,z), 4 for time (t), and
+                5,6,7 for anything else needed.
+
+     dim[i] = length of dimension #i, for i=1..dim[0]  (must be positive)
+              - also see the discussion of intent_code, far below
+
+     pixdim[i] = voxel width along dimension #i, i=1..dim[0] (positive)
+                 - cf. ORIENTATION section below for use of pixdim[0]
+                 - the units of pixdim can be specified with the xyzt_units
+                   field (also described far below).
+
+   Number of bits per voxel value is in bitpix, which MUST correspond with
+   the datatype field.  The total number of bytes in the image data is
+     dim[1] * ... * dim[dim[0]] * bitpix / 8
+
+   In NIFTI-1 files, dimensions 1,2,3 are for space, dimension 4 is for time,
+   and dimension 5 is for storing multiple values at each spatiotemporal
+   voxel.  Some examples:
+     - A typical whole-brain FMRI experiment's time series:
+        - dim[0] = 4
+        - dim[1] = 64   pixdim[1] = 3.75 xyzt_units =  NIFTI_UNITS_MM
+        - dim[2] = 64   pixdim[2] = 3.75             | NIFTI_UNITS_SEC
+        - dim[3] = 20   pixdim[3] = 5.0
+        - dim[4] = 120  pixdim[4] = 2.0
+     - A typical T1-weighted anatomical volume:
+        - dim[0] = 3
+        - dim[1] = 256  pixdim[1] = 1.0  xyzt_units = NIFTI_UNITS_MM
+        - dim[2] = 256  pixdim[2] = 1.0
+        - dim[3] = 128  pixdim[3] = 1.1
+     - A single slice EPI time series:
+        - dim[0] = 4
+        - dim[1] = 64   pixdim[1] = 3.75 xyzt_units =  NIFTI_UNITS_MM
+        - dim[2] = 64   pixdim[2] = 3.75             | NIFTI_UNITS_SEC
+        - dim[3] = 1    pixdim[3] = 5.0
+        - dim[4] = 1200 pixdim[4] = 0.2
+     - A 3-vector stored at each point in a 3D volume:
+        - dim[0] = 5
+        - dim[1] = 256  pixdim[1] = 1.0  xyzt_units = NIFTI_UNITS_MM
+        - dim[2] = 256  pixdim[2] = 1.0
+        - dim[3] = 128  pixdim[3] = 1.1
+        - dim[4] = 1    pixdim[4] = 0.0
+        - dim[5] = 3                     intent_code = NIFTI_INTENT_VECTOR
+     - A single time series with a 3x3 matrix at each point:
+        - dim[0] = 5
+        - dim[1] = 1                     xyzt_units = NIFTI_UNITS_SEC
+        - dim[2] = 1
+        - dim[3] = 1
+        - dim[4] = 1200 pixdim[4] = 0.2
+        - dim[5] = 9                     intent_code = NIFTI_INTENT_GENMATRIX
+        - intent_p1 = intent_p2 = 3.0    (indicates matrix dimensions)
+-----------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------*/
+/* DATA STORAGE:
+   ------------
+   If the magic field is "n+1", then the voxel data is stored in the
+   same file as the header.  In this case, the voxel data starts at offset
+   (int)vox_offset into the header file.  Thus, vox_offset=352.0 means that
+   the data starts immediately after the NIFTI-1 header.  If vox_offset is
+   greater than 352, the NIFTI-1 format does not say much about the
+   contents of the dataset file between the end of the header and the
+   start of the data.
+
+   FILES:
+   -----
+   If the magic field is "ni1", then the voxel data is stored in the
+   associated ".img" file, starting at offset 0 (i.e., vox_offset is not
+   used in this case, and should be set to 0.0).
+
+   When storing NIFTI-1 datasets in pairs of files, it is customary to name
+   the files in the pattern "name.hdr" and "name.img", as in ANALYZE 7.5.
+   When storing in a single file ("n+1"), the file name should be in
+   the form "name.nii" (the ".nft" and ".nif" suffixes are already taken;
+   cf. http://www.icdatamaster.com/n.html ).
+
+   BYTE ORDERING:
+   -------------
+   The byte order of the data arrays is presumed to be the same as the byte
+   order of the header (which is determined by examining dim[0]).
+
+   Floating point types are presumed to be stored in IEEE-754 format.
+-----------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------*/
+/* DETAILS ABOUT vox_offset:
+   ------------------------
+   In a .nii file, the vox_offset field value is interpreted as the start
+   location of the image data bytes in that file. In a .hdr/.img file pair,
+   the vox_offset field value is the start location of the image data
+   bytes in the .img file.
+    * If vox_offset is less than 352 in a .nii file, it is equivalent
+      to 352 (i.e., image data never starts before byte #352 in a .nii file).
+    * The default value for vox_offset in a .nii file is 352.
+    * In a .hdr file, the default value for vox_offset is 0.
+    * vox_offset should be an integer multiple of 16; otherwise, some
+      programs may not work properly (e.g., SPM). This is to allow
+      memory-mapped input to be properly byte-aligned.
+   Note that since vox_offset is an IEEE-754 32 bit float (for compatibility
+   with the ANALYZE-7.5 format), it effectively has a 24 bit mantissa. All
+   integers from 0 to 2^24 can be represented exactly in this format, but not
+   all larger integers are exactly storable as IEEE-754 32 bit floats. However,
+   unless you plan to have vox_offset be potentially larger than 16 MB, this
+   should not be an issue. (Actually, any integral multiple of 16 up to 2^27
+   can be represented exactly in this format, which allows for up to 128 MB
+   of random information before the image data.  If that isn't enough, then
+   perhaps this format isn't right for you.)
+
+   In a .img file (i.e., image data stored separately from the NIfTI-1
+   header), data bytes between #0 and #vox_offset-1 (inclusive) are completely
+   undefined and unregulated by the NIfTI-1 standard. One potential use of
+   having vox_offset > 0 in the .hdr/.img file pair storage method is to make
+   the .img file be a copy of (or link to) a pre-existing image file in some
+   other format, such as DICOM; then vox_offset would be set to the offset of
+   the image data in this file. (It may not be possible to follow the
+   "multiple-of-16 rule" with an arbitrary external file; using the NIfTI-1
+   format in such a case may lead to a file that is incompatible with software
+   that relies on vox_offset being a multiple of 16.)
+
+   In a .nii file, data bytes between #348 and #vox_offset-1 (inclusive) may
+   be used to store user-defined extra information; similarly, in a .hdr file,
+   any data bytes after byte #347 are available for user-defined extra
+   information. The (very weak) regulation of this extra header data is
+   described elsewhere.
+-----------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------*/
+/* DATA SCALING:
+   ------------
+   If the scl_slope field is nonzero, then each voxel value in the dataset
+   should be scaled as
+      y = scl_slope * x + scl_inter
+   where x = voxel value stored
+         y = "true" voxel value
+   Normally, we would expect this scaling to be used to store "true" floating
+   values in a smaller integer datatype, but that is not required.  That is,
+   it is legal to use scaling even if the datatype is a float type (crazy,
+   perhaps, but legal).
+    - However, the scaling is to be ignored if datatype is DT_RGB24.
+    - If datatype is a complex type, then the scaling is to be
+      applied to both the real and imaginary parts.
+
+   The cal_min and cal_max fields (if nonzero) are used for mapping (possibly
+   scaled) dataset values to display colors:
+    - Minimum display intensity (black) corresponds to dataset value cal_min.
+    - Maximum display intensity (white) corresponds to dataset value cal_max.
+    - Dataset values below cal_min should display as black also, and values
+      above cal_max as white.
+    - Colors "black" and "white", of course, may refer to any scalar display
+      scheme (e.g., a color lookup table specified via aux_file).
+    - cal_min and cal_max only make sense when applied to scalar-valued
+      datasets (i.e., dim[0] < 5 or dim[5] = 1).
+-----------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------*/
+/* TYPE OF DATA (acceptable values for datatype field):
+   ---------------------------------------------------
+   Values of datatype smaller than 256 are ANALYZE 7.5 compatible.
+   Larger values are NIFTI-1 additions.  These are all multiples of 256, so
+   that no bits below position 8 are set in datatype.  But there is no need
+   to use only powers-of-2, as the original ANALYZE 7.5 datatype codes do.
+
+   The additional codes are intended to include a complete list of basic
+   scalar types, including signed and unsigned integers from 8 to 64 bits,
+   floats from 32 to 128 bits, and complex (float pairs) from 64 to 256 bits.
+
+   Note that most programs will support only a few of these datatypes!
+   A NIFTI-1 program should fail gracefully (e.g., print a warning message)
+   when it encounters a dataset with a type it doesn't like.
+-----------------------------------------------------------------------------*/
+
+#undef DT_UNKNOWN  /* defined in dirent.h on some Unix systems */
+#include<stdint.h>
+/*! \defgroup NIFTI1_DATATYPES
+    \brief nifti1 datatype codes
+    @{
+ */
+/*--- the original ANALYZE 7.5 type codes ---*/
+const int32_t DT_NONE                   =0;
+const int32_t DT_UNKNOWN                =0;     /* what it says, dude           */
+const int32_t DT_BINARY                 =1;     /* binary (1 bit/voxel)         */
+const int32_t DT_UNSIGNED_CHAR          =2;     /* unsigned char (8 bits/voxel) */
+const int32_t DT_SIGNED_SHORT           =4;     /* signed short (16 bits/voxel) */
+const int32_t DT_SIGNED_INT             =8;     /* signed int (32 bits/voxel)   */
+const int32_t DT_FLOAT                 =16;     /* float (32 bits/voxel)        */
+const int32_t DT_COMPLEX                =32;     /* complex (64 bits/voxel)      */
+const int32_t DT_DOUBLE                 =64;     /* double (64 bits/voxel)       */
+const int32_t DT_RGB                   =128;     /* RGB triple (24 bits/voxel)   */
+const int32_t DT_ALL                   =255;     /* not very useful (?)          */
+
+/*----- another set of names for the same ---*/
+const int32_t DT_UINT8                   =2;
+const int32_t DT_INT16                   =4;
+const int32_t DT_INT32                   =8;
+const int32_t DT_FLOAT32                =16;
+const int32_t DT_COMPLEX64              =32;
+const int32_t DT_FLOAT64                =64;
+const int32_t DT_RGB24                 =128;
+
+/*------------------- new codes for NIFTI ---*/
+const int32_t DT_INT8                  =256;     /* signed char (8 bits)         */
+const int32_t DT_UINT16                =512;     /* unsigned short (16 bits)     */
+const int32_t DT_UINT32                =768;     /* unsigned int (32 bits)       */
+const int32_t DT_INT64                =1024;     /* long long (64 bits)          */
+const int32_t DT_UINT64               =1280;     /* unsigned long long (64 bits) */
+const int32_t DT_FLOAT128             =1536;     /* long double (128 bits)       */
+const int32_t DT_COMPLEX128           =1792;     /* double pair (128 bits)       */
+const int32_t DT_COMPLEX256           =2048;     /* long double pair (256 bits)  */
+/* @} */
+
+
+/*------- aliases for all the above codes ---*/
+
+/*! \defgroup NIFTI1_DATATYPE_ALIASES
+    \brief aliases for the nifti1 datatype codes
+    @{
+ */
+/*! unsigned char. */
+const int32_t NIFTI_TYPE_UINT8          =2;
+/*! signed short. */
+const int32_t NIFTI_TYPE_INT16          =4;
+/*! signed int. */
+const int32_t NIFTI_TYPE_INT32          =8;
+/*! 32 bit float. */
+const int32_t NIFTI_TYPE_FLOAT32       =16;
+/*! 64 bit complex = 2 32 bit floats. */
+const int32_t NIFTI_TYPE_COMPLEX64     =32;
+/*! 64 bit float = double. */
+const int32_t NIFTI_TYPE_FLOAT64       =64;
+/*! 3 8 bit bytes. */
+const int32_t NIFTI_TYPE_RGB24        =128;
+/*! signed char. */
+const int32_t NIFTI_TYPE_INT8         =256;
+/*! unsigned short. */
+const int32_t NIFTI_TYPE_UINT16       =512;
+/*! unsigned int. */
+const int32_t NIFTI_TYPE_UINT32       =768;
+/*! signed long long. */
+const int32_t NIFTI_TYPE_INT64       =1024;
+/*! unsigned long long. */
+const int32_t NIFTI_TYPE_UINT64      =1280;
+/*! 128 bit float = long double. */
+const int32_t NIFTI_TYPE_FLOAT128    =1536;
+/*! 128 bit complex = 2 64 bit floats. */
+const int32_t NIFTI_TYPE_COMPLEX128  =1792;
+/*! 256 bit complex = 2 128 bit floats */
+const int32_t NIFTI_TYPE_COMPLEX256  =2048;
+/* @} */
+
+/*-------- sample typedefs for complicated types ---*/
+#if 0
+typedef struct { float       r,i;     } complex_float ;
+typedef struct { double      r,i;     } complex_double ;
+typedef struct { long double r,i;     } complex_longdouble ;
+typedef struct { unsigned char r,g,b; } rgb_byte ;
+#endif
+
+/*---------------------------------------------------------------------------*/
+/* INTERPRETATION OF VOXEL DATA:
+   ----------------------------
+   The intent_code field can be used to indicate that the voxel data has
+   some particular meaning.  In particular, a large number of codes is
+   given to indicate that the the voxel data should be interpreted as
+   being drawn from a given probability distribution.
+
+   VECTOR-VALUED DATASETS:
+   ----------------------
+   The 5th dimension of the dataset, if present (i.e., dim[0]=5 and
+   dim[5] > 1), contains multiple values (e.g., a vector) to be stored
+   at each spatiotemporal location.  For example, the header values
+    - dim[0] = 5
+    - dim[1] = 64
+    - dim[2] = 64
+    - dim[3] = 20
+    - dim[4] = 1     (indicates no time axis)
+    - dim[5] = 3
+    - datatype = DT_FLOAT
+    - intent_code = NIFTI_INTENT_VECTOR
+   mean that this dataset should be interpreted as a 3D volume (64x64x20),
+   with a 3-vector of floats defined at each point in the 3D grid.
+
+   A program reading a dataset with a 5th dimension may want to reformat
+   the image data to store each voxels' set of values together in a struct
+   or array.  This programming detail, however, is beyond the scope of the
+   NIFTI-1 file specification!  Uses of dimensions 6 and 7 are also not
+   specified here.
+
+   STATISTICAL PARAMETRIC DATASETS (i.e., SPMs):
+   --------------------------------------------
+   Values of intent_code from NIFTI_FIRST_STATCODE to NIFTI_LAST_STATCODE
+   (inclusive) indicate that the numbers in the dataset should be interpreted
+   as being drawn from a given distribution.  Most such distributions have
+   auxiliary parameters (e.g., NIFTI_INTENT_TTEST has 1 DOF parameter).
+
+   If the dataset DOES NOT have a 5th dimension, then the auxiliary parameters
+   are the same for each voxel, and are given in header fields intent_p1,
+   intent_p2, and intent_p3.
+
+   If the dataset DOES have a 5th dimension, then the auxiliary parameters
+   are different for each voxel.  For example, the header values
+    - dim[0] = 5
+    - dim[1] = 128
+    - dim[2] = 128
+    - dim[3] = 1      (indicates a single slice)
+    - dim[4] = 1      (indicates no time axis)
+    - dim[5] = 2
+    - datatype = DT_FLOAT
+    - intent_code = NIFTI_INTENT_TTEST
+   mean that this is a 2D dataset (128x128) of t-statistics, with the
+   t-statistic being in the first "plane" of data and the degrees-of-freedom
+   parameter being in the second "plane" of data.
+
+   If the dataset 5th dimension is used to store the voxel-wise statistical
+   parameters, then dim[5] must be 1 plus the number of parameters required
+   by that distribution (e.g., intent_code=NIFTI_INTENT_TTEST implies dim[5]
+   must be 2, as in the example just above).
+
+   Note: intent_code values 2..10 are compatible with AFNI 1.5x (which is
+   why there is no code with value=1, which is obsolescent in AFNI).
+
+   OTHER INTENTIONS:
+   ----------------
+   The purpose of the intent_* fields is to help interpret the values
+   stored in the dataset.  Some non-statistical values for intent_code
+   and conventions are provided for storing other complex data types.
+
+   The intent_name field provides space for a 15 character (plus 0 byte)
+   'name' string for the type of data stored. Examples:
+    - intent_code = NIFTI_INTENT_ESTIMATE; intent_name = "T1";
+       could be used to signify that the voxel values are estimates of the
+       NMR parameter T1.
+    - intent_code = NIFTI_INTENT_TTEST; intent_name = "House";
+       could be used to signify that the voxel values are t-statistics
+       for the significance of 'activation' response to a House stimulus.
+    - intent_code = NIFTI_INTENT_DISPVECT; intent_name = "ToMNI152";
+       could be used to signify that the voxel values are a displacement
+       vector that transforms each voxel (x,y,z) location to the
+       corresponding location in the MNI152 standard brain.
+    - intent_code = NIFTI_INTENT_SYMMATRIX; intent_name = "DTI";
+       could be used to signify that the voxel values comprise a diffusion
+       tensor image.
+
+   If no data name is implied or needed, intent_name[0] should be set to 0.
+-----------------------------------------------------------------------------*/
+
+/*! default: no intention is indicated in the header. */
+
+const int32_t NIFTI_INTENT_NONE        =0;
+
+/*-------- These codes are for probability distributions ---------------*/
+/* Most distributions have a number of parameters,
+       below denoted by p1, p2, and p3, and stored in
+        - intent_p1, intent_p2, intent_p3 if dataset doesn't have 5th dimension
+        - image data array                if dataset does have 5th dimension
+
+       Functions to compute with many of the distributions below can be found
+       in the CDF library from U Texas.
+
+       Formulas for and discussions of these distributions can be found in the
+       following books:
+
+        [U] Univariate Discrete Distributions,
+            NL Johnson, S Kotz, AW Kemp.
+
+        [C1] Continuous Univariate Distributions, vol. 1,
+             NL Johnson, S Kotz, N Balakrishnan.
+
+        [C2] Continuous Univariate Distributions, vol. 2,
+             NL Johnson, S Kotz, N Balakrishnan.                            */
+/*----------------------------------------------------------------------*/
+
+/*! [C2, chap 32] Correlation coefficient R (1 param):
+       p1 = degrees of freedom
+       R/sqrt(1-R*R) is t-distributed with p1 DOF. */
+
+/*! \defgroup NIFTI1_INTENT_CODES
+    \brief nifti1 intent codes, to describe intended meaning of dataset contents
+    @{
+ */
+const int32_t NIFTI_INTENT_CORREL     =2;
+
+/*! [C2, chap 28] Student t statistic (1 param): p1 = DOF. */
+
+const int32_t NIFTI_INTENT_TTEST      =3;
+
+/*! [C2, chap 27] Fisher F statistic (2 params):
+       p1 = numerator DOF, p2 = denominator DOF. */
+
+const int32_t NIFTI_INTENT_FTEST      =4;
+
+/*! [C1, chap 13] Standard normal (0 params): Density = N(0,1). */
+
+const int32_t NIFTI_INTENT_ZSCORE     =5;
+
+/*! [C1, chap 18] Chi-squared (1 param): p1 = DOF.
+      Density(x) proportional to exp(-x/2) * x^(p1/2-1). */
+
+const int32_t NIFTI_INTENT_CHISQ      =6;
+
+/*! [C2, chap 25] Beta distribution (2 params): p1=a, p2=b.
+      Density(x) proportional to x^(a-1) * (1-x)^(b-1). */
+
+const int32_t NIFTI_INTENT_BETA       =7;
+
+/*! [U, chap 3] Binomial distribution (2 params):
+       p1 = number of trials, p2 = probability per trial.
+      Prob(x) = (p1 choose x) * p2^x * (1-p2)^(p1-x), for x=0,1,...,p1. */
+
+const int32_t NIFTI_INTENT_BINOM      =8;
+
+/*! [C1, chap 17] Gamma distribution (2 params):
+       p1 = shape, p2 = scale.
+      Density(x) proportional to x^(p1-1) * exp(-p2*x). */
+
+const int32_t NIFTI_INTENT_GAMMA      =9;
+
+/*! [U, chap 4] Poisson distribution (1 param): p1 = mean.
+      Prob(x) = exp(-p1) * p1^x / x! , for x=0,1,2,.... */
+
+const int32_t NIFTI_INTENT_POISSON   =10;
+
+/*! [C1, chap 13] Normal distribution (2 params):
+       p1 = mean, p2 = standard deviation. */
+
+const int32_t NIFTI_INTENT_NORMAL    =11;
+
+/*! [C2, chap 30] Noncentral F statistic (3 params):
+       p1 = numerator DOF, p2 = denominator DOF,
+       p3 = numerator noncentrality parameter.  */
+
+const int32_t NIFTI_INTENT_FTEST_NONC=12;
+
+/*! [C2, chap 29] Noncentral chi-squared statistic (2 params):
+       p1 = DOF, p2 = noncentrality parameter.     */
+
+const int32_t NIFTI_INTENT_CHISQ_NONC=13;
+
+/*! [C2, chap 23] Logistic distribution (2 params):
+       p1 = location, p2 = scale.
+      Density(x) proportional to sech^2((x-p1)/(2*p2)). */
+
+const int32_t NIFTI_INTENT_LOGISTIC  =14;
+
+/*! [C2, chap 24] Laplace distribution (2 params):
+       p1 = location, p2 = scale.
+      Density(x) proportional to exp(-abs(x-p1)/p2). */
+
+const int32_t NIFTI_INTENT_LAPLACE   =15;
+
+/*! [C2, chap 26] Uniform distribution: p1 = lower end, p2 = upper end. */
+
+const int32_t NIFTI_INTENT_UNIFORM   =16;
+
+/*! [C2, chap 31] Noncentral t statistic (2 params):
+       p1 = DOF, p2 = noncentrality parameter. */
+
+const int32_t NIFTI_INTENT_TTEST_NONC=17;
+
+/*! [C1, chap 21] Weibull distribution (3 params):
+       p1 = location, p2 = scale, p3 = power.
+      Density(x) proportional to
+       ((x-p1)/p2)^(p3-1) * exp(-((x-p1)/p2)^p3) for x > p1. */
+
+const int32_t NIFTI_INTENT_WEIBULL   =18;
+
+/*! [C1, chap 18] Chi distribution (1 param): p1 = DOF.
+      Density(x) proportional to x^(p1-1) * exp(-x^2/2) for x > 0.
+       p1 = 1 = 'half normal' distribution
+       p1 = 2 = Rayleigh distribution
+       p1 = 3 = Maxwell-Boltzmann distribution.                  */
+
+const int32_t NIFTI_INTENT_CHI       =19;
+
+/*! [C1, chap 15] Inverse Gaussian (2 params):
+       p1 = mu, p2 = lambda
+      Density(x) proportional to
+       exp(-p2*(x-p1)^2/(2*p1^2*x)) / x^3  for x > 0. */
+
+const int32_t NIFTI_INTENT_INVGAUSS  =20;
+
+/*! [C2, chap 22] Extreme value type I (2 params):
+       p1 = location, p2 = scale
+      cdf(x) = exp(-exp(-(x-p1)/p2)). */
+
+const int32_t NIFTI_INTENT_EXTVAL    =21;
+
+/*! Data is a 'p-value' (no params). */
+
+const int32_t NIFTI_INTENT_PVAL      =22;
+
+/*! Data is ln(p-value) (no params).
+      To be safe, a program should compute p = exp(-abs(this_value)).
+      The nifti_stats.c library returns this_value
+      as positive, so that this_value = -log(p). */
+
+
+const int32_t NIFTI_INTENT_LOGPVAL   =23;
+
+/*! Data is log10(p-value) (no params).
+      To be safe, a program should compute p = pow(10.,-abs(this_value)).
+      The nifti_stats.c library returns this_value
+      as positive, so that this_value = -log10(p). */
+
+const int32_t NIFTI_INTENT_LOG10PVAL =24;
+
+/*! Smallest intent_code that indicates a statistic. */
+
+const int32_t NIFTI_FIRST_STATCODE    =2;
+
+/*! Largest intent_code that indicates a statistic. */
+
+const int32_t NIFTI_LAST_STATCODE    =24;
+
+/*---------- these values for intent_code aren't for statistics ----------*/
+
+/*! To signify that the value at each voxel is an estimate
+     of some parameter, set intent_code = NIFTI_INTENT_ESTIMATE.
+     The name of the parameter may be stored in intent_name.     */
+
+const int32_t NIFTI_INTENT_ESTIMATE =1001;
+
+/*! To signify that the value at each voxel is an index into
+     some set of labels, set intent_code = NIFTI_INTENT_LABEL.
+     The filename with the labels may stored in aux_file.        */
+
+const int32_t NIFTI_INTENT_LABEL    =1002;
+
+/*! To signify that the value at each voxel is an index into the
+     NeuroNames labels set, set intent_code = NIFTI_INTENT_NEURONAME. */
+
+const int32_t NIFTI_INTENT_NEURONAME=1003;
+
+/*! To store an M x N matrix at each voxel:
+       - dataset must have a 5th dimension (dim[0]=5 and dim[5]>1)
+       - intent_code must be NIFTI_INTENT_GENMATRIX
+       - dim[5] must be M*N
+       - intent_p1 must be M (in float format)
+       - intent_p2 must be N (ditto)
+       - the matrix values A[i][[j] are stored in row-order:
+         - A[0][0] A[0][1] ... A[0][N-1]
+         - A[1][0] A[1][1] ... A[1][N-1]
+         - etc., until
+         - A[M-1][0] A[M-1][1] ... A[M-1][N-1]        */
+
+const int32_t NIFTI_INTENT_GENMATRIX=1004;
+
+/*! To store an NxN symmetric matrix at each voxel:
+       - dataset must have a 5th dimension
+       - intent_code must be NIFTI_INTENT_SYMMATRIX
+       - dim[5] must be N*(N+1)/2
+       - intent_p1 must be N (in float format)
+       - the matrix values A[i][[j] are stored in row-order:
+         - A[0][0]
+         - A[1][0] A[1][1]
+         - A[2][0] A[2][1] A[2][2]
+         - etc.: row-by-row                           */
+
+const int32_t NIFTI_INTENT_SYMMATRIX=1005;
+
+/*! To signify that the vector value at each voxel is to be taken
+     as a displacement field or vector:
+       - dataset must have a 5th dimension
+       - intent_code must be NIFTI_INTENT_DISPVECT
+       - dim[5] must be the dimensionality of the displacment
+         vector (e.g., 3 for spatial displacement, 2 for in-plane) */
+
+const int32_t NIFTI_INTENT_DISPVECT =1006;   /* specifically for displacements */
+const int32_t NIFTI_INTENT_VECTOR   =1007;   /* for any other type of vector */
+
+/*! To signify that the vector value at each voxel is really a
+     spatial coordinate (e.g., the vertices or nodes of a surface mesh):
+       - dataset must have a 5th dimension
+       - intent_code must be NIFTI_INTENT_POINTSET
+       - dim[0] = 5
+       - dim[1] = number of points
+       - dim[2] = dim[3] = dim[4] = 1
+       - dim[5] must be the dimensionality of space (e.g., 3 => 3D space).
+       - intent_name may describe the object these points come from
+         (e.g., "pial", "gray/white" , "EEG", "MEG").                   */
+
+const int32_t NIFTI_INTENT_POINTSET =1008;
+
+/*! To signify that the vector value at each voxel is really a triple
+     of indexes (e.g., forming a triangle) from a pointset dataset:
+       - dataset must have a 5th dimension
+       - intent_code must be NIFTI_INTENT_TRIANGLE
+       - dim[0] = 5
+       - dim[1] = number of triangles
+       - dim[2] = dim[3] = dim[4] = 1
+       - dim[5] = 3
+       - datatype should be an integer type (preferably DT_INT32)
+       - the data values are indexes (0,1,...) into a pointset dataset. */
+
+const int32_t NIFTI_INTENT_TRIANGLE =1009;
+
+/*! To signify that the vector value at each voxel is a quaternion:
+       - dataset must have a 5th dimension
+       - intent_code must be NIFTI_INTENT_QUATERNION
+       - dim[0] = 5
+       - dim[5] = 4
+       - datatype should be a floating point type     */
+
+const int32_t NIFTI_INTENT_QUATERNION=1010;
+
+/*! Dimensionless value - no params - although, as in _ESTIMATE
+     the name of the parameter may be stored in intent_name.     */
+
+const int32_t NIFTI_INTENT_DIMLESS   =1011;
+/* @} */
+
+/*---------------------------------------------------------------------------*/
+/* 3D IMAGE (VOLUME) ORIENTATION AND LOCATION IN SPACE:
+   ---------------------------------------------------
+   There are 3 different methods by which continuous coordinates can
+   attached to voxels.  The discussion below emphasizes 3D volumes, and
+   the continuous coordinates are referred to as (x,y,z).  The voxel
+   index coordinates (i.e., the array indexes) are referred to as (i,j,k),
+   with valid ranges:
+     i = 0 .. dim[1]-1
+     j = 0 .. dim[2]-1  (if dim[0] >= 2)
+     k = 0 .. dim[3]-1  (if dim[0] >= 3)
+   The (x,y,z) coordinates refer to the CENTER of a voxel.  In methods
+   2 and 3, the (x,y,z) axes refer to a subject-based coordinate system,
+   with
+     +x = Right  +y = Anterior  +z = Superior.
+   This is a right-handed coordinate system.  However, the exact direction
+   these axes point with respect to the subject depends on qform_code
+   (Method 2) and sform_code (Method 3).
+
+   N.B.: The i index varies most rapidly, j index next, k index slowest.
+    Thus, voxel (i,j,k) is stored starting at location
+      (i + j*dim[1] + k*dim[1]*dim[2]) * (bitpix/8)
+    into the dataset array.
+
+   N.B.: The ANALYZE 7.5 coordinate system is
+      +x = Left  +y = Anterior  +z = Superior
+    which is a left-handed coordinate system.  This backwardness is
+    too difficult to tolerate, so this NIFTI-1 standard specifies the
+    coordinate order which is most common in functional neuroimaging.
+
+   N.B.: The 3 methods below all give the locations of the voxel centers
+    in the (x,y,z) coordinate system.  In many cases, programs will wish
+    to display image data on some other grid.  In such a case, the program
+    will need to convert its desired (x,y,z) values into (i,j,k) values
+    in order to extract (or interpolate) the image data.  This operation
+    would be done with the inverse transformation to those described below.
+
+   N.B.: Method 2 uses a factor 'qfac' which is either -1 or 1; qfac is
+    stored in the otherwise unused pixdim[0].  If pixdim[0]=0.0 (which
+    should not occur), we take qfac=1.  Of course, pixdim[0] is only used
+    when reading a NIFTI-1 header, not when reading an ANALYZE 7.5 header.
+
+   N.B.: The units of (x,y,z) can be specified using the xyzt_units field.
+
+   METHOD 1 (the "old" way, used only when qform_code = 0):
+   -------------------------------------------------------
+   The coordinate mapping from (i,j,k) to (x,y,z) is the ANALYZE
+   7.5 way.  This is a simple scaling relationship:
+
+     x = pixdim[1] * i
+     y = pixdim[2] * j
+     z = pixdim[3] * k
+
+   No particular spatial orientation is attached to these (x,y,z)
+   coordinates.  (NIFTI-1 does not have the ANALYZE 7.5 orient field,
+   which is not general and is often not set properly.)  This method
+   is not recommended, and is present mainly for compatibility with
+   ANALYZE 7.5 files.
+
+   METHOD 2 (used when qform_code > 0, which should be the "normal" case):
+   ---------------------------------------------------------------------
+   The (x,y,z) coordinates are given by the pixdim[] scales, a rotation
+   matrix, and a shift.  This method is intended to represent
+   "scanner-anatomical" coordinates, which are often embedded in the
+   image header (e.g., DICOM fields (0020,0032), (0020,0037), (0028,0030),
+   and (0018,0050)), and represent the nominal orientation and location of
+   the data.  This method can also be used to represent "aligned"
+   coordinates, which would typically result from some post-acquisition
+   alignment of the volume to a standard orientation (e.g., the same
+   subject on another day, or a rigid rotation to true anatomical
+   orientation from the tilted position of the subject in the scanner).
+   The formula for (x,y,z) in terms of header parameters and (i,j,k) is:
+
+     [ x ]   [ R11 R12 R13 ] [        pixdim[1] * i ]   [ qoffset_x ]
+     [ y ] = [ R21 R22 R23 ] [        pixdim[2] * j ] + [ qoffset_y ]
+     [ z ]   [ R31 R32 R33 ] [ qfac * pixdim[3] * k ]   [ qoffset_z ]
+
+   The qoffset_* shifts are in the NIFTI-1 header.  Note that the center
+   of the (i,j,k)=(0,0,0) voxel (first value in the dataset array) is
+   just (x,y,z)=(qoffset_x,qoffset_y,qoffset_z).
+
+   The rotation matrix R is calculated from the quatern_* parameters.
+   This calculation is described below.
+
+   The scaling factor qfac is either 1 or -1.  The rotation matrix R
+   defined by the quaternion parameters is "proper" (has determinant 1).
+   This may not fit the needs of the data; for example, if the image
+   grid is
+     i increases from Left-to-Right
+     j increases from Anterior-to-Posterior
+     k increases from Inferior-to-Superior
+   Then (i,j,k) is a left-handed triple.  In this example, if qfac=1,
+   the R matrix would have to be
+
+     [  1   0   0 ]
+     [  0  -1   0 ]  which is "improper" (determinant = -1).
+     [  0   0   1 ]
+
+   If we set qfac=-1, then the R matrix would be
+
+     [  1   0   0 ]
+     [  0  -1   0 ]  which is proper.
+     [  0   0  -1 ]
+
+   This R matrix is represented by quaternion [a,b,c,d] = [0,1,0,0]
+   (which encodes a 180 degree rotation about the x-axis).
+
+   METHOD 3 (used when sform_code > 0):
+   -----------------------------------
+   The (x,y,z) coordinates are given by a general affine transformation
+   of the (i,j,k) indexes:
+
+     x = srow_x[0] * i + srow_x[1] * j + srow_x[2] * k + srow_x[3]
+     y = srow_y[0] * i + srow_y[1] * j + srow_y[2] * k + srow_y[3]
+     z = srow_z[0] * i + srow_z[1] * j + srow_z[2] * k + srow_z[3]
+
+   The srow_* vectors are in the NIFTI_1 header.  Note that no use is
+   made of pixdim[] in this method.
+
+   WHY 3 METHODS?
+   --------------
+   Method 1 is provided only for backwards compatibility.  The intention
+   is that Method 2 (qform_code > 0) represents the nominal voxel locations
+   as reported by the scanner, or as rotated to some fiducial orientation and
+   location.  Method 3, if present (sform_code > 0), is to be used to give
+   the location of the voxels in some standard space.  The sform_code
+   indicates which standard space is present.  Both methods 2 and 3 can be
+   present, and be useful in different contexts (method 2 for displaying the
+   data on its original grid; method 3 for displaying it on a standard grid).
+
+   In this scheme, a dataset would originally be set up so that the
+   Method 2 coordinates represent what the scanner reported.  Later,
+   a registration to some standard space can be computed and inserted
+   in the header.  Image display software can use either transform,
+   depending on its purposes and needs.
+
+   In Method 2, the origin of coordinates would generally be whatever
+   the scanner origin is; for example, in MRI, (0,0,0) is the center
+   of the gradient coil.
+
+   In Method 3, the origin of coordinates would depend on the value
+   of sform_code; for example, for the Talairach coordinate system,
+   (0,0,0) corresponds to the Anterior Commissure.
+
+   QUATERNION REPRESENTATION OF ROTATION MATRIX (METHOD 2)
+   -------------------------------------------------------
+   The orientation of the (x,y,z) axes relative to the (i,j,k) axes
+   in 3D space is specified using a unit quaternion [a,b,c,d], where
+   a*a+b*b+c*c+d*d=1.  The (b,c,d) values are all that is needed, since
+   we require that a = sqrt(1.0-(b*b+c*c+d*d)) be nonnegative.  The (b,c,d)
+   values are stored in the (quatern_b,quatern_c,quatern_d) fields.
+
+   The quaternion representation is chosen for its compactness in
+   representing rotations. The (proper) 3x3 rotation matrix that
+   corresponds to [a,b,c,d] is
+
+         [ a*a+b*b-c*c-d*d   2*b*c-2*a*d       2*b*d+2*a*c     ]
+     R = [ 2*b*c+2*a*d       a*a+c*c-b*b-d*d   2*c*d-2*a*b     ]
+         [ 2*b*d-2*a*c       2*c*d+2*a*b       a*a+d*d-c*c-b*b ]
+
+         [ R11               R12               R13             ]
+       = [ R21               R22               R23             ]
+         [ R31               R32               R33             ]
+
+   If (p,q,r) is a unit 3-vector, then rotation of angle h about that
+   direction is represented by the quaternion
+
+     [a,b,c,d] = [cos(h/2), p*sin(h/2), q*sin(h/2), r*sin(h/2)].
+
+   Requiring a >= 0 is equivalent to requiring -Pi <= h <= Pi.  (Note that
+   [-a,-b,-c,-d] represents the same rotation as [a,b,c,d]; there are 2
+   quaternions that can be used to represent a given rotation matrix R.)
+   To rotate a 3-vector (x,y,z) using quaternions, we compute the
+   quaternion product
+
+     [0,x',y',z'] = [a,b,c,d] * [0,x,y,z] * [a,-b,-c,-d]
+
+   which is equivalent to the matrix-vector multiply
+
+     [ x' ]     [ x ]
+     [ y' ] = R [ y ]   (equivalence depends on a*a+b*b+c*c+d*d=1)
+     [ z' ]     [ z ]
+
+   Multiplication of 2 quaternions is defined by the following:
+
+     [a,b,c,d] = a*1 + b*I + c*J + d*K
+     where
+       I*I = J*J = K*K = -1 (I,J,K are square roots of -1)
+       I*J =  K    J*K =  I    K*I =  J
+       J*I = -K    K*J = -I    I*K = -J  (not commutative!)
+     For example
+       [a,b,0,0] * [0,0,0,1] = [0,0,-b,a]
+     since this expands to
+       (a+b*I)*(K) = (a*K+b*I*K) = (a*K-b*J).
+
+   The above formula shows how to go from quaternion (b,c,d) to
+   rotation matrix and direction cosines.  Conversely, given R,
+   we can compute the fields for the NIFTI-1 header by
+
+     a = 0.5  * sqrt(1+R11+R22+R33)    (not stored)
+     b = 0.25 * (R32-R23) / a       => quatern_b
+     c = 0.25 * (R13-R31) / a       => quatern_c
+     d = 0.25 * (R21-R12) / a       => quatern_d
+
+   If a=0 (a 180 degree rotation), alternative formulas are needed.
+   See the nifti1_io.c function mat44_to_quatern() for an implementation
+   of the various cases in converting R to [a,b,c,d].
+
+   Note that R-transpose (= R-inverse) would lead to the quaternion
+   [a,-b,-c,-d].
+
+   The choice to specify the qoffset_x (etc.) values in the final
+   coordinate system is partly to make it easy to convert DICOM images to
+   this format.  The DICOM attribute "Image Position (Patient)" (0020,0032)
+   stores the (Xd,Yd,Zd) coordinates of the center of the first voxel.
+   Here, (Xd,Yd,Zd) refer to DICOM coordinates, and Xd=-x, Yd=-y, Zd=z,
+   where (x,y,z) refers to the NIFTI coordinate system discussed above.
+   (i.e., DICOM +Xd is Left, +Yd is Posterior, +Zd is Superior,
+        whereas +x is Right, +y is Anterior  , +z is Superior. )
+   Thus, if the (0020,0032) DICOM attribute is extracted into (px,py,pz), then
+     qoffset_x = -px   qoffset_y = -py   qoffset_z = pz
+   is a reasonable setting when qform_code=NIFTI_XFORM_SCANNER_ANAT.
+
+   That is, DICOM's coordinate system is 180 degrees rotated about the z-axis
+   from the neuroscience/NIFTI coordinate system.  To transform between DICOM
+   and NIFTI, you just have to negate the x- and y-coordinates.
+
+   The DICOM attribute (0020,0037) "Image Orientation (Patient)" gives the
+   orientation of the x- and y-axes of the image data in terms of 2 3-vectors.
+   The first vector is a unit vector along the x-axis, and the second is
+   along the y-axis.  If the (0020,0037) attribute is extracted into the
+   value (xa,xb,xc,ya,yb,yc), then the first two columns of the R matrix
+   would be
+              [ -xa  -ya ]
+              [ -xb  -yb ]
+              [  xc   yc ]
+   The negations are because DICOM's x- and y-axes are reversed relative
+   to NIFTI's.  The third column of the R matrix gives the direction of
+   displacement (relative to the subject) along the slice-wise direction.
+   This orientation is not encoded in the DICOM standard in a simple way;
+   DICOM is mostly concerned with 2D images.  The third column of R will be
+   either the cross-product of the first 2 columns or its negative.  It is
+   possible to infer the sign of the 3rd column by examining the coordinates
+   in DICOM attribute (0020,0032) "Image Position (Patient)" for successive
+   slices.  However, this method occasionally fails for reasons that I
+   (RW Cox) do not understand.
+-----------------------------------------------------------------------------*/
+
+/* [qs]form_code value:  */      /* x,y,z coordinate system refers to:    */
+/*-----------------------*/      /*---------------------------------------*/
+
+/*! \defgroup NIFTI1_XFORM_CODES
+    \brief nifti1 xform codes to describe the "standard" coordinate system
+    @{
+ */
+/*! Arbitrary coordinates (Method 1). */
+
+const int32_t NIFTI_XFORM_UNKNOWN     =0;
+
+/*! Scanner-based anatomical coordinates */
+
+const int32_t NIFTI_XFORM_SCANNER_ANAT=1;
+
+/*! Coordinates aligned to another file's,
+                                        or to anatomical "truth".            */
+
+const int32_t NIFTI_XFORM_ALIGNED_ANAT=2;
+
+/*! Coordinates aligned to Talairach-
+                                        Tournoux Atlas; (0,0,0)=AC, etc. */
+
+const int32_t NIFTI_XFORM_TALAIRACH   =3;
+
+/*! MNI 152 normalized coordinates. */
+
+const int32_t NIFTI_XFORM_MNI_152     =4;
+/* @} */
+
+/*---------------------------------------------------------------------------*/
+/* UNITS OF SPATIAL AND TEMPORAL DIMENSIONS:
+   ----------------------------------------
+   The codes below can be used in xyzt_units to indicate the units of pixdim.
+   As noted earlier, dimensions 1,2,3 are for x,y,z; dimension 4 is for
+   time (t).
+    - If dim[4]=1 or dim[0] < 4, there is no time axis.
+    - A single time series (no space) would be specified with
+      - dim[0] = 4 (for scalar data) or dim[0] = 5 (for vector data)
+      - dim[1] = dim[2] = dim[3] = 1
+      - dim[4] = number of time points
+      - pixdim[4] = time step
+      - xyzt_units indicates units of pixdim[4]
+      - dim[5] = number of values stored at each time point
+
+   Bits 0..2 of xyzt_units specify the units of pixdim[1..3]
+    (e.g., spatial units are values 1..7).
+   Bits 3..5 of xyzt_units specify the units of pixdim[4]
+    (e.g., temporal units are multiples of 8).
+
+   This compression of 2 distinct concepts into 1 byte is due to the
+   limited space available in the 348 byte ANALYZE 7.5 header.  The
+   macros XYZT_TO_SPACE and XYZT_TO_TIME can be used to mask off the
+   undesired bits from the xyzt_units fields, leaving "pure" space
+   and time codes.  Inversely, the macro SPACE_TIME_TO_XYZT can be
+   used to assemble a space code (0,1,2,...,7) with a time code
+   (0,8,16,32,...,56) into the combined value for xyzt_units.
+
+   Note that codes are provided to indicate the "time" axis units are
+   actually frequency in Hertz (_HZ), in part-per-million (_PPM)
+   or in radians-per-second (_RADS).
+
+   The toffset field can be used to indicate a nonzero start point for
+   the time axis.  That is, time point #m is at t=toffset+m*pixdim[4]
+   for m=0..dim[4]-1.
+-----------------------------------------------------------------------------*/
+
+/*! \defgroup NIFTI1_UNITS
+    \brief nifti1 units codes to describe the unit of measurement for
+           each dimension of the dataset
+    @{
+ */
+/*! NIFTI code for unspecified units. */
+const int32_t NIFTI_UNITS_UNKNOWN=0;
+
+/** Space codes are multiples of 1. **/
+/*! NIFTI code for meters. */
+const int32_t NIFTI_UNITS_METER  =1;
+/*! NIFTI code for millimeters. */
+const int32_t NIFTI_UNITS_MM     =2;
+/*! NIFTI code for micrometers. */
+const int32_t NIFTI_UNITS_MICRON =3;
+
+/** Time codes are multiples of 8. **/
+/*! NIFTI code for seconds. */
+const int32_t NIFTI_UNITS_SEC    =8;
+/*! NIFTI code for milliseconds. */
+const int32_t NIFTI_UNITS_MSEC  =16;
+/*! NIFTI code for microseconds. */
+const int32_t NIFTI_UNITS_USEC  =24;
+
+/*** These units are for spectral data: ***/
+/*! NIFTI code for Hertz. */
+const int32_t NIFTI_UNITS_HZ    =32;
+/*! NIFTI code for ppm. */
+const int32_t NIFTI_UNITS_PPM   =40;
+/*! NIFTI code for radians per second. */
+const int32_t NIFTI_UNITS_RADS  =48;
+/* @} */
+
+#undef  XYZT_TO_SPACE
+#undef  XYZT_TO_TIME
+#define XYZT_TO_SPACE(xyzt)       ( (xyzt) & 0x07 )
+#define XYZT_TO_TIME(xyzt)        ( (xyzt) & 0x38 )
+
+#undef  SPACE_TIME_TO_XYZT
+#define SPACE_TIME_TO_XYZT(ss,tt) (  (((char)(ss)) & 0x07)   \
+    | (((char)(tt)) & 0x38) )
+
+/*---------------------------------------------------------------------------*/
+/* MRI-SPECIFIC SPATIAL AND TEMPORAL INFORMATION:
+   ---------------------------------------------
+   A few fields are provided to store some extra information
+   that is sometimes important when storing the image data
+   from an FMRI time series experiment.  (After processing such
+   data into statistical images, these fields are not likely
+   to be useful.)
+
+  { freq_dim  } = These fields encode which spatial dimension (1,2, or 3)
+  { phase_dim } = corresponds to which acquisition dimension for MRI data.
+  { slice_dim } =
+    Examples:
+      Rectangular scan multi-slice EPI:
+        freq_dim = 1  phase_dim = 2  slice_dim = 3  (or some permutation)
+      Spiral scan multi-slice EPI:
+        freq_dim = phase_dim = 0  slice_dim = 3
+        since the concepts of frequency- and phase-encoding directions
+        don't apply to spiral scan
+
+    slice_duration = If this is positive, AND if slice_dim is nonzero,
+                     indicates the amount of time used to acquire 1 slice.
+                     slice_duration*dim[slice_dim] can be less than pixdim[4]
+                     with a clustered acquisition method, for example.
+
+    slice_code = If this is nonzero, AND if slice_dim is nonzero, AND
+                 if slice_duration is positive, indicates the timing
+                 pattern of the slice acquisition.  The following codes
+                 are defined:
+                   NIFTI_SLICE_SEQ_INC  == sequential increasing
+                   NIFTI_SLICE_SEQ_DEC  == sequential decreasing
+                   NIFTI_SLICE_ALT_INC  == alternating increasing
+                   NIFTI_SLICE_ALT_DEC  == alternating decreasing
+                   NIFTI_SLICE_ALT_INC2 == alternating increasing #2
+                   NIFTI_SLICE_ALT_DEC2 == alternating decreasing #2
+  { slice_start } = Indicates the start and end of the slice acquisition
+  { slice_end   } = pattern, when slice_code is nonzero.  These values
+                    are present to allow for the possible addition of
+                    "padded" slices at either end of the volume, which
+                    don't fit into the slice timing pattern.  If there
+                    are no padding slices, then slice_start=0 and
+                    slice_end=dim[slice_dim]-1 are the correct values.
+                    For these values to be meaningful, slice_start must
+                    be non-negative and slice_end must be greater than
+                    slice_start.  Otherwise, they should be ignored.
+
+  The following table indicates the slice timing pattern, relative to
+  time=0 for the first slice acquired, for some sample cases.  Here,
+  dim[slice_dim]=7 (there are 7 slices, labeled 0..6), slice_duration=0.1,
+  and slice_start=1, slice_end=5 (1 padded slice on each end).
+
+  slice
+  index  SEQ_INC SEQ_DEC ALT_INC ALT_DEC ALT_INC2 ALT_DEC2
+    6  :   n/a     n/a     n/a     n/a    n/a      n/a    n/a = not applicable
+    5  :   0.4     0.0     0.2     0.0    0.4      0.2    (slice time offset
+    4  :   0.3     0.1     0.4     0.3    0.1      0.0     doesn't apply to
+    3  :   0.2     0.2     0.1     0.1    0.3      0.3     slices outside
+    2  :   0.1     0.3     0.3     0.4    0.0      0.1     the range
+    1  :   0.0     0.4     0.0     0.2    0.2      0.4     slice_start ..
+    0  :   n/a     n/a     n/a     n/a    n/a      n/a     slice_end)
+
+  The SEQ slice_codes are sequential ordering (uncommon but not unknown),
+  either increasing in slice number or decreasing (INC or DEC), as
+  illustrated above.
+
+  The ALT slice codes are alternating ordering.  The 'standard' way for
+  these to operate (without the '2' on the end) is for the slice timing
+  to start at the edge of the slice_start .. slice_end group (at slice_start
+  for INC and at slice_end for DEC).  For the 'ALT_*2' slice_codes, the
+  slice timing instead starts at the first slice in from the edge (at
+  slice_start+1 for INC2 and at slice_end-1 for DEC2).  This latter
+  acquisition scheme is found on some Siemens scanners.
+
+  The fields freq_dim, phase_dim, slice_dim are all squished into the single
+  byte field dim_info (2 bits each, since the values for each field are
+  limited to the range 0..3).  This unpleasantness is due to lack of space
+  in the 348 byte allowance.
+
+  The macros DIM_INFO_TO_FREQ_DIM, DIM_INFO_TO_PHASE_DIM, and
+  DIM_INFO_TO_SLICE_DIM can be used to extract these values from the
+  dim_info byte.
+
+  The macro FPS_INTO_DIM_INFO can be used to put these 3 values
+  into the dim_info byte.
+-----------------------------------------------------------------------------*/
+
+#undef  DIM_INFO_TO_FREQ_DIM
+#undef  DIM_INFO_TO_PHASE_DIM
+#undef  DIM_INFO_TO_SLICE_DIM
+
+#define DIM_INFO_TO_FREQ_DIM(di)   ( ((di)     ) & 0x03 )
+#define DIM_INFO_TO_PHASE_DIM(di)  ( ((di) >> 2) & 0x03 )
+#define DIM_INFO_TO_SLICE_DIM(di)  ( ((di) >> 4) & 0x03 )
+
+#undef  FPS_INTO_DIM_INFO
+#define FPS_INTO_DIM_INFO(fd,pd,sd) ( ( ( ((char)(fd)) & 0x03)      ) |  \
+    ( ( ((char)(pd)) & 0x03) << 2 ) |  \
+    ( ( ((char)(sd)) & 0x03) << 4 )  )
+
+/*! \defgroup NIFTI1_SLICE_ORDER
+    \brief nifti1 slice order codes, describing the acquisition order
+           of the slices
+    @{
+ */
+const int32_t NIFTI_SLICE_UNKNOWN  =0;
+const int32_t NIFTI_SLICE_SEQ_INC  =1;
+const int32_t NIFTI_SLICE_SEQ_DEC  =2;
+const int32_t NIFTI_SLICE_ALT_INC  =3;
+const int32_t NIFTI_SLICE_ALT_DEC  =4;
+const int32_t NIFTI_SLICE_ALT_INC2 =5; /* 05 May 2005: RWCox */
+const int32_t NIFTI_SLICE_ALT_DEC2 =6; /* 05 May 2005: RWCox */
+/* @} */
+
+/*---------------------------------------------------------------------------*/
+/* UNUSED FIELDS:
+   -------------
+   Some of the ANALYZE 7.5 fields marked as ++UNUSED++ may need to be set
+   to particular values for compatibility with other programs.  The issue
+   of interoperability of ANALYZE 7.5 files is a murky one -- not all
+   programs require exactly the same set of fields.  (Unobscuring this
+   murkiness is a principal motivation behind NIFTI-1.)
+
+   Some of the fields that may need to be set for other (non-NIFTI aware)
+   software to be happy are:
+
+     extents    dbh.h says this should be 16384
+     regular    dbh.h says this should be the character 'r'
+     glmin,   } dbh.h says these values should be the min and max voxel
+      glmax   }  values for the entire dataset
+
+   It is best to initialize ALL fields in the NIFTI-1 header to 0
+   (e.g., with calloc()), then fill in what is needed.
+-----------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------*/
+/* MISCELLANEOUS C MACROS
+-----------------------------------------------------------------------------*/
+
+/*.................*/
+/*! Given a nifti_1_header struct, check if it has a good magic number.
+    Returns NIFTI version number (1..9) if magic is good, 0 if it is not. */
+
+/*#define NIFTI_VERSION(h)                               \
+    ( ( (h).magic[0]=='n' && (h).magic[3]=='\0'    &&     \
+    ( (h).magic[1]=='i' || (h).magic[1]=='+' ) &&     \
+    ( (h).magic[2]>='1' && (h).magic[2]<='9' )   )    \
+    ? (h).magic[2]-'0' : 0 )
+
+//*/
+
+/*.................*/
+/*! Check if a nifti_1_header struct says if the data is stored in the
+    same file or in a separate file.  Returns 1 if the data is in the same
+    file as the header, 0 if it is not.                                   */
+
+//#define NIFTI_ONEFILE(h) ( (h).magic[1] == '+' )
+
+/*.................*/
+/*! Check if a nifti_1_header struct needs to be byte swapped.
+    Returns 1 if it needs to be swapped, 0 if it does not.     */
+
+//#define NIFTI_NEEDS_SWAP(h) ( (h).dim[0] < 0 || (h).dim[0] > 7 )
+
+/*.................*/
+/*! Check if a nifti_1_header struct contains a 5th (vector) dimension.
+    Returns size of 5th dimension if > 1, returns 0 otherwise.         */
+
+//#define NIFTI_5TH_DIM(h) ( ((h).dim[0]>4 && (h).dim[5]>1) ? (h).dim[5] : 0 )
+
+/*****************************************************************************/
+
+}//namespace
+
+#endif /* _NIFTI_HEADER_ */
diff --git a/src/Nifti/nifti2.h b/src/Nifti/nifti2.h
new file mode 100644
index 0000000..0a6daf3
--- /dev/null
+++ b/src/Nifti/nifti2.h
@@ -0,0 +1,124 @@
+#ifndef __NIFTI2_HEADER
+#define __NIFTI2_HEADER
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "nifti1.h"
+
+namespace cifti
+{
+
+#include <stdint.h>
+/*extended nifti intent codes*/
+const int32_t NIFTI_INTENT_CONNECTIVITY_UNKNOWN=3000;
+const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE=3001;
+const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_TIME=3002;//CIFTI-1 name
+const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES=3002;//CIFTI-2 name
+const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED=3003;
+const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_TIME=3004;//ditto
+const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SERIES=3004;
+const int32_t NIFTI_INTENT_CONNECTIVITY_CONNECTIVITY_TRAJECTORY=3005;//ditto
+const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_TRAJECTORY=3005;
+const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_SCALARS=3006;
+const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_LABELS=3007;
+const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SCALAR=3008;
+const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_DENSE=3009;
+const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_PARCELLATED=3010;
+const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SERIES=3011;
+const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SCALAR=3012;
+
+const int32_t NIFTI_ECODE_CIFTI=32;
+
+#define NIFTI2_VERSION(h) \
+    (h).sizeof_hdr == 348 ? 1 : (\
+    (h).sizeof_hdr == 1543569408 ? 1 : (\
+    (h).sizeof_hdr == 540 ? 2 : (\
+    (h).sizeof_hdr == 469893120 ? 2 : 0)))
+
+#define NIFTI2_NEEDS_SWAP(h) \
+    (h).sizeof_hdr == 469893120 ? 1 : (\
+    (h).sizeof_hdr == 1543569408 ? 1 : 0)
+
+//hopefully cross-platform solution to byte padding added by some compilers
+#pragma pack(push)
+#pragma pack(1)
+
+/*! \struct nifti_2_header
+    \brief Data structure defining the fields in the nifti2 header.
+           This binary header should be found at the beginning of a valid
+           NIFTI-2 header file.
+ */
+/*************************/  /************************/ /************/
+struct nifti_2_header {   /* NIFTI-2 usage         */  /* NIFTI-1 usage        */ /*  offset  */
+    /*************************/  /************************/ /************/
+    int32_t   sizeof_hdr;     /*!< MUST be 540           */  /* int32_t sizeof_hdr; (348) */  /*   0 */
+    char  magic[8] ;      /*!< MUST be valid signature. */  /* char magic[4];     */  /*   4 */
+    int16_t datatype;       /*!< Defines data type!    */  /* short datatype;       */  /*  12 */
+    int16_t bitpix;         /*!< Number bits/voxel.    */  /* short bitpix;         */  /*  14 */
+    int64_t dim[8];     /*!< Data array dimensions.*/  /* short dim[8];         */  /*  16 */
+    double intent_p1 ;    /*!< 1st intent parameter. */  /* float intent_p1;      */  /*  80 */
+    double intent_p2 ;    /*!< 2nd intent parameter. */  /* float intent_p2;      */  /*  88 */
+    double intent_p3 ;    /*!< 3rd intent parameter. */  /* float intent_p3;      */  /*  96 */
+    double pixdim[8];     /*!< Grid spacings.        */  /* float pixdim[8];      */  /* 104 */
+    int64_t vox_offset; /*!< Offset into .nii file */  /* float vox_offset;     */  /* 168 */
+    double scl_slope ;    /*!< Data scaling: slope.  */  /* float scl_slope;      */  /* 176 */
+    double scl_inter ;    /*!< Data scaling: offset. */  /* float scl_inter;      */  /* 184 */
+    double cal_max;       /*!< Max display intensity */  /* float cal_max;        */  /* 192 */
+    double cal_min;       /*!< Min display intensity */  /* float cal_min;        */  /* 200 */
+    double slice_duration;/*!< Time for 1 slice.     */  /* float slice_duration; */  /* 208 */
+    double toffset;       /*!< Time axis shift.      */  /* float toffset;        */  /* 216 */
+    int64_t slice_start;/*!< First slice index.    */  /* short slice_start;    */  /* 224 */
+    int64_t slice_end;  /*!< Last slice index.     */  /* short slice_end;      */  /* 232 */
+    char  descrip[80];    /*!< any text you like.    */  /* char descrip[80];     */  /* 240 */
+    char  aux_file[24];   /*!< auxiliary filename.   */  /* char aux_file[24];    */  /* 320 */
+    int32_t qform_code ;      /*!< NIFTI_XFORM_* code.   */ /* short qform_code;      */  /* 344 */
+    int32_t sform_code ;      /*!< NIFTI_XFORM_* code.   */ /* short sform_code;      */  /* 348 */
+    double quatern_b ;    /*!< Quaternion b param.   */ /* float quatern_b;       */  /* 352 */
+    double quatern_c ;    /*!< Quaternion c param.   */ /* float quatern_c;       */  /* 360 */
+    double quatern_d ;    /*!< Quaternion d param.   */ /* float quatern_d;       */  /* 368 */
+    double qoffset_x ;    /*!< Quaternion x shift.   */ /* float qoffset_x;       */  /* 376 */
+    double qoffset_y ;    /*!< Quaternion y shift.   */ /* float qoffset_y;       */  /* 384 */
+    double qoffset_z ;    /*!< Quaternion z shift.   */ /* float qoffset_z;       */  /* 392 */
+    double srow_x[4] ;    /*!< 1st row affine transform. */  /* float srow_x[4];  */  /* 400 */
+    double srow_y[4] ;    /*!< 2nd row affine transform. */  /* float srow_y[4];  */  /* 432 */
+    double srow_z[4] ;    /*!< 3rd row affine transform. */  /* float srow_z[4];  */  /* 464 */
+    int32_t slice_code ;      /*!< Slice timing order.   */  /* char slice_code;      */  /* 496 */
+    int32_t xyzt_units ;      /*!< Units of pixdim[1..4] */  /* char xyzt_units;      */  /* 500 */
+    int32_t intent_code ;     /*!< NIFTI_INTENT_* code.  */  /* short intent_code;    */  /* 504 */
+    char intent_name[16]; /*!< 'name' or meaning of data. */ /* char intent_name[16]; */  /* 508 */
+    char dim_info;        /*!< MRI slice ordering.   */      /* char dim_info;        */  /* 524 */
+    char unused_str[15];  /*!< unused, filled with \0 */                                  /* 525 */
+} ;                       /**** 540 bytes total ****/
+typedef struct nifti_2_header nifti_2_header ;
+
+//and restore packing behavior
+#pragma pack(pop)
+
+}//namespace
+
+#endif //__NIFTI2_HEADER
diff --git a/src/NiftiIO.cxx b/src/NiftiIO.cxx
new file mode 100644
index 0000000..1c582a0
--- /dev/null
+++ b/src/NiftiIO.cxx
@@ -0,0 +1,134 @@
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Common/CiftiAssert.h"
+#include "NiftiIO.h"
+
+#include "Common/CiftiException.h"
+
+using namespace std;
+using namespace cifti;
+
+void NiftiIO::openRead(const AString& filename)
+{
+    m_file.open(filename);
+    m_header.read(m_file);
+    if (m_header.getDataType() == DT_BINARY)
+    {
+        throw CiftiException("file uses the binary datatype, which is unsupported: " + filename);
+    }
+    m_dims = m_header.getDimensions();
+}
+
+void NiftiIO::writeNew(const AString& filename, const NiftiHeader& header, const int& version, const bool& withRead, const bool& swapEndian)
+{
+    if (header.getDataType() == DT_BINARY)
+    {
+        throw CiftiException("writing NIFTI with binary datatype is unsupported");
+    }
+    if (withRead)
+    {
+        m_file.open(filename, BinaryFile::READ_WRITE_TRUNCATE);//for cifti on-disk writing, replace structure with along row needs to RMW
+    } else {
+        m_file.open(filename, BinaryFile::WRITE_TRUNCATE);
+    }
+    m_header = header;
+    m_header.write(m_file, version, swapEndian);//the header's getDataOffset() is not what gets written, as it doesn't reflect changes in the extensions
+    m_dims = m_header.getDimensions();
+}
+
+void NiftiIO::close()
+{
+    m_file.close();
+    m_dims.clear();
+}
+
+int NiftiIO::getNumComponents() const
+{
+    switch (m_header.getDataType())
+    {
+        case NIFTI_TYPE_RGB24:
+            return 3;
+            break;
+        case NIFTI_TYPE_COMPLEX64:
+        case NIFTI_TYPE_COMPLEX128:
+        case NIFTI_TYPE_COMPLEX256:
+            return 2;
+            break;
+        case NIFTI_TYPE_INT8:
+        case NIFTI_TYPE_UINT8:
+        case NIFTI_TYPE_INT16:
+        case NIFTI_TYPE_UINT16:
+        case NIFTI_TYPE_INT32:
+        case NIFTI_TYPE_UINT32:
+        case NIFTI_TYPE_FLOAT32:
+        case NIFTI_TYPE_INT64:
+        case NIFTI_TYPE_UINT64:
+        case NIFTI_TYPE_FLOAT64:
+        case NIFTI_TYPE_FLOAT128:
+            return 1;
+            break;
+        default:
+            CiftiAssert(0);
+            throw CiftiException("internal error, report what you did to the developers");
+    }
+}
+
+int NiftiIO::numBytesPerElem()
+{
+    switch (m_header.getDataType())
+    {
+        case NIFTI_TYPE_INT8:
+        case NIFTI_TYPE_UINT8:
+        case NIFTI_TYPE_RGB24:
+            return 1;
+            break;
+        case NIFTI_TYPE_INT16:
+        case NIFTI_TYPE_UINT16:
+            return 2;
+            break;
+        case NIFTI_TYPE_INT32:
+        case NIFTI_TYPE_UINT32:
+        case NIFTI_TYPE_FLOAT32:
+        case NIFTI_TYPE_COMPLEX64:
+            return 4;
+            break;
+        case NIFTI_TYPE_INT64:
+        case NIFTI_TYPE_UINT64:
+        case NIFTI_TYPE_FLOAT64:
+        case NIFTI_TYPE_COMPLEX128:
+            return 8;
+            break;
+        case NIFTI_TYPE_FLOAT128:
+        case NIFTI_TYPE_COMPLEX256:
+            return 16;
+            break;
+        default:
+            CiftiAssert(0);
+            throw CiftiException("internal error, report what you did to the developers");
+    }
+}
diff --git a/src/NiftiIO.h b/src/NiftiIO.h
new file mode 100644
index 0000000..509713a
--- /dev/null
+++ b/src/NiftiIO.h
@@ -0,0 +1,305 @@
+#ifndef __NIFTI_IO_H__
+#define __NIFTI_IO_H__
+
+/*LICENSE_START*/ 
+/*
+ *  Copyright (c) 2014, Washington University School of Medicine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without modification,
+ *  are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Common/AString.h"
+
+#include "Common/ByteSwapping.h"
+#include "Common/BinaryFile.h"
+#include "Common/CiftiException.h"
+#include "Common/CiftiMutex.h"
+#include "Nifti/NiftiHeader.h"
+
+//include MultiDimIterator from a private include directory, in case people want to use it with NiftiIO
+#include "Common/MultiDimIterator.h"
+
+#include <cmath>
+#include <limits>
+#include <vector>
+
+namespace cifti
+{
+    
+    class NiftiIO
+    {
+        BinaryFile m_file;
+        NiftiHeader m_header;
+        std::vector<int64_t> m_dims;
+        std::vector<char> m_scratch;//scratch memory for byteswapping, type conversion, etc
+        CiftiMutex m_mutex;
+        int numBytesPerElem();//for resizing scratch
+        template<typename TO, typename FROM>
+        void convertRead(TO* out, FROM* in, const int64_t& count);//for reading from file
+        template<typename TO, typename FROM>
+        void convertWrite(TO* out, const FROM* in, const int64_t& count);//for writing to file
+    public:
+        void openRead(const AString& filename);
+        void writeNew(const AString& filename, const NiftiHeader& header, const int& version = 1, const bool& withRead = false, const bool& swapEndian = false);
+        AString getFilename() const { return m_file.getFilename(); }
+        void overrideDimensions(const std::vector<int64_t>& newDims) { m_dims = newDims; }//HACK: deal with reading/writing CIFTI-1's broken headers
+        void close();
+        const NiftiHeader& getHeader() const { return m_header; }
+        const std::vector<int64_t>& getDimensions() const { return m_dims; }
+        int getNumComponents() const;
+        //to read/write 1 frame of a standard volume file, call with fullDims = 3, indexSelect containing indexes for any of dims 4-7 that exist
+        //NOTE: you need to provide storage for all components within the range, if getNumComponents() == 3 and fullDims == 0, you need 3 elements allocated
+        template<typename T>
+        void readData(T* dataOut, const int& fullDims, const std::vector<int64_t>& indexSelect, const bool& tolerateShortRead = false);
+        template<typename T>
+        void writeData(const T* dataIn, const int& fullDims, const std::vector<int64_t>& indexSelect);
+    };
+    
+    template<typename T>
+    void NiftiIO::readData(T* dataOut, const int& fullDims, const std::vector<int64_t>& indexSelect, const bool& tolerateShortRead)
+    {
+        if (fullDims < 0) throw CiftiException("NiftiIO: fulldims must not be negative");
+        if (fullDims > (int)m_dims.size()) throw CiftiException("NiftiIO: fulldims must not be greater than number of dimensions");
+        if ((size_t)fullDims + indexSelect.size() != m_dims.size())
+        {//could be >=, but should catch more stupid mistakes as ==
+            throw CiftiException("NiftiIO: fulldims plus length of indexSelect must equal number of dimensions");
+        }
+        int64_t numElems = getNumComponents();//for now, calculate read size on the fly, as the read call will be the slowest part
+        int curDim;
+        for (curDim = 0; curDim < fullDims; ++curDim)
+        {
+            numElems *= m_dims[curDim];
+        }
+        int64_t numDimSkip = numElems, numSkip = 0;
+        for (; curDim < (int)m_dims.size(); ++curDim)
+        {
+            if (indexSelect[curDim - fullDims] < 0) throw CiftiException("NiftiIO: indices must not be negative");
+            if (indexSelect[curDim - fullDims] >= m_dims[curDim]) throw CiftiException("NiftiIO: index exceeds nifti dimension length");
+            numSkip += indexSelect[curDim - fullDims] * numDimSkip;
+            numDimSkip *= m_dims[curDim];
+        }
+        CiftiMutexLocker locked(&m_mutex);//protect starting with resizing until we are done converting, because we use an internal variable for scratch space
+        //we can't guarantee that the output memory is enough to use as scratch space, as we might be doing a narrowing conversion
+        //we are doing FILE ACCESS, so cpu performance isn't really something to worry about
+        m_scratch.resize(numElems * numBytesPerElem());
+        m_file.seek(numSkip * numBytesPerElem() + m_header.getDataOffset());
+        int64_t numRead = 0;
+        m_file.read(m_scratch.data(), m_scratch.size(), &numRead);
+        if ((numRead != (int64_t)m_scratch.size() && !tolerateShortRead) || numRead < 0)//for now, assume read giving -1 is always a problem
+        {
+            throw CiftiException("error while reading from file '" + m_file.getFilename() + "'");
+        }
+        switch (m_header.getDataType())
+        {
+            case NIFTI_TYPE_UINT8:
+            case NIFTI_TYPE_RGB24://handled by components
+                convertRead(dataOut, (uint8_t*)m_scratch.data(), numElems);
+                break;
+            case NIFTI_TYPE_INT8:
+                convertRead(dataOut, (int8_t*)m_scratch.data(), numElems);
+                break;
+            case NIFTI_TYPE_UINT16:
+                convertRead(dataOut, (uint16_t*)m_scratch.data(), numElems);
+                break;
+            case NIFTI_TYPE_INT16:
+                convertRead(dataOut, (int16_t*)m_scratch.data(), numElems);
+                break;
+            case NIFTI_TYPE_UINT32:
+                convertRead(dataOut, (uint32_t*)m_scratch.data(), numElems);
+                break;
+            case NIFTI_TYPE_INT32:
+                convertRead(dataOut, (int32_t*)m_scratch.data(), numElems);
+                break;
+            case NIFTI_TYPE_UINT64:
+                convertRead(dataOut, (uint64_t*)m_scratch.data(), numElems);
+                break;
+            case NIFTI_TYPE_INT64:
+                convertRead(dataOut, (int64_t*)m_scratch.data(), numElems);
+                break;
+            case NIFTI_TYPE_FLOAT32:
+            case NIFTI_TYPE_COMPLEX64://components
+                convertRead(dataOut, (float*)m_scratch.data(), numElems);
+                break;
+            case NIFTI_TYPE_FLOAT64:
+            case NIFTI_TYPE_COMPLEX128:
+                convertRead(dataOut, (double*)m_scratch.data(), numElems);
+                break;
+            case NIFTI_TYPE_FLOAT128:
+            case NIFTI_TYPE_COMPLEX256:
+                convertRead(dataOut, (long double*)m_scratch.data(), numElems);
+                break;
+            default:
+                throw CiftiException("internal error, tell the developers what you just tried to do");
+        }
+    }
+    
+    template<typename T>
+    void NiftiIO::writeData(const T* dataIn, const int& fullDims, const std::vector<int64_t>& indexSelect)
+    {
+        if (fullDims < 0) throw CiftiException("NiftiIO: fulldims must not be negative");
+        if (fullDims > (int)m_dims.size()) throw CiftiException("NiftiIO: fulldims must not be greater than number of dimensions");
+        if ((size_t)fullDims + indexSelect.size() != m_dims.size())
+        {//could be >=, but should catch more stupid mistakes as ==
+            throw CiftiException("NiftiIO: fulldims plus length of indexSelect must equal number of dimensions");
+        }
+        int64_t numElems = getNumComponents();//for now, calculate read size on the fly, as the read call will be the slowest part
+        int curDim;
+        for (curDim = 0; curDim < fullDims; ++curDim)
+        {
+            numElems *= m_dims[curDim];
+        }
+        int64_t numDimSkip = numElems, numSkip = 0;
+        for (; curDim < (int)m_dims.size(); ++curDim)
+        {
+            if (indexSelect[curDim - fullDims] < 0) throw CiftiException("NiftiIO: indices must not be negative");
+            if (indexSelect[curDim - fullDims] >= m_dims[curDim]) throw CiftiException("NiftiIO: index exceeds nifti dimension length");
+            numSkip += indexSelect[curDim - fullDims] * numDimSkip;
+            numDimSkip *= m_dims[curDim];
+        }
+        CiftiMutexLocker locked(&m_mutex);//protect starting with resizing until we are done writing, because we use an internal variable for scratch space
+        //we are doing FILE ACCESS, so cpu performance isn't really something to worry about
+        m_scratch.resize(numElems * numBytesPerElem());
+        m_file.seek(numSkip * numBytesPerElem() + m_header.getDataOffset());
+        switch (m_header.getDataType())
+        {
+            case NIFTI_TYPE_UINT8:
+            case NIFTI_TYPE_RGB24://handled by components
+                convertWrite((uint8_t*)m_scratch.data(), dataIn, numElems);
+                break;
+            case NIFTI_TYPE_INT8:
+                convertWrite((int8_t*)m_scratch.data(), dataIn, numElems);
+                break;
+            case NIFTI_TYPE_UINT16:
+                convertWrite((uint16_t*)m_scratch.data(), dataIn, numElems);
+                break;
+            case NIFTI_TYPE_INT16:
+                convertWrite((int16_t*)m_scratch.data(), dataIn, numElems);
+                break;
+            case NIFTI_TYPE_UINT32:
+                convertWrite((uint32_t*)m_scratch.data(), dataIn, numElems);
+                break;
+            case NIFTI_TYPE_INT32:
+                convertWrite((int32_t*)m_scratch.data(), dataIn, numElems);
+                break;
+            case NIFTI_TYPE_UINT64:
+                convertWrite((uint64_t*)m_scratch.data(), dataIn, numElems);
+                break;
+            case NIFTI_TYPE_INT64:
+                convertWrite((int64_t*)m_scratch.data(), dataIn, numElems);
+                break;
+            case NIFTI_TYPE_FLOAT32:
+            case NIFTI_TYPE_COMPLEX64://components
+                convertWrite((float*)m_scratch.data(), dataIn, numElems);
+                break;
+            case NIFTI_TYPE_FLOAT64:
+            case NIFTI_TYPE_COMPLEX128:
+                convertWrite((double*)m_scratch.data(), dataIn, numElems);
+                break;
+            case NIFTI_TYPE_FLOAT128:
+            case NIFTI_TYPE_COMPLEX256:
+                convertWrite((long double*)m_scratch.data(), dataIn, numElems);
+                break;
+            default:
+                throw CiftiException("internal error, tell the developers what you just tried to do");
+        }
+        m_file.write(m_scratch.data(), m_scratch.size());
+    }
+    
+    template<typename TO, typename FROM>
+    void NiftiIO::convertRead(TO* out, FROM* in, const int64_t& count)
+    {
+        if (m_header.isSwapped())
+        {
+            ByteSwapping::swapArray(in, count);
+        }
+        double mult, offset;
+        bool doScale = m_header.getDataScaling(mult, offset);
+        if (std::numeric_limits<TO>::is_integer)//do round to nearest when integer output type
+        {
+            if (doScale)
+            {
+                for (int64_t i = 0; i < count; ++i)
+                {
+                    out[i] = (TO)floor(0.5 + offset + mult * (long double)in[i]);//we don't always need that much precision, but it will still be faster than hard drives
+                }
+            } else {
+                for (int64_t i = 0; i < count; ++i)
+                {
+                    out[i] = (TO)floor(0.5 + in[i]);
+                }
+            }
+        } else {
+            if (doScale)
+            {
+                for (int64_t i = 0; i < count; ++i)
+                {
+                    out[i] = (TO)(offset + mult * (long double)in[i]);//we don't always need that much precision, but it will still be faster than hard drives
+                }
+            } else {
+                for (int64_t i = 0; i < count; ++i)
+                {
+                    out[i] = (TO)in[i];//explicit cast to make sure the compiler doesn't squawk
+                }
+            }
+        }
+    }
+    
+    template<typename TO, typename FROM>
+    void NiftiIO::convertWrite(TO* out, const FROM* in, const int64_t& count)
+    {
+        double mult, offset;
+        bool doScale = m_header.getDataScaling(mult, offset);
+        if (std::numeric_limits<TO>::is_integer)//do round to nearest when integer output type
+        {
+            if (doScale)
+            {
+                for (int64_t i = 0; i < count; ++i)
+                {
+                    out[i] = (TO)floor(0.5 + ((long double)in[i] - offset) / mult);//we don't always need that much precision, but it will still be faster than hard drives
+                }
+            } else {
+                for (int64_t i = 0; i < count; ++i)
+                {
+                    out[i] = (TO)floor(0.5 + in[i]);
+                }
+            }
+        } else {
+            if (doScale)
+            {
+                for (int64_t i = 0; i < count; ++i)
+                {
+                    out[i] = (TO)(((long double)in[i] - offset) / mult);//we don't always need that much precision, but it will still be faster than hard drives
+                }
+            } else {
+                for (int64_t i = 0; i < count; ++i)
+                {
+                    out[i] = (TO)in[i];//explicit cast to make sure the compiler doesn't squawk
+                }
+            }
+        }
+        if (m_header.isSwapped()) ByteSwapping::swapArray(out, count);
+    }
+    
+}
+
+#endif //__NIFTI_IO_H__

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/ciftilib.git



More information about the debian-med-commit mailing list