[med-svn] [Git][med-team/ciftilib][upstream/latest] New upstream version 1.5.3

Andreas Tille gitlab at salsa.debian.org
Wed Jan 30 09:35:50 GMT 2019


Andreas Tille pushed to branch upstream/latest at Debian Med / ciftilib


Commits:
84a45634 by Andreas Tille at 2019-01-30T08:40:33Z
New upstream version 1.5.3
- - - - -


20 changed files:

- .travis.yml
- CMakeLists.txt
- Doxyfile.in
- README
- example/CMakeLists.txt
- + example/datatype.cxx
- src/CMakeLists.txt
- src/Cifti/CMakeLists.txt
- src/Cifti/CiftiBrainModelsMap.cxx
- src/CiftiFile.cxx
- src/CiftiFile.h
- src/Common/AString.h
- src/Common/BinaryFile.cxx
- src/Common/BinaryFile.h
- src/Common/CMakeLists.txt
- src/Nifti/CMakeLists.txt
- src/Nifti/NiftiHeader.cxx
- src/Nifti/NiftiHeader.h
- src/NiftiIO.cxx
- src/NiftiIO.h


Changes:

=====================================
.travis.yml
=====================================
@@ -19,10 +19,11 @@ compiler:
     - gcc
 
 env:
-    - IGNORE_QT=false SHARED=true
-    - IGNORE_QT=false SHARED=false
-    - IGNORE_QT=true SHARED=true
-    - IGNORE_QT=true SHARED=false
+    matrix:
+        - IGNORE_QT=false SHARED=true
+        - IGNORE_QT=false SHARED=false
+        - IGNORE_QT=true SHARED=true
+        - IGNORE_QT=true SHARED=false
 
 before_install:
     - mkdir ../build
@@ -30,5 +31,7 @@ before_install:
 
 script:
     - cmake -D BUILD_SHARED_LIBS:BOOL=$SHARED -D IGNORE_QT:BOOL=$IGNORE_QT ../CiftiLib
+    - export LD_LIBRARY_PATH=$(if [[ $CXX == "clang++" ]]; then echo -n '/usr/local/clang/lib'; fi)
     - make -j 4
+    - example/xmlinfo ../CiftiLib/example/data/ones.dscalar.nii
     - ctest


=====================================
CMakeLists.txt
=====================================
@@ -41,7 +41,7 @@ IF (NOT IGNORE_QT)
             SET(LIBS ${LIBS} Qt5::Core)
             #whatever that means
             ADD_DEFINITIONS(-DCIFTILIB_USE_QT)
-            SET(CIFTILIB_PKGCONFIG_REQUIRES_LINE "Requires: Qt5Core Qt5Xml")
+            SET(CIFTILIB_PKGCONFIG_REQUIRES_LINE "Requires: Qt5Core")
             SET(CIFTILIB_PKGCONFIG_DEFINE "-DCIFTILIB_USE_QT")
         ENDIF (Qt5Core_FOUND)
     ENDIF (QT_FOUND)


=====================================
Doxyfile.in
=====================================
@@ -231,7 +231,7 @@ EXTENSION_MAPPING      =
 # func(std::string) {}). This also make the inheritance and collaboration 
 # diagrams that involve STL classes more complete and accurate.
 
-BUILTIN_STL_SUPPORT    = NO
+BUILTIN_STL_SUPPORT    = YES
 
 # If you use Microsoft's C++/CLI language, you should set this option to YES to 
 # enable parsing support.


=====================================
README
=====================================
@@ -1,27 +1,41 @@
 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:
+To build it, and example executables, in the recommended "out-of-source" method using cmake:
 
 #start one level up from the source tree
-#make build directory beside the source tree, enter it
+#make a 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
+#NOTE: 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
+cmake -D CMAKE_BUILD_TYPE=Release ../CiftiLib
 #OR
 cmake-gui ../CiftiLib
 
 #build
 make
 
-#OPTIONAL: run tests, make docs
+#OPTIONAL: run tests
 make test
+
+#install library to location specified by cmake variable CMAKE_INSTALL_PREFIX
+make install
+
+#OPTIONAL: generate documentation (needs doxygen installed and on PATH)
 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'.
+The build results will be in subdirectories of the build directory, not in the source directory.
+
+To use the installed library, use pkg-config to get the needed directories and libraries.  For instance, in cmake:
+
+find_package(PkgConfig)
+pkg_check_modules(PC_CIFTI REQUIRED CiftiLib)
+
+You can then use cmake variables PC_CIFTI_LIBRARIES, PC_CIFTI_INCLUDE_DIRS, and similar, see here for descriptions:
+
+https://cmake.org/cmake/help/v3.0/module/FindPkgConfig.html
 
 Troubleshooting:
 


=====================================
example/CMakeLists.txt
=====================================
@@ -1,6 +1,4 @@
 
-PROJECT(example)
-
 ADD_EXECUTABLE(rewrite
 rewrite.cxx)
 
@@ -15,6 +13,13 @@ TARGET_LINK_LIBRARIES(xmlinfo
 Cifti
 ${LIBS})
 
+ADD_EXECUTABLE(datatype
+datatype.cxx)
+
+TARGET_LINK_LIBRARIES(datatype
+Cifti
+${LIBS})
+
 INCLUDE_DIRECTORIES(
 ${CMAKE_SOURCE_DIR}/example
 ${CMAKE_SOURCE_DIR}/src
@@ -29,7 +34,7 @@ ones.dscalar.nii
 )
 
 IF(QT_FOUND)
-    #QT4
+    #QT4 and QT5
     SET(cifti_be_md5s
         3ba20f6ef590735c1211991f8e0144e6
         e3a1639ef4b354752b0abb0613eedb73
@@ -44,6 +49,13 @@ IF(QT_FOUND)
         32345267599b07083092b7dedfd8796c
         512e0359c64d69dde93d605f8797f3a2
     )
+    SET(cifti_datatype_md5s
+        cca91b955b1134251d62764cb1ebf44c
+        6359af74ba6b51357aefab7de7a76097
+        10ee62309850e55936fa9f702df8b4d1
+        e4997bdd4b8202ff502a19173693c43f
+        870dae2d646cadeed1494f6271433499
+    )
 ELSE(QT_FOUND)
     #xml++
     SET(cifti_be_md5s
@@ -60,6 +72,13 @@ ELSE(QT_FOUND)
         6fabac021e377efd35dede7198feefd4
         fe0cbb768e26aa12a0e03990f4f50a30
     )
+    SET(cifti_datatype_md5s
+        6db4a73e4e11a1ac0a5e7cbfb56eff40
+        f321156573ed8f165b208d84769bfd9a
+        794d60d9d397fe341e18313efeeac5ea
+        ea43725139bd3e152197fdf22c5e72e7
+        4dbb23ab2564ba8c9f242a3cb6036600
+    )
 ENDIF(QT_FOUND)
 
 #ADD_TEST(timer ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver timer)
@@ -76,8 +95,16 @@ FOREACH(index RANGE ${loop_end})
     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)
+    SET_TESTS_PROPERTIES(rewrite-little-md5-${testfile} PROPERTIES DEPENDS rewrite-little-${testfile})
 
     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)
+    SET_TESTS_PROPERTIES(rewrite-big-md5-${testfile} PROPERTIES DEPENDS rewrite-big-${testfile})
+    
+    ADD_TEST(datatype-${testfile} datatype ${CMAKE_SOURCE_DIR}/example/data/${testfile} datatype-${testfile})
+    LIST(GET cifti_datatype_md5s ${index} goodsum)
+    ADD_TEST(datatype-md5-${testfile} ${CMAKE_COMMAND} -Dgood_sum=${goodsum} -Dcheck_file=datatype-${testfile} -P ${CMAKE_SOURCE_DIR}/cmake/scripts/testmd5.cmake)
+    SET_TESTS_PROPERTIES(rewrite-big-md5-${testfile} PROPERTIES DEPENDS datatype-${testfile})
+    
 ENDFOREACH(index RANGE ${loop_end})


=====================================
example/datatype.cxx
=====================================
@@ -0,0 +1,37 @@
+#include "CiftiFile.h"
+
+#include <iostream>
+#include <vector>
+
+using namespace std;
+using namespace cifti;
+
+/**\file datatype.cxx
+This program reads a Cifti file from argv[1], and writes it out to argv[2] using 8-bit unsigned integer and data scaling.
+It uses a single CiftiFile object to do this, for simplicity - to see how to do something similar with two objects,
+which is more relevant for how you would do processing on cifti files, see rewrite.cxx.
+
+\include datatype.cxx
+*/
+
+int main(int argc, char** argv)
+{
+    if (argc < 3)
+    {
+        cout << "usage: " << argv[0] << " <input cifti> <output cifti>" << endl;
+        cout << "  rewrite the input cifti file to the output filename, using uint8 and data scaling." << endl;
+        return 1;
+    }
+    try
+    {
+        CiftiFile inputFile(argv[1]);//on-disk reading by default
+        inputFile.setWritingDataTypeAndScaling(NIFTI_TYPE_UINT8, -1.0, 6.0);//tells it to use this datatype to best represent this specified range of values [-1.0, 6.0] whenever this instance is written
+        inputFile.writeFile(argv[2]);//if this is the same filename as the input, CiftiFile actually detects this and reads the input into memory first
+        //otherwise, it will read and write one row at a time, using very little memory
+        //inputFile.setWritingDataTypeNoScaling(NIFTI_TYPE_FLOAT32);//this is how you would revert back to writing as float32 without rescaling
+    } catch (CiftiException& e) {
+        cerr << "Caught CiftiException: " + AString_to_std_string(e.whatString()) << endl;
+        return 1;
+    }
+    return 0;
+}


=====================================
src/CMakeLists.txt
=====================================
@@ -1,4 +1,3 @@
-PROJECT(src)
 
 SET(HEADERS
 CiftiFile.h


=====================================
src/Cifti/CMakeLists.txt
=====================================
@@ -1,6 +1,4 @@
 
-PROJECT(Cifti)
-
 SET(HEADERS
 CiftiXML.h
 CiftiVersion.h


=====================================
src/Cifti/CiftiBrainModelsMap.cxx
=====================================
@@ -262,7 +262,7 @@ const vector<int64_t>& CiftiBrainModelsMap::getNodeList(const StructureEnum::Enu
     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
+        throw CiftiException("getNodeList called for nonexistent 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;
@@ -274,7 +274,7 @@ vector<CiftiBrainModelsMap::SurfaceMap> CiftiBrainModelsMap::getSurfaceMap(const
     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
+        throw CiftiException("getSurfaceMap called for nonexistent structure");//also throw, for consistency
     }
     CiftiAssertVectorIndex(m_modelsInfo, iter->second);
     const BrainModelPriv& myModel = m_modelsInfo[iter->second];
@@ -380,7 +380,7 @@ vector<CiftiBrainModelsMap::VolumeMap> CiftiBrainModelsMap::getVolumeStructureMa
     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
+        throw CiftiException("getVolumeStructureMap called for nonexistent structure");//also throw, for consistency
     }
     CiftiAssertVectorIndex(m_modelsInfo, iter->second);
     const BrainModelPriv& myModel = m_modelsInfo[iter->second];
@@ -404,7 +404,7 @@ const vector<int64_t>& CiftiBrainModelsMap::getVoxelList(const StructureEnum::En
     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
+        throw CiftiException("getVoxelList called for nonexistent 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;


=====================================
src/CiftiFile.cxx
=====================================
@@ -54,7 +54,8 @@ namespace
         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
+        CiftiOnDiskImpl(const AString& filename, const CiftiXML& xml, const CiftiVersion& version, const bool& swapEndian,
+                        const int16_t& datatype, const bool& rescale, const double& minval, const double& maxval);//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; }
@@ -62,6 +63,7 @@ namespace
         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);
+        void close();
     };
     
     class CiftiMemoryImpl : public CiftiFile::WriteImplInterface
@@ -135,17 +137,22 @@ CiftiFile::WriteImplInterface::~WriteImplInterface()
 {
 }
 
+CiftiFile::CiftiFile()
+{
+    m_endianPref = NATIVE;
+    setWritingDataTypeNoScaling();//default argument is float32
+}
+
 CiftiFile::CiftiFile(const AString& fileName)
 {
     m_endianPref = NATIVE;
+    setWritingDataTypeNoScaling();//default argument is float32
     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();
+    close();//to make sure it closes everything first, even if the open throws
     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();
@@ -161,15 +168,33 @@ void CiftiFile::setWritingFile(const AString& fileName, const CiftiVersion& writ
     m_endianPref = endian;
 }
 
+void CiftiFile::setWritingDataTypeNoScaling(const int16_t& type)
+{
+    m_writingDataType = type;//could do some validation here
+    m_doWriteScaling = false;
+    m_minScalingVal = -1.0;//these scaling values should never be used, but don't leave them uninitialized
+    m_maxScalingVal = 1.0;
+    m_writingImpl.reset();//prevent writing to previous writing implementation, let the next set...() set up for writing
+}
+
+void CiftiFile::setWritingDataTypeAndScaling(const int16_t& type, const double& minval, const double& maxval)
+{
+    m_writingDataType = type;//could do some validation here
+    m_doWriteScaling = true;
+    m_minScalingVal = minval;
+    m_maxScalingVal = maxval;
+    m_writingImpl.reset();//prevent writing to previous writing implementation, let the next set...() set up for writing
+}
+
 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
+    AString canonicalFilename = pathToCanonical(fileName);//NOTE: returns EMPTY STRING for nonexistent 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
+    {//empty string test is so that we don't say collision if both are nonexistent - 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
@@ -177,7 +202,8 @@ void CiftiFile::writeFile(const AString& fileName, const CiftiVersion& writingVe
         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));
+    boost::shared_ptr<WriteImplInterface> tempWrite(new CiftiOnDiskImpl(pathToAbsolute(fileName), m_xml, writingVersion, writeSwapped,
+                                                                        m_writingDataType, m_doWriteScaling, m_minScalingVal, m_maxScalingVal));
     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
     {
@@ -190,6 +216,22 @@ void CiftiFile::writeFile(const AString& fileName, const CiftiVersion& writingVe
     }
 }
 
+void CiftiFile::close()
+{
+    if (m_writingImpl != NULL)
+    {
+        m_writingImpl->close();//only writing implementations should ever throw errors on close, and specifically only on-disk
+    }
+    m_writingImpl.reset();
+    m_readingImpl.reset();
+    m_dims.clear();
+    m_xml = CiftiXML();
+    m_writingFile = "";
+    m_onDiskVersion = CiftiVersion();//for completeness, it gets reset on open anyway
+    m_endianPref = NATIVE;//reset things to defaults
+    setWritingDataTypeNoScaling();//default argument is float32
+}
+
 void CiftiFile::convertToInMemory()
 {
     if (isInMemory()) return;
@@ -231,9 +273,14 @@ void CiftiFile::getColumn(float* dataOut, const int64_t& index) const
 
 void CiftiFile::setCiftiXML(const CiftiXML& xml, const bool useOldMetadata)
 {
+    if (xml.getNumberOfDimensions() == 0) throw CiftiException("setCiftiXML called with 0-dimensional CiftiXML");
+    vector<int64_t> xmlDims = xml.getDimensions();
+    for (size_t i = 0; i < xmlDims.size(); ++i)
+    {
+        if (xmlDims[i] < 1) throw CiftiException("cifti xml dimensions must be greater than zero");
+    }
     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
@@ -242,11 +289,7 @@ void CiftiFile::setCiftiXML(const CiftiXML& xml, const bool useOldMetadata)
     } 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");
-    }
+    m_dims = xmlDims;
 }
 
 void CiftiFile::setRow(const float* dataIn, const vector<int64_t>& indexSelect)
@@ -300,14 +343,15 @@ void CiftiFile::verifyWriteImpl()
             CiftiOnDiskImpl* testImpl = dynamic_cast<CiftiOnDiskImpl*>(m_readingImpl.get());
             if (testImpl != NULL)
             {
-                AString canonicalCurrent = pathToCanonical(testImpl->getFilename());//returns "" if nonexistant, if unlinked while open
+                AString canonicalCurrent = pathToCanonical(testImpl->getFilename());//returns "" if nonexistent, 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
+        m_writingImpl = boost::shared_ptr<CiftiOnDiskImpl>(new CiftiOnDiskImpl(m_writingFile, m_xml, m_onDiskVersion, shouldSwap(m_endianPref),
+                                                                               m_writingDataType, m_doWriteScaling, m_minScalingVal, m_maxScalingVal));//this constructor makes new file for writing
         if (m_readingImpl != NULL)
         {
             copyImplData(m_readingImpl.get(), m_writingImpl.get(), m_dims);
@@ -412,7 +456,7 @@ CiftiOnDiskImpl::CiftiOnDiskImpl(const AString& filename)
     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
+        if (m_xml.getDimensionLength(i - 4) < 0)//CiftiXML will only let this happen with cifti-1
         {
             m_xml.getSeriesMap(i - 4).setLength(dimCheck[i]);//and only in a series map
         } else {
@@ -424,10 +468,126 @@ CiftiOnDiskImpl::CiftiOnDiskImpl(const AString& filename)
     }
 }
 
-CiftiOnDiskImpl::CiftiOnDiskImpl(const AString& filename, const CiftiXML& xml, const CiftiVersion& version, const bool& swapEndian)
+namespace
+{
+    void warnForBadExtension(const AString& filename, const CiftiXML& myXML)
+    {
+        char junk[16];
+        int32_t intent_code = myXML.getIntentInfo(CiftiVersion(), junk);//use default writing version to check file extension, older version is missing some intent codes
+        switch (intent_code)
+        {
+            case 3000://unknown
+                if (!AString_endsWith(filename, ".nii"))
+                {
+                    cerr << "warning: cifti file of nonstandard mapping combination '" << AString_to_std_string(filename) << "' should be saved ending in .<something>.nii" << endl;
+                }
+                if (AString_endsWith(filename, ".dconn.nii") ||
+                    AString_endsWith(filename, ".dtseries.nii") ||
+                    AString_endsWith(filename, ".pconn.nii") ||
+                    AString_endsWith(filename, ".ptseries.nii") ||
+                    AString_endsWith(filename, ".dscalar.nii") ||
+                    AString_endsWith(filename, ".dfan.nii") ||
+                    AString_endsWith(filename, ".fiberTemp.nii") ||
+                    AString_endsWith(filename, ".dlabel.nii") ||
+                    AString_endsWith(filename, ".pscalar.nii") ||
+                    AString_endsWith(filename, ".pdconn.nii") ||
+                    AString_endsWith(filename, ".dpconn.nii") ||
+                    AString_endsWith(filename, ".pconnseries.nii") ||
+                    AString_endsWith(filename, ".pconnscalar.nii"))
+                {
+                    cerr << "warning: cifti file of nonstandard mapping combination '" << AString_to_std_string(filename) << "' should NOT be saved using an already-used cifti extension, "
+                            << "please choose a different, reasonable cifti extension ending in .<something>.nii" << endl;
+                }
+                break;
+            case 3001:
+                if (!AString_endsWith(filename, ".dconn.nii"))
+                {
+                    cerr << "warning: dense by dense cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .dconn.nii" << endl;
+                }
+                break;
+            case 3002:
+                if (!AString_endsWith(filename, ".dtseries.nii"))
+                {
+                    cerr << "warning: series by dense cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .dtseries.nii" << endl;
+                }
+                break;
+            case 3003:
+                if (!AString_endsWith(filename, ".pconn.nii"))
+                {
+                    cerr << "warning: parcels by parcels cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .pconn.nii" << endl;
+                }
+                break;
+            case 3004:
+                if (!AString_endsWith(filename, ".ptseries.nii"))
+                {
+                    cerr << "warning: series by parcels cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .ptseries.nii" << endl;
+                }
+                break;
+            case 3006://3005 unused in practice
+                if (!(AString_endsWith(filename, ".dscalar.nii") || AString_endsWith(filename, ".dfan.nii") || AString_endsWith(filename, ".fiberTEMP.nii")))
+                {//there are additional special extensions in the standard for this mapping combination (specializations of scalar maps)
+                    //also include workbench's fiberTEMP special extension
+                    cerr << "warning: scalars by dense cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .dscalar.nii" << endl;
+                }
+                break;
+            case 3007:
+                if (!AString_endsWith(filename, ".dlabel.nii"))
+                {
+                    cerr << "warning: labels by dense cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .dlabel.nii" << endl;
+                }
+                break;
+            case 3008:
+                if (!AString_endsWith(filename, ".pscalar.nii"))
+                {
+                    cerr << "warning: scalars by parcels cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .pscalar.nii" << endl;
+                }
+                break;
+            case 3009:
+                if (!AString_endsWith(filename, ".pdconn.nii"))
+                {
+                    cerr << "warning: dense by parcels cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .pdconn.nii" << endl;
+                }
+                break;
+            case 3010:
+                if (!AString_endsWith(filename, ".dpconn.nii"))
+                {
+                    cerr << "warning: parcels by dense cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .dpconn.nii" << endl;
+                }
+                break;
+            case 3011:
+                if (!AString_endsWith(filename, ".pconnseries.nii"))
+                {
+                    cerr << "warning: parcels by parcels by series cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .pconnseries.nii" << endl;
+                }
+                break;
+            case 3012:
+                if (!AString_endsWith(filename, ".pconnscalar.nii"))
+                {
+                    cerr << "warning: parcels by parcels by scalar cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .pconnscalar.nii" << endl;
+                }
+                break;
+            default:
+                CiftiAssert(0);
+                throw CiftiException("internal error, tell the developers what you just tried to do");
+        }
+    }
+}
+
+CiftiOnDiskImpl::CiftiOnDiskImpl(const AString& filename, const CiftiXML& xml, const CiftiVersion& version, const bool& swapEndian,
+                                 const int16_t& datatype, const bool& rescale, const double& minval, const double& maxval)
 {//starts writing new file
+    warnForBadExtension(filename, xml);
     NiftiHeader outHeader;
-    outHeader.setDataType(NIFTI_TYPE_FLOAT32);//actually redundant currently, default is float32
+    if (rescale)
+    {
+        outHeader.setDataTypeAndScaleRange(datatype, minval, maxval);
+    } else {
+        outHeader.setDataType(datatype);
+    }
+    if (outHeader.getNumComponents() != 1)
+    {
+        throw CiftiException("cifti cannot be written with multi-component nifti datatypes (i.e., complex, RGB)");
+    }
     char intentName[16];
     int32_t intentCode = xml.getIntentInfo(version, intentName);
     outHeader.setIntent(intentCode, intentName);
@@ -455,6 +615,11 @@ CiftiOnDiskImpl::CiftiOnDiskImpl(const AString& filename, const CiftiXML& xml, c
     m_xml = xml;
 }
 
+void CiftiOnDiskImpl::close()
+{
+    m_nifti.close();//lets this throw when there is a writing problem
+}//don't bother resetting m_xml, this instance is about to be destroyed
+
 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


=====================================
src/CiftiFile.h
=====================================
@@ -32,6 +32,7 @@
 #include "Common/CiftiException.h"
 #include "Common/MultiDimIterator.h"
 #include "Cifti/CiftiXML.h"
+#include "Nifti/nifti1.h"
 
 #include "boost/shared_ptr.hpp"
 
@@ -53,8 +54,8 @@ namespace cifti
             BIG
         };
         
-        CiftiFile() { m_endianPref = NATIVE; }
-        
+        CiftiFile();
+
         ///starts on-disk reading
         explicit CiftiFile(const AString &fileName);
         
@@ -67,6 +68,9 @@ namespace cifti
         ///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);
         
+        ///closes the underlying file to flush it, so that exceptions can be thrown
+        void close();
+
         ///reads file into memory, closes file
         void convertToInMemory();
         
@@ -97,6 +101,10 @@ namespace cifti
         
         ///for 2D only, if you don't want to pass a vector for indexing
         void setRow(const float* dataIn, const int64_t& index);
+        
+        ///data type and scaling options - should be set before setRow, etc, to avoid rewriting of file
+        void setWritingDataTypeNoScaling(const int16_t& type = NIFTI_TYPE_FLOAT32);
+        void setWritingDataTypeAndScaling(const int16_t& type, const double& minval, const double& maxval);
 
         //implementation details from here down
         class ReadImplInterface
@@ -113,6 +121,7 @@ namespace cifti
         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 void close() {}
             virtual ~WriteImplInterface();
         };
     private:
@@ -123,6 +132,9 @@ namespace cifti
         CiftiXML m_xml;
         CiftiVersion m_onDiskVersion;
         ENDIAN m_endianPref;
+        bool m_doWriteScaling;
+        int16_t m_writingDataType;
+        double m_minScalingVal, m_maxScalingVal;
         
         void verifyWriteImpl();
         static void copyImplData(const ReadImplInterface* from, WriteImplInterface* to, const std::vector<int64_t>& dims);


=====================================
src/Common/AString.h
=====================================
@@ -58,6 +58,10 @@ namespace cifti
     {
         return mystr.mid(first, count);
     }
+    inline bool AString_endsWith(const AString& test, const AString& pattern)
+    {
+        return test.endsWith(pattern);
+    }
     template <typename T>
     AString AString_number(const T& num)
     {
@@ -93,6 +97,10 @@ namespace cifti
     {//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);
     }
+    inline bool AString_endsWith(const AString& test, const AString& pattern)
+    {
+        return test.substr(test.size() - pattern.size()) == pattern;
+    }
     template <typename T>
     AString AString_number(const T& num)
     {


=====================================
src/Common/BinaryFile.cxx
=====================================
@@ -40,6 +40,8 @@
     #include <QFile>
 #else
     #include "stdio.h"
+    #include "sys/stat.h"
+    #include "sys/types.h"
     #include "errno.h"
     #define BOOST_FILESYSTEM_VERSION 3
     #include "boost/filesystem.hpp"
@@ -70,6 +72,7 @@ namespace cifti
         void close();
         void seek(const int64_t& position);
         int64_t pos();
+        int64_t size() { return -1; }
         void read(void* dataOut, const int64_t& count, int64_t* numRead);
         void write(const void* dataIn, const int64_t& count);
         ~ZFileImpl();
@@ -88,6 +91,7 @@ namespace cifti
         void close();
         void seek(const int64_t& position);
         int64_t pos();
+        int64_t size() { return m_file.size(); }
         void read(void* dataOut, const int64_t& count, int64_t* numRead);
         void write(const void* dataIn, const int64_t& count);
     };
@@ -104,6 +108,7 @@ namespace cifti
         void close();
         void seek(const int64_t& position);
         int64_t pos();
+        int64_t size();
         void read(void* dataOut, const int64_t& count, int64_t* numRead);
         void write(const void* dataIn, const int64_t& count);
         ~StrFileImpl();
@@ -186,6 +191,12 @@ int64_t BinaryFile::pos()
     return m_impl->pos();
 }
 
+int64_t BinaryFile::size()
+{
+    if (m_curMode == NONE) throw CiftiException("file is not open, can't report size");
+    return m_impl->size();
+}
+
 void BinaryFile::write(const void* dataIn, const int64_t& count)
 {
     CiftiAssert(count >= 0);//not sure about allowing 0
@@ -394,7 +405,7 @@ void QFileImpl::write(const void* dataIn, const int64_t& count)
     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
+        writeret = m_file.write(((const char*)dataIn) + total, maxToWrite);//QFile probably also chokes on large writes
         if (writeret < 1) break;//0 or -1 means error or eof
         total += writeret;
     }
@@ -494,6 +505,14 @@ int64_t StrFileImpl::pos()
     return m_curPos;//we can avoid a call here also
 }
 
+int64_t StrFileImpl::size()
+{
+    struct stat mystat;
+    int result = fstat(fileno(m_file), &mystat);
+    if (result != 0) return -1;
+    return mystat.st_size;
+}
+
 void StrFileImpl::write(const void* dataIn, const int64_t& count)
 {
     if (m_file == NULL) throw CiftiException("write called on unopened StrFileImpl");//shouldn't happen


=====================================
src/Common/BinaryFile.h
=====================================
@@ -62,6 +62,7 @@ namespace cifti {
         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
+        int64_t size();//may return -1 if size cannot be determined efficiently
         class ImplInterface
         {
         protected:
@@ -72,6 +73,7 @@ namespace cifti {
             const AString& getFilename() const { return m_fileName; }
             virtual void seek(const int64_t& position) = 0;
             virtual int64_t pos() = 0;
+            virtual int64_t size() = 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();


=====================================
src/Common/CMakeLists.txt
=====================================
@@ -1,6 +1,4 @@
 
-PROJECT(Common)
-
 SET(HEADERS
 AString.h
 ByteSwapping.h


=====================================
src/Nifti/CMakeLists.txt
=====================================
@@ -1,6 +1,4 @@
 
-project (Nifti)
-
 SET(HEADERS
 NiftiHeader.h
 )


=====================================
src/Nifti/NiftiHeader.cxx
=====================================
@@ -37,6 +37,7 @@
 #include <cstring>
 #include <iostream>
 #include <limits>
+#include "stdint.h"
 
 using namespace std;
 using namespace boost;
@@ -219,7 +220,7 @@ vector<vector<float> > NiftiHeader::getSForm() const
             {
                 for (int j = 0; j < 3; ++j)
                 {
-                    rotmat[i][j] *= m_header.pixdim[i + 1];
+                    rotmat[i][j] *= m_header.pixdim[j + 1];
                 }
             }
             if (m_header.pixdim[0] < 0.0f)//left handed coordinate system, flip the kvec
@@ -239,13 +240,13 @@ vector<vector<float> > NiftiHeader::getSForm() const
             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;
+            cerr << "warning: found quaternion with length greater than 1 in nifti header, 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];
         }
     } else {//fall back to analyze and complain
-        cerr << "no sform or qform code found, using ANALYZE coordinates!" << endl;
+        cerr << "warning: 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];
@@ -265,11 +266,31 @@ vector<vector<float> > NiftiHeader::getSForm() const
         case NIFTI_UNITS_MM:
             break;
         default:
-            cerr << "unrecognized spatial unit in nifti header" << endl;
+            cerr << "warning: unrecognized spatial unit in nifti header" << endl;
     }
     return ret.getMatrix();
 }
 
+double NiftiHeader::getTimeStep() const
+{
+    int timeUnit = XYZT_TO_TIME(m_header.xyzt_units);
+    double ret = m_header.pixdim[4];
+    switch (timeUnit)
+    {
+        case NIFTI_UNITS_USEC:
+            ret /= 1000000.0;
+            break;
+        case NIFTI_UNITS_MSEC:
+            ret /= 1000.0;
+            break;
+        default:
+            cerr << AString_to_std_string("warning: non-time units code " + AString_number(timeUnit) + " used in nifti header, pretending units are seconds") << endl;
+        case NIFTI_UNITS_SEC:
+            break;
+    }
+    return ret;
+}
+
 AString NiftiHeader::toString() const
 {
     AString ret;
@@ -325,6 +346,16 @@ AString NiftiHeader::toString() const
         ret += "qoffset_y: " + AString_number(m_header.qoffset_y) + "\n";
         ret += "qoffset_z: " + AString_number(m_header.qoffset_z) + "\n";
     }
+    ret += "effective sform:\n";
+    vector<vector<float> > tempSform = getSForm();
+    for (int i = 0; i < 3; ++i)
+    {
+        for (int j = 0; j < 4; ++j)
+        {
+            ret += " " + AString_number(tempSform[i][j]);
+        }
+        ret += "\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";
@@ -377,6 +408,13 @@ void NiftiHeader::setIntent(const int32_t& code, const char name[16])
     for (; i < 16; ++i) m_header.intent_name[i] = '\0';
 }
 
+void NiftiHeader::setDescription(const char descrip[80])
+{
+    int i;//custom strncpy-like code to fill nulls to the end
+    for (i = 0; i < 80 && descrip[i] != '\0'; ++i) m_header.descrip[i] = descrip[i];
+    for (; i < 80; ++i) m_header.descrip[i] = '\0';
+}
+
 void NiftiHeader::setSForm(const vector<vector<float> >& sForm)
 {
     CiftiAssert(sForm.size() >= 3);//programmer error to pass badly sized matrix
@@ -386,7 +424,8 @@ void NiftiHeader::setSForm(const vector<vector<float> >& sForm)
         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
+    int timeUnit = XYZT_TO_TIME(m_header.xyzt_units);
+    m_header.xyzt_units = SPACE_TIME_TO_XYZT(NIFTI_UNITS_MM, timeUnit);//overwrite whatever spatial unit we read in
     for (int i = 0; i < 4; i++)
     {
         m_header.srow_x[i] = sForm[0][i];
@@ -411,9 +450,9 @@ void NiftiHeader::setSForm(const vector<vector<float> >& sForm)
         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];
+    rotmat[0][0] = ivec[0]; rotmat[1][0] = ivec[1]; rotmat[2][0] = ivec[2];
+    rotmat[0][1] = jvec[0]; rotmat[1][1] = jvec[1]; rotmat[2][1] = jvec[2];
+    rotmat[0][2] = kvec[0]; rotmat[1][2] = kvec[1]; rotmat[2][2] = kvec[2];
     float quat[4];
     if (!MathFunctions::matrixToQuatern(rotmat, quat))
     {
@@ -435,6 +474,13 @@ void NiftiHeader::setSForm(const vector<vector<float> >& sForm)
     }
 }
 
+void NiftiHeader::setTimeStep(const double& seconds)
+{
+    int spaceUnit = XYZT_TO_SPACE(m_header.xyzt_units);//save the current space units so we don't clobber it...
+    m_header.xyzt_units = SPACE_TIME_TO_XYZT(spaceUnit, NIFTI_UNITS_SEC);//overwrite the time part of the units with seconds
+    m_header.pixdim[4] = seconds;
+}
+
 void NiftiHeader::clearDataScaling()
 {
     m_header.scl_slope = 1.0;
@@ -447,6 +493,141 @@ void NiftiHeader::setDataScaling(const double& mult, const double& offset)
     m_header.scl_inter = offset;
 }
 
+namespace
+{
+    template<typename T>
+    struct Scaling
+    {
+        double mult, offset;
+        Scaling(const double& minval, const double& maxval)
+        {
+            std::numeric_limits<T> mylimits;
+            double mymin = mylimits.min();
+            if (!mylimits.is_integer) mymin = -mylimits.max();//again, c++11 can use lowest() instead of these lines
+            mult = (maxval - minval) / ((double)mylimits.max() - mymin);//multiplying is the first step of decoding (after byteswap), so start with the range
+            offset = minval - mymin * mult;//offset is added after multiplying the encoded value by mult
+        }
+    };
+}
+
+void NiftiHeader::setDataTypeAndScaleRange(const int16_t& type, const double& minval, const double& maxval)
+{
+    setDataType(type);
+    switch (type)
+    {
+        case NIFTI_TYPE_RGB24:
+            clearDataScaling();//RGB ignores scaling fields
+            break;
+        case DT_BINARY://currently not supported in read/write functions anyway
+            setDataScaling(maxval - minval, minval);//make the two possible decoded values equal to the min and max
+            break;
+        case NIFTI_TYPE_INT8:
+        {
+            Scaling<int8_t> myscale(minval, maxval);
+            setDataScaling(myscale.mult, myscale.offset);
+            break;
+        }
+        case NIFTI_TYPE_UINT8:
+        {
+            Scaling<uint8_t> myscale(minval, maxval);
+            setDataScaling(myscale.mult, myscale.offset);
+            break;
+        }
+        case NIFTI_TYPE_INT16:
+        {
+            Scaling<int16_t> myscale(minval, maxval);
+            setDataScaling(myscale.mult, myscale.offset);
+            break;
+        }
+        case NIFTI_TYPE_UINT16:
+        {
+            Scaling<uint16_t> myscale(minval, maxval);
+            setDataScaling(myscale.mult, myscale.offset);
+            break;
+        }
+        case NIFTI_TYPE_INT32:
+        {
+            Scaling<int32_t> myscale(minval, maxval);
+            setDataScaling(myscale.mult, myscale.offset);
+            break;
+        }
+        case NIFTI_TYPE_UINT32:
+        {
+            Scaling<uint32_t> myscale(minval, maxval);
+            setDataScaling(myscale.mult, myscale.offset);
+            break;
+        }
+        case NIFTI_TYPE_INT64:
+        {
+            Scaling<int64_t> myscale(minval, maxval);
+            setDataScaling(myscale.mult, myscale.offset);
+            break;
+        }
+        case NIFTI_TYPE_UINT64:
+        {
+            Scaling<uint64_t> myscale(minval, maxval);
+            setDataScaling(myscale.mult, myscale.offset);
+            break;
+        }
+        case NIFTI_TYPE_FLOAT32:
+        case NIFTI_TYPE_COMPLEX64:
+        {
+            Scaling<float> myscale(minval, maxval);
+            setDataScaling(myscale.mult, myscale.offset);
+            break;
+        }
+        case NIFTI_TYPE_FLOAT64:
+        case NIFTI_TYPE_COMPLEX128:
+        {
+            Scaling<double> myscale(minval, maxval);
+            setDataScaling(myscale.mult, myscale.offset);
+            break;
+        }
+        case NIFTI_TYPE_FLOAT128:
+        case NIFTI_TYPE_COMPLEX256:
+        {
+            Scaling<long double> myscale(minval, maxval);
+            setDataScaling(myscale.mult, myscale.offset);
+            break;
+        }
+        default:
+            CiftiAssert(0);
+            throw CiftiException("internal error, report what you did to the developers");
+    }
+}
+
+int NiftiHeader::getNumComponents() const
+{
+    switch (getDataType())
+    {
+        case NIFTI_TYPE_RGB24:
+            return 3;
+            break;
+        case NIFTI_TYPE_COMPLEX64:
+        case NIFTI_TYPE_COMPLEX128:
+        case NIFTI_TYPE_COMPLEX256:
+            return 2;
+            break;
+        case DT_BINARY:
+        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");
+    }
+}
+
 void NiftiHeader::read(BinaryFile& inFile)
 {
     nifti_1_header buffer1;
@@ -454,6 +635,7 @@ void NiftiHeader::read(BinaryFile& inFile)
     inFile.read(&buffer1, sizeof(nifti_1_header));
     int version = NIFTI2_VERSION(buffer1);
     bool swapped = false;
+    Quirks myquirks;
     try
     {
         if (version == 2)
@@ -465,14 +647,14 @@ void NiftiHeader::read(BinaryFile& inFile)
                 swapped = true;
                 swapHeaderBytes(buffer2);
             }
-            setupFrom(buffer2);
+            myquirks = setupFrom(buffer2, inFile.getFilename());
         } else if (version == 1) {
             if (NIFTI2_NEEDS_SWAP(buffer1))//yes, this works on nifti-1 also
             {
                 swapped = true;
                 swapHeaderBytes(buffer1);
             }
-            setupFrom(buffer1);
+            myquirks = setupFrom(buffer1, inFile.getFilename());
         } else {
             throw CiftiException(inFile.getFilename() + " is not a valid NIfTI file");
         }
@@ -480,58 +662,74 @@ void NiftiHeader::read(BinaryFile& inFile)
         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)
+    if (myquirks.no_extender)
+    {
+        int min_offset = 352;
+        if (version == 2) min_offset = 544;
+        cerr << AString_to_std_string("warning: in file '" + inFile.getFilename() + "', vox_offset is " + AString_number(m_header.vox_offset) +
+        ", nifti standard specifies that it should be at least " + AString_number(min_offset) + ", assuming malformed file with no extender") << endl;
+    } else {
+        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"
         {
-            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
+            int64_t extStart;
+            if (version == 1)
             {
-                tempExtension->m_bytes.resize(esize - 2 * sizeof(int32_t));
-                inFile.read(tempExtension->m_bytes.data(), esize - 2 * sizeof(int32_t));
+                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
             }
-            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)
+NiftiHeader::Quirks NiftiHeader::setupFrom(const nifti_1_header& header, const AString& filename)
 {
-    if (header.sizeof_hdr != sizeof(nifti_1_header)) throw CiftiException("incorrect sizeof_hdr");
+    Quirks ret;
+    if (header.sizeof_hdr != sizeof(nifti_1_header)) throw CiftiException("incorrect sizeof_hdr in file '" + filename + "'");
     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]");
+    if (strncmp(header.magic, magic, 4) != 0) throw CiftiException("incorrect magic in file '" + filename + "'");
+    if (header.dim[0] < 1 || header.dim[0] > 7) throw CiftiException("incorrect dim[0] in file '" + filename + "'");
     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.dim[i + 1] < 1) throw CiftiException("dim[" + AString_number(i + 1) + "] < 1 in file '" + filename + "'");
+    }
+    if (header.vox_offset < 352)
+    {
+        if (header.vox_offset < 348)
+        {
+            throw CiftiException("file '" + filename + "' has invalid vox_offset: " + AString_number(header.vox_offset));
+        }
+        ret.no_extender = true;
     }
-    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");
+    if (header.bitpix != numBits) cerr << AString_to_std_string("warning: datatype disagrees with bitpix in file '" + filename + "'") << endl;
     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
     {
@@ -574,24 +772,27 @@ void NiftiHeader::setupFrom(const nifti_1_header& header)
     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;
+    return ret;
 }
 
-void NiftiHeader::setupFrom(const nifti_2_header& header)
+NiftiHeader::Quirks NiftiHeader::setupFrom(const nifti_2_header& header, const AString& filename)
 {
-    if (header.sizeof_hdr != sizeof(nifti_2_header)) throw CiftiException("incorrect sizeof_hdr");
+    Quirks ret;
+    if (header.sizeof_hdr != sizeof(nifti_2_header)) throw CiftiException("incorrect sizeof_hdr in file '" + filename + "'");
     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.magic[i] != magic[i]) throw CiftiException("incorrect magic in file '" + filename + "'");
     }
-    if (header.dim[0] < 1 || header.dim[0] > 7) throw CiftiException("incorrect dim[0]");
+    if (header.dim[0] < 1 || header.dim[0] > 7) throw CiftiException("incorrect dim[0] in file '" + filename + "'");
     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.dim[i + 1] < 1) throw CiftiException("dim[" + AString_number(i + 1) + "] < 1 in file '" + filename + "'");
     }
-    if (header.vox_offset < 352) throw CiftiException("incorrect vox_offset");
-    if (header.bitpix != typeToNumBits(header.datatype)) throw CiftiException("datatype disagrees with bitpix");
+    if (header.vox_offset < 544) throw CiftiException("file '" + filename + "' has invalid vox_offset: " + AString_number(header.vox_offset));//haven't noticed any nifti-2 with bad vox_offset yet, and all cifti files have a big extension, so they have to have used it correctly
+    if (header.bitpix != typeToNumBits(header.datatype)) cerr << AString_to_std_string("warning: datatype disagrees with bitpix in file '" + filename + "'") << endl;
     memcpy(&m_header, &header, sizeof(nifti_2_header));
+    return ret;
 }
 
 int NiftiHeader::typeToNumBits(const int64_t& type)
@@ -631,7 +832,7 @@ int NiftiHeader::typeToNumBits(const int64_t& type)
             return 256;
             break;
         default:
-            throw CiftiException("incorrect datatype code");
+            throw CiftiException("incorrect nifti datatype code");
     }
 }
 
@@ -706,6 +907,12 @@ void NiftiHeader::swapHeaderBytes(nifti_2_header& header)
 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());
+    double junk1, junk2;
+    int16_t datatype = getDataType();
+    if (getDataScaling(junk1, junk2) && ((datatype & 0x70) > 0 || datatype >= 1536))
+    {//that hacky expression is to detect 16, 32, 64, 1536, 1792, and 2048
+        cerr << "warning: writing nifti file with scaling factor and floating point datatype" << endl;
+    }
     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)


=====================================
src/Nifti/NiftiHeader.h
=====================================
@@ -58,25 +58,36 @@ namespace cifti
         
         std::vector<int64_t> getDimensions() const;
         std::vector<std::vector<float> > getSForm() const;
+        double getTimeStep() const;//seconds
         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
+        const char* getDescription() const { return m_header.descrip; }//NOTE: 80 BYTES, MAY NOT HAVE A NULL TERMINATOR
         bool getDataScaling(double& mult, double& offset) const;//returns false if scaling not needed
+        int getNumComponents() const;
         AString toString() const;
         
         void setDimensions(const std::vector<int64_t>& dimsIn);
         void setSForm(const std::vector<std::vector<float> > &sForm);
+        void setTimeStep(const double& seconds);
         void setIntent(const int32_t& code, const char name[16]);
+        void setDescription(const char descrip[80]);
         void setDataType(const int16_t& type);
         void clearDataScaling();
         void setDataScaling(const double& mult, const double& offset);
+        void setDataTypeAndScaleRange(const int16_t& type, const double& minval, const double& maxval);
         ///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:
+        struct Quirks
+        {
+            bool no_extender;
+            Quirks() { no_extender = false; }
+        };
         nifti_2_header m_header;//storage for header values regardless of version
         int m_version;
         bool m_isSwapped;
@@ -84,8 +95,8 @@ namespace cifti
         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);
+        Quirks setupFrom(const nifti_1_header& header, const AString& filename);//error check provided header, and populate members from it
+        Quirks setupFrom(const nifti_2_header& header, const AString& filename);
         static int typeToNumBits(const int64_t& type);
         int64_t computeVoxOffset(const int& version) const;
     };


=====================================
src/NiftiIO.cxx
=====================================
@@ -42,6 +42,16 @@ void NiftiIO::openRead(const AString& filename)
         throw CiftiException("file uses the binary datatype, which is unsupported: " + filename);
     }
     m_dims = m_header.getDimensions();
+    int64_t filesize = m_file.size();//returns -1 if it can't efficiently determine size
+    int64_t elemCount = getNumComponents();
+    for (int i = 0; i < (int)m_dims.size(); ++i)
+    {
+        elemCount *= m_dims[i];
+    }
+    if (filesize >= 0 && filesize < m_header.getDataOffset() + numBytesPerElem() * elemCount)
+    {
+        throw CiftiException("nifti file is truncated: " + filename);
+    }
 }
 
 void NiftiIO::writeNew(const AString& filename, const NiftiHeader& header, const int& version, const bool& withRead, const bool& swapEndian)
@@ -69,33 +79,7 @@ void NiftiIO::close()
 
 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");
-    }
+    return m_header.getNumComponents();
 }
 
 int NiftiIO::numBytesPerElem()


=====================================
src/NiftiIO.h
=====================================
@@ -58,6 +58,8 @@ namespace cifti
         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
+        template<typename TO, typename FROM>
+        static TO clamp(const FROM& in);//deal with integer cast being undefined when converting from outside range
     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);
@@ -240,12 +242,12 @@ namespace cifti
             {
                 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
+                    out[i] = clamp<TO, long double>(floor(0.5l + 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]);
+                    out[i] = clamp<TO, double>(floor(0.5 + in[i]));
                 }
             }
         } else {
@@ -270,17 +272,17 @@ namespace cifti
         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
-        {
+        {//TODO: what about NaN?
             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
+                    out[i] = clamp<TO, long double>(floor(0.5l + ((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]);
+                    out[i] = clamp<TO, double>(floor(0.5 + in[i]));
                 }
             }
         } else {
@@ -300,6 +302,19 @@ namespace cifti
         if (m_header.isSwapped()) ByteSwapping::swapArray(out, count);
     }
     
+    template<typename TO, typename FROM>
+    TO NiftiIO::clamp(const FROM& in)
+    {
+        std::numeric_limits<TO> mylimits;
+        if (mylimits.max() < in) return mylimits.max();
+        if (mylimits.is_integer)//c++11 can use lowest() instead of this mess
+        {
+            if (mylimits.min() > in) return mylimits.min();
+        } else {
+            if (-mylimits.max() > in) return -mylimits.max();
+        }
+        return (TO)in;
+    }
 }
 
 #endif //__NIFTI_IO_H__



View it on GitLab: https://salsa.debian.org/med-team/ciftilib/commit/84a45634bfd805dfb20633034579bbe2d2110d14

-- 
View it on GitLab: https://salsa.debian.org/med-team/ciftilib/commit/84a45634bfd805dfb20633034579bbe2d2110d14
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20190130/615d9392/attachment-0001.html>


More information about the debian-med-commit mailing list