Skip to content
Commits on Source (8)
......@@ -19,6 +19,7 @@ compiler:
- gcc
env:
matrix:
- IGNORE_QT=false SHARED=true
- IGNORE_QT=false SHARED=false
- IGNORE_QT=true SHARED=true
......@@ -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
......@@ -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)
......
......@@ -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.
......
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:
......
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
-- Andreas Tille <tille@debian.org> Wed, 30 Jan 2019 10:15:35 +0100
ciftilib (1.5.1-3) unstable; urgency=medium
* Team upload
......
......@@ -4,13 +4,13 @@ Uploaders: Ghislain Antony Vaillant <ghisvail@gmail.com>
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
......
From: Ghislain Antony Vaillant <ghisvail@gmail.com>
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})
From: Ghislain Antony Vaillant <ghisvail@gmail.com>
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<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;
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<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
@@ -300,7 +300,7 @@ 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
Enable-parallel-execution-of-tests.patch
Fix-spelling-errors-reported-by-Lintian.patch
......@@ -25,3 +25,8 @@ override_dh_compress-indep:
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
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})
#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;
}
PROJECT(src)
SET(HEADERS
CiftiFile.h
......
PROJECT(Cifti)
SET(HEADERS
CiftiXML.h
CiftiVersion.h
......
......@@ -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;
......
......@@ -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
......
......@@ -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,7 +54,7 @@ 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();
......@@ -98,6 +102,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);
......
......@@ -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)
{
......
......@@ -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
......