diff -Nru ciftilib-1.5.1/CMakeLists.txt ciftilib-1.5.3/CMakeLists.txt --- ciftilib-1.5.1/CMakeLists.txt 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/CMakeLists.txt 2018-08-22 22:45:51.000000000 +0000 @@ -41,7 +41,7 @@ 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) diff -Nru ciftilib-1.5.1/debian/changelog ciftilib-1.5.3/debian/changelog --- ciftilib-1.5.1/debian/changelog 2018-07-17 12:54:03.000000000 +0000 +++ ciftilib-1.5.3/debian/changelog 2019-04-04 13:32:13.000000000 +0000 @@ -1,14 +1,26 @@ -ciftilib (1.5.1-3ubuntu2) cosmic; urgency=medium +ciftilib (1.5.3-2ubuntu1) disco; urgency=medium - * No-change rebuild for boost soname change. + * Allow stderr output in autopkgtests for GCC warnings on armhf - -- Matthias Klose Tue, 17 Jul 2018 12:54:03 +0000 + -- Graham Inggs Thu, 04 Apr 2019 13:32:13 +0000 -ciftilib (1.5.1-3ubuntu1) cosmic; urgency=medium +ciftilib (1.5.3-2) unstable; urgency=medium - * Allow stderr output in autopkgtests for GCC warnings on armhf + * gbp.conf: drop pq settings + * Cherry-pick upstream fix for FTBFS on big endian. + Thanks to Adrian Bunk for reporting (Closes: #921555) + + -- Ghislain Antony Vaillant Tue, 02 Apr 2019 21:49:25 +0200 + +ciftilib (1.5.3-1) unstable; urgency=medium + + * Team upload. + * New upstream version + * debhelper 12 + * Standards-Version: 4.3.0 + * Do not run build time tests parallel - -- Graham Inggs Wed, 20 Jun 2018 06:01:36 +0000 + -- Andreas Tille Wed, 30 Jan 2019 10:15:35 +0100 ciftilib (1.5.1-3) unstable; urgency=medium diff -Nru ciftilib-1.5.1/debian/compat ciftilib-1.5.3/debian/compat --- ciftilib-1.5.1/debian/compat 2018-06-19 07:58:56.000000000 +0000 +++ ciftilib-1.5.3/debian/compat 2019-04-02 19:49:25.000000000 +0000 @@ -1 +1 @@ -11 +12 diff -Nru ciftilib-1.5.1/debian/control ciftilib-1.5.3/debian/control --- ciftilib-1.5.1/debian/control 2018-06-20 06:01:36.000000000 +0000 +++ ciftilib-1.5.3/debian/control 2019-04-04 13:32:13.000000000 +0000 @@ -5,13 +5,13 @@ Section: libs Priority: optional Build-Depends: cmake, - debhelper (>= 11~), + debhelper (>= 12~), libboost-dev, libboost-filesystem-dev, libxml++2.6-dev, zlib1g-dev Build-Depends-Indep: doxygen -Standards-Version: 4.1.4 +Standards-Version: 4.3.0 Vcs-Browser: https://salsa.debian.org/med-team/ciftilib Vcs-Git: https://salsa.debian.org/med-team/ciftilib.git Homepage: https://github.com/Washington-University/CiftiLib diff -Nru ciftilib-1.5.1/debian/gbp.conf ciftilib-1.5.3/debian/gbp.conf --- ciftilib-1.5.1/debian/gbp.conf 2018-06-19 07:58:56.000000000 +0000 +++ ciftilib-1.5.3/debian/gbp.conf 2019-04-02 19:49:25.000000000 +0000 @@ -4,6 +4,3 @@ upstream-tag = upstream/%(version)s debian-tag = debian/%(version)s pristine-tar = True - -[pq] -patch-numbers = False diff -Nru ciftilib-1.5.1/debian/patches/0001-force-endian-of-datatype-example-to-make-tests-pass-.patch ciftilib-1.5.3/debian/patches/0001-force-endian-of-datatype-example-to-make-tests-pass-.patch --- ciftilib-1.5.1/debian/patches/0001-force-endian-of-datatype-example-to-make-tests-pass-.patch 1970-01-01 00:00:00.000000000 +0000 +++ ciftilib-1.5.3/debian/patches/0001-force-endian-of-datatype-example-to-make-tests-pass-.patch 2019-04-02 19:49:25.000000000 +0000 @@ -0,0 +1,30 @@ +From: Tim Coalson +Date: Mon, 1 Apr 2019 16:56:12 -0500 +Subject: force endian of datatype example to make tests pass on bigendian + systems + +--- + example/datatype.cxx | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/example/datatype.cxx b/example/datatype.cxx +index a293856..1da380b 100644 +--- a/example/datatype.cxx ++++ b/example/datatype.cxx +@@ -19,14 +19,14 @@ int main(int argc, char** argv) + if (argc < 3) + { + cout << "usage: " << argv[0] << " " << endl; +- cout << " rewrite the input cifti file to the output filename, using uint8 and data scaling." << endl; ++ cout << " rewrite the input cifti file to the output filename, using uint8 and data scaling, little-endian." << 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 ++ inputFile.writeFile(argv[2], CiftiVersion(), CiftiFile::LITTLE);//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) { diff -Nru ciftilib-1.5.1/debian/patches/Enable-parallel-execution-of-tests.patch ciftilib-1.5.3/debian/patches/Enable-parallel-execution-of-tests.patch --- ciftilib-1.5.1/debian/patches/Enable-parallel-execution-of-tests.patch 2018-06-19 07:58:56.000000000 +0000 +++ ciftilib-1.5.3/debian/patches/Enable-parallel-execution-of-tests.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -From: Ghislain Antony Vaillant -Date: Tue, 8 Nov 2016 11:28:10 +0000 -Subject: Enable parallel execution of tests. - ---- - example/CMakeLists.txt | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt -index 71f4b0a..bc34779 100644 ---- a/example/CMakeLists.txt -+++ b/example/CMakeLists.txt -@@ -76,8 +76,10 @@ 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}) - ENDFOREACH(index RANGE ${loop_end}) diff -Nru ciftilib-1.5.1/debian/patches/Fix-spelling-errors-reported-by-Lintian.patch ciftilib-1.5.3/debian/patches/Fix-spelling-errors-reported-by-Lintian.patch --- ciftilib-1.5.1/debian/patches/Fix-spelling-errors-reported-by-Lintian.patch 2018-06-19 07:58:56.000000000 +0000 +++ ciftilib-1.5.3/debian/patches/Fix-spelling-errors-reported-by-Lintian.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,76 +0,0 @@ -From: Ghislain Antony Vaillant -Date: Tue, 8 Nov 2016 12:41:01 +0000 -Subject: Fix spelling errors reported by Lintian. - ---- - src/Cifti/CiftiBrainModelsMap.cxx | 8 ++++---- - src/CiftiFile.cxx | 6 +++--- - 2 files changed, 7 insertions(+), 7 deletions(-) - -diff --git a/src/Cifti/CiftiBrainModelsMap.cxx b/src/Cifti/CiftiBrainModelsMap.cxx -index 39e65c6..d8c1e54 100644 ---- a/src/Cifti/CiftiBrainModelsMap.cxx -+++ b/src/Cifti/CiftiBrainModelsMap.cxx -@@ -262,7 +262,7 @@ const vector& CiftiBrainModelsMap::getNodeList(const StructureEnum::Enu - map::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::getSurfaceMap(const - map::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::getVolumeStructureMa - map::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& CiftiBrainModelsMap::getVoxelList(const StructureEnum::En - map::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; -diff --git a/src/CiftiFile.cxx b/src/CiftiFile.cxx -index 99dd512..6e768d7 100644 ---- a/src/CiftiFile.cxx -+++ b/src/CiftiFile.cxx -@@ -165,11 +165,11 @@ void CiftiFile::writeFile(const AString& fileName, const CiftiVersion& writingVe - { - 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(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 tempMemory(new CiftiMemoryImpl(m_xml));//because tempRead is a ReadImpl, can't be used to copy to -@@ -300,7 +300,7 @@ void CiftiFile::verifyWriteImpl() - CiftiOnDiskImpl* testImpl = dynamic_cast(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 diff -Nru ciftilib-1.5.1/debian/patches/series ciftilib-1.5.3/debian/patches/series --- ciftilib-1.5.1/debian/patches/series 2018-06-19 07:58:56.000000000 +0000 +++ ciftilib-1.5.3/debian/patches/series 2019-04-02 19:49:25.000000000 +0000 @@ -1,2 +1 @@ -Enable-parallel-execution-of-tests.patch -Fix-spelling-errors-reported-by-Lintian.patch +0001-force-endian-of-datatype-example-to-make-tests-pass-.patch diff -Nru ciftilib-1.5.1/debian/rules ciftilib-1.5.3/debian/rules --- ciftilib-1.5.1/debian/rules 2018-06-19 07:58:56.000000000 +0000 +++ ciftilib-1.5.3/debian/rules 2019-04-02 19:49:25.000000000 +0000 @@ -25,3 +25,8 @@ override_dh_auto_test-indep: echo "Do not run test suite for arch indep builds" + +override_dh_auto_test-arch: +ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) + dh_auto_test --no-parallel +endif diff -Nru ciftilib-1.5.1/Doxyfile.in ciftilib-1.5.3/Doxyfile.in --- ciftilib-1.5.1/Doxyfile.in 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/Doxyfile.in 2018-08-22 22:45:51.000000000 +0000 @@ -231,7 +231,7 @@ # 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. diff -Nru ciftilib-1.5.1/example/CMakeLists.txt ciftilib-1.5.3/example/CMakeLists.txt --- ciftilib-1.5.1/example/CMakeLists.txt 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/example/CMakeLists.txt 2018-08-22 22:45:51.000000000 +0000 @@ -1,6 +1,4 @@ -PROJECT(example) - ADD_EXECUTABLE(rewrite rewrite.cxx) @@ -15,6 +13,13 @@ 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 @@ ) IF(QT_FOUND) - #QT4 + #QT4 and QT5 SET(cifti_be_md5s 3ba20f6ef590735c1211991f8e0144e6 e3a1639ef4b354752b0abb0613eedb73 @@ -44,6 +49,13 @@ 32345267599b07083092b7dedfd8796c 512e0359c64d69dde93d605f8797f3a2 ) + SET(cifti_datatype_md5s + cca91b955b1134251d62764cb1ebf44c + 6359af74ba6b51357aefab7de7a76097 + 10ee62309850e55936fa9f702df8b4d1 + e4997bdd4b8202ff502a19173693c43f + 870dae2d646cadeed1494f6271433499 + ) ELSE(QT_FOUND) #xml++ SET(cifti_be_md5s @@ -60,6 +72,13 @@ 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 @@ 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}) diff -Nru ciftilib-1.5.1/example/datatype.cxx ciftilib-1.5.3/example/datatype.cxx --- ciftilib-1.5.1/example/datatype.cxx 1970-01-01 00:00:00.000000000 +0000 +++ ciftilib-1.5.3/example/datatype.cxx 2018-08-22 22:45:51.000000000 +0000 @@ -0,0 +1,37 @@ +#include "CiftiFile.h" + +#include +#include + +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] << " " << 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; +} diff -Nru ciftilib-1.5.1/README ciftilib-1.5.3/README --- ciftilib-1.5.1/README 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/README 2018-08-22 22:45:51.000000000 +0000 @@ -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: diff -Nru ciftilib-1.5.1/src/Cifti/CiftiBrainModelsMap.cxx ciftilib-1.5.3/src/Cifti/CiftiBrainModelsMap.cxx --- ciftilib-1.5.1/src/Cifti/CiftiBrainModelsMap.cxx 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/Cifti/CiftiBrainModelsMap.cxx 2018-08-22 22:45:51.000000000 +0000 @@ -262,7 +262,7 @@ map::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 @@ map::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 @@ map::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 @@ map::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; diff -Nru ciftilib-1.5.1/src/Cifti/CMakeLists.txt ciftilib-1.5.3/src/Cifti/CMakeLists.txt --- ciftilib-1.5.1/src/Cifti/CMakeLists.txt 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/Cifti/CMakeLists.txt 2018-08-22 22:45:51.000000000 +0000 @@ -1,6 +1,4 @@ -PROJECT(Cifti) - SET(HEADERS CiftiXML.h CiftiVersion.h diff -Nru ciftilib-1.5.1/src/CiftiFile.cxx ciftilib-1.5.3/src/CiftiFile.cxx --- ciftilib-1.5.1/src/CiftiFile.cxx 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/CiftiFile.cxx 2018-08-22 22:45:51.000000000 +0000 @@ -54,7 +54,8 @@ 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& 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 @@ bool isSwapped() const { return m_nifti.getHeader().isSwapped(); } void setRow(const float* dataIn, const std::vector& indexSelect); void setColumn(const float* dataIn, const int64_t& index); + void close(); }; class CiftiMemoryImpl : public CiftiFile::WriteImplInterface @@ -135,17 +137,22 @@ { } +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 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 @@ 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(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 tempMemory(new CiftiMemoryImpl(m_xml));//because tempRead is a ReadImpl, can't be used to copy to @@ -177,7 +202,8 @@ 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 tempWrite(new CiftiOnDiskImpl(pathToAbsolute(fileName), m_xml, writingVersion, writeSwapped)); + boost::shared_ptr 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::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::setCiftiXML(const CiftiXML& xml, const bool useOldMetadata) { + if (xml.getNumberOfDimensions() == 0) throw CiftiException("setCiftiXML called with 0-dimensional CiftiXML"); + vector 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 @@ } 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& indexSelect) @@ -300,14 +343,15 @@ CiftiOnDiskImpl* testImpl = dynamic_cast(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(new CiftiOnDiskImpl(m_writingFile, m_xml, m_onDiskVersion, shouldSwap(m_endianPref)));//this constructor makes new file for writing + m_writingImpl = boost::shared_ptr(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 @@ 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, 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 ..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 ..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 @@ 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& indexSelect, const bool& tolerateShortRead) const { m_nifti.readData(dataOut, 5, indexSelect, tolerateShortRead);//5 means 4 reserved (space and time) plus the first cifti dimension diff -Nru ciftilib-1.5.1/src/CiftiFile.h ciftilib-1.5.3/src/CiftiFile.h --- ciftilib-1.5.1/src/CiftiFile.h 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/CiftiFile.h 2018-08-22 22:45:51.000000000 +0000 @@ -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 @@ BIG }; - CiftiFile() { m_endianPref = NATIVE; } - + CiftiFile(); + ///starts on-disk reading explicit CiftiFile(const AString &fileName); @@ -67,6 +68,9 @@ ///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 @@ ///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 @@ public: virtual void setRow(const float* dataIn, const std::vector& indexSelect) = 0; virtual void setColumn(const float* dataIn, const int64_t& index) = 0; + virtual void close() {} virtual ~WriteImplInterface(); }; private: @@ -123,6 +132,9 @@ 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& dims); diff -Nru ciftilib-1.5.1/src/CMakeLists.txt ciftilib-1.5.3/src/CMakeLists.txt --- ciftilib-1.5.1/src/CMakeLists.txt 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/CMakeLists.txt 2018-08-22 22:45:51.000000000 +0000 @@ -1,4 +1,3 @@ -PROJECT(src) SET(HEADERS CiftiFile.h diff -Nru ciftilib-1.5.1/src/Common/AString.h ciftilib-1.5.3/src/Common/AString.h --- ciftilib-1.5.1/src/Common/AString.h 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/Common/AString.h 2018-08-22 22:45:51.000000000 +0000 @@ -58,6 +58,10 @@ { return mystr.mid(first, count); } + inline bool AString_endsWith(const AString& test, const AString& pattern) + { + return test.endsWith(pattern); + } template AString AString_number(const T& num) { @@ -93,6 +97,10 @@ {//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 AString AString_number(const T& num) { diff -Nru ciftilib-1.5.1/src/Common/BinaryFile.cxx ciftilib-1.5.3/src/Common/BinaryFile.cxx --- ciftilib-1.5.1/src/Common/BinaryFile.cxx 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/Common/BinaryFile.cxx 2018-08-22 22:45:51.000000000 +0000 @@ -40,6 +40,8 @@ #include #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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 diff -Nru ciftilib-1.5.1/src/Common/BinaryFile.h ciftilib-1.5.3/src/Common/BinaryFile.h --- ciftilib-1.5.1/src/Common/BinaryFile.h 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/Common/BinaryFile.h 2018-08-22 22:45:51.000000000 +0000 @@ -62,6 +62,7 @@ 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 @@ 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(); diff -Nru ciftilib-1.5.1/src/Common/CMakeLists.txt ciftilib-1.5.3/src/Common/CMakeLists.txt --- ciftilib-1.5.1/src/Common/CMakeLists.txt 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/Common/CMakeLists.txt 2018-08-22 22:45:51.000000000 +0000 @@ -1,6 +1,4 @@ -PROJECT(Common) - SET(HEADERS AString.h ByteSwapping.h diff -Nru ciftilib-1.5.1/src/Nifti/CMakeLists.txt ciftilib-1.5.3/src/Nifti/CMakeLists.txt --- ciftilib-1.5.1/src/Nifti/CMakeLists.txt 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/Nifti/CMakeLists.txt 2018-08-22 22:45:51.000000000 +0000 @@ -1,6 +1,4 @@ -project (Nifti) - SET(HEADERS NiftiHeader.h ) diff -Nru ciftilib-1.5.1/src/Nifti/NiftiHeader.cxx ciftilib-1.5.3/src/Nifti/NiftiHeader.cxx --- ciftilib-1.5.1/src/Nifti/NiftiHeader.cxx 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/Nifti/NiftiHeader.cxx 2018-08-22 22:45:51.000000000 +0000 @@ -37,6 +37,7 @@ #include #include #include +#include "stdint.h" using namespace std; using namespace boost; @@ -219,7 +220,7 @@ { 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 @@ 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 @@ 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 @@ 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 > 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 @@ 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 >& sForm) { CiftiAssert(sForm.size() >= 3);//programmer error to pass badly sized matrix @@ -386,7 +424,8 @@ 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 @@ 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::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 @@ m_header.scl_inter = offset; } +namespace +{ + template + struct Scaling + { + double mult, offset; + Scaling(const double& minval, const double& maxval) + { + std::numeric_limits 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 myscale(minval, maxval); + setDataScaling(myscale.mult, myscale.offset); + break; + } + case NIFTI_TYPE_UINT8: + { + Scaling myscale(minval, maxval); + setDataScaling(myscale.mult, myscale.offset); + break; + } + case NIFTI_TYPE_INT16: + { + Scaling myscale(minval, maxval); + setDataScaling(myscale.mult, myscale.offset); + break; + } + case NIFTI_TYPE_UINT16: + { + Scaling myscale(minval, maxval); + setDataScaling(myscale.mult, myscale.offset); + break; + } + case NIFTI_TYPE_INT32: + { + Scaling myscale(minval, maxval); + setDataScaling(myscale.mult, myscale.offset); + break; + } + case NIFTI_TYPE_UINT32: + { + Scaling myscale(minval, maxval); + setDataScaling(myscale.mult, myscale.offset); + break; + } + case NIFTI_TYPE_INT64: + { + Scaling myscale(minval, maxval); + setDataScaling(myscale.mult, myscale.offset); + break; + } + case NIFTI_TYPE_UINT64: + { + Scaling myscale(minval, maxval); + setDataScaling(myscale.mult, myscale.offset); + break; + } + case NIFTI_TYPE_FLOAT32: + case NIFTI_TYPE_COMPLEX64: + { + Scaling myscale(minval, maxval); + setDataScaling(myscale.mult, myscale.offset); + break; + } + case NIFTI_TYPE_FLOAT64: + case NIFTI_TYPE_COMPLEX128: + { + Scaling myscale(minval, maxval); + setDataScaling(myscale.mult, myscale.offset); + break; + } + case NIFTI_TYPE_FLOAT128: + case NIFTI_TYPE_COMPLEX256: + { + Scaling 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 @@ 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 @@ 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 @@ 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" + if (myquirks.no_extender) { - 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) + 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 tempExtension(new NiftiExtension()); - if ((size_t)esize > 2 * sizeof(int32_t))//don't try to read 0 bytes + 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) { - tempExtension->m_bytes.resize(esize - 2 * sizeof(int32_t)); - inFile.read(tempExtension->m_bytes.data(), esize - 2 * sizeof(int32_t)); + 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 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 @@ 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 @@ return 256; break; default: - throw CiftiException("incorrect datatype code"); + throw CiftiException("incorrect nifti datatype code"); } } @@ -706,6 +907,12 @@ 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) diff -Nru ciftilib-1.5.1/src/Nifti/NiftiHeader.h ciftilib-1.5.3/src/Nifti/NiftiHeader.h --- ciftilib-1.5.1/src/Nifti/NiftiHeader.h 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/Nifti/NiftiHeader.h 2018-08-22 22:45:51.000000000 +0000 @@ -58,25 +58,36 @@ std::vector getDimensions() const; std::vector > 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& dimsIn); void setSForm(const std::vector > &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 > 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 @@ 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; }; diff -Nru ciftilib-1.5.1/src/NiftiIO.cxx ciftilib-1.5.3/src/NiftiIO.cxx --- ciftilib-1.5.1/src/NiftiIO.cxx 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/NiftiIO.cxx 2018-08-22 22:45:51.000000000 +0000 @@ -42,6 +42,16 @@ 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 @@ 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() diff -Nru ciftilib-1.5.1/src/NiftiIO.h ciftilib-1.5.3/src/NiftiIO.h --- ciftilib-1.5.1/src/NiftiIO.h 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/src/NiftiIO.h 2018-08-22 22:45:51.000000000 +0000 @@ -58,6 +58,8 @@ void convertRead(TO* out, FROM* in, const int64_t& count);//for reading from file template void convertWrite(TO* out, const FROM* in, const int64_t& count);//for writing to file + template + 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 @@ { 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(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(floor(0.5 + in[i])); } } } else { @@ -270,17 +272,17 @@ double mult, offset; bool doScale = m_header.getDataScaling(mult, offset); if (std::numeric_limits::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(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(floor(0.5 + in[i])); } } } else { @@ -300,6 +302,19 @@ if (m_header.isSwapped()) ByteSwapping::swapArray(out, count); } + template + TO NiftiIO::clamp(const FROM& in) + { + std::numeric_limits 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__ diff -Nru ciftilib-1.5.1/.travis.yml ciftilib-1.5.3/.travis.yml --- ciftilib-1.5.1/.travis.yml 2016-08-23 04:17:32.000000000 +0000 +++ ciftilib-1.5.3/.travis.yml 2018-08-22 22:45:51.000000000 +0000 @@ -19,10 +19,11 @@ - 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 @@ 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