Codebase list ciftilib / 84a4563
New upstream version 1.5.3 Andreas Tille 5 years ago
20 changed file(s) with 641 addition(s) and 144 deletion(s). Raw diff Collapse all Expand all
1818 - gcc
1919
2020 env:
21 - IGNORE_QT=false SHARED=true
22 - IGNORE_QT=false SHARED=false
23 - IGNORE_QT=true SHARED=true
24 - IGNORE_QT=true SHARED=false
21 matrix:
22 - IGNORE_QT=false SHARED=true
23 - IGNORE_QT=false SHARED=false
24 - IGNORE_QT=true SHARED=true
25 - IGNORE_QT=true SHARED=false
2526
2627 before_install:
2728 - mkdir ../build
2930
3031 script:
3132 - cmake -D BUILD_SHARED_LIBS:BOOL=$SHARED -D IGNORE_QT:BOOL=$IGNORE_QT ../CiftiLib
33 - export LD_LIBRARY_PATH=$(if [[ $CXX == "clang++" ]]; then echo -n '/usr/local/clang/lib'; fi)
3234 - make -j 4
35 - example/xmlinfo ../CiftiLib/example/data/ones.dscalar.nii
3336 - ctest
4040 SET(LIBS ${LIBS} Qt5::Core)
4141 #whatever that means
4242 ADD_DEFINITIONS(-DCIFTILIB_USE_QT)
43 SET(CIFTILIB_PKGCONFIG_REQUIRES_LINE "Requires: Qt5Core Qt5Xml")
43 SET(CIFTILIB_PKGCONFIG_REQUIRES_LINE "Requires: Qt5Core")
4444 SET(CIFTILIB_PKGCONFIG_DEFINE "-DCIFTILIB_USE_QT")
4545 ENDIF (Qt5Core_FOUND)
4646 ENDIF (QT_FOUND)
230230 # func(std::string) {}). This also make the inheritance and collaboration
231231 # diagrams that involve STL classes more complete and accurate.
232232
233 BUILTIN_STL_SUPPORT = NO
233 BUILTIN_STL_SUPPORT = YES
234234
235235 # If you use Microsoft's C++/CLI language, you should set this option to YES to
236236 # enable parsing support.
00 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.
11
2 It is currently set up to be compiled, along with a few simple examples, by cmake:
2 To build it, and example executables, in the recommended "out-of-source" method using cmake:
33
44 #start one level up from the source tree
5 #make build directory beside the source tree, enter it
5 #make a build directory beside the source tree, enter it
66 mkdir build; cd build
77
8 #you may want to set the cmake variable CMAKE_BUILD_TYPE to Release or Debug before building
8 #NOTE: you may want to set the cmake variable CMAKE_BUILD_TYPE to Release or Debug before building
99 #the default build behavior may be non-optimized and without debug symbols.
1010
1111 #run cmake to generate makefiles
12 cmake ../CiftiLib -D CMAKE_BUILD_TYPE=Release
12 cmake -D CMAKE_BUILD_TYPE=Release ../CiftiLib
1313 #OR
1414 cmake-gui ../CiftiLib
1515
1616 #build
1717 make
1818
19 #OPTIONAL: run tests, make docs
19 #OPTIONAL: run tests
2020 make test
21
22 #install library to location specified by cmake variable CMAKE_INSTALL_PREFIX
23 make install
24
25 #OPTIONAL: generate documentation (needs doxygen installed and on PATH)
2126 make doc
2227
23 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'.
28 The build results will be in subdirectories of the build directory, not in the source directory.
29
30 To use the installed library, use pkg-config to get the needed directories and libraries. For instance, in cmake:
31
32 find_package(PkgConfig)
33 pkg_check_modules(PC_CIFTI REQUIRED CiftiLib)
34
35 You can then use cmake variables PC_CIFTI_LIBRARIES, PC_CIFTI_INCLUDE_DIRS, and similar, see here for descriptions:
36
37 https://cmake.org/cmake/help/v3.0/module/FindPkgConfig.html
2438
2539 Troubleshooting:
2640
0
1 PROJECT(example)
20
31 ADD_EXECUTABLE(rewrite
42 rewrite.cxx)
119 xmlinfo.cxx)
1210
1311 TARGET_LINK_LIBRARIES(xmlinfo
12 Cifti
13 ${LIBS})
14
15 ADD_EXECUTABLE(datatype
16 datatype.cxx)
17
18 TARGET_LINK_LIBRARIES(datatype
1419 Cifti
1520 ${LIBS})
1621
2833 )
2934
3035 IF(QT_FOUND)
31 #QT4
36 #QT4 and QT5
3237 SET(cifti_be_md5s
3338 3ba20f6ef590735c1211991f8e0144e6
3439 e3a1639ef4b354752b0abb0613eedb73
4247 e096a1cf0713f7a36590445e9f6564b1
4348 32345267599b07083092b7dedfd8796c
4449 512e0359c64d69dde93d605f8797f3a2
50 )
51 SET(cifti_datatype_md5s
52 cca91b955b1134251d62764cb1ebf44c
53 6359af74ba6b51357aefab7de7a76097
54 10ee62309850e55936fa9f702df8b4d1
55 e4997bdd4b8202ff502a19173693c43f
56 870dae2d646cadeed1494f6271433499
4557 )
4658 ELSE(QT_FOUND)
4759 #xml++
5971 6fabac021e377efd35dede7198feefd4
6072 fe0cbb768e26aa12a0e03990f4f50a30
6173 )
74 SET(cifti_datatype_md5s
75 6db4a73e4e11a1ac0a5e7cbfb56eff40
76 f321156573ed8f165b208d84769bfd9a
77 794d60d9d397fe341e18313efeeac5ea
78 ea43725139bd3e152197fdf22c5e72e7
79 4dbb23ab2564ba8c9f242a3cb6036600
80 )
6281 ENDIF(QT_FOUND)
6382
6483 #ADD_TEST(timer ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver timer)
7594 ADD_TEST(rewrite-little-${testfile} rewrite ${CMAKE_SOURCE_DIR}/example/data/${testfile} little-${testfile} LITTLE)
7695 LIST(GET cifti_le_md5s ${index} goodsum)
7796 ADD_TEST(rewrite-little-md5-${testfile} ${CMAKE_COMMAND} -Dgood_sum=${goodsum} -Dcheck_file=little-${testfile} -P ${CMAKE_SOURCE_DIR}/cmake/scripts/testmd5.cmake)
97 SET_TESTS_PROPERTIES(rewrite-little-md5-${testfile} PROPERTIES DEPENDS rewrite-little-${testfile})
7898
7999 ADD_TEST(rewrite-big-${testfile} rewrite ${CMAKE_SOURCE_DIR}/example/data/${testfile} big-${testfile} BIG)
80100 LIST(GET cifti_be_md5s ${index} goodsum)
81101 ADD_TEST(rewrite-big-md5-${testfile} ${CMAKE_COMMAND} -Dgood_sum=${goodsum} -Dcheck_file=big-${testfile} -P ${CMAKE_SOURCE_DIR}/cmake/scripts/testmd5.cmake)
102 SET_TESTS_PROPERTIES(rewrite-big-md5-${testfile} PROPERTIES DEPENDS rewrite-big-${testfile})
103
104 ADD_TEST(datatype-${testfile} datatype ${CMAKE_SOURCE_DIR}/example/data/${testfile} datatype-${testfile})
105 LIST(GET cifti_datatype_md5s ${index} goodsum)
106 ADD_TEST(datatype-md5-${testfile} ${CMAKE_COMMAND} -Dgood_sum=${goodsum} -Dcheck_file=datatype-${testfile} -P ${CMAKE_SOURCE_DIR}/cmake/scripts/testmd5.cmake)
107 SET_TESTS_PROPERTIES(rewrite-big-md5-${testfile} PROPERTIES DEPENDS datatype-${testfile})
108
82109 ENDFOREACH(index RANGE ${loop_end})
0 #include "CiftiFile.h"
1
2 #include <iostream>
3 #include <vector>
4
5 using namespace std;
6 using namespace cifti;
7
8 /**\file datatype.cxx
9 This program reads a Cifti file from argv[1], and writes it out to argv[2] using 8-bit unsigned integer and data scaling.
10 It uses a single CiftiFile object to do this, for simplicity - to see how to do something similar with two objects,
11 which is more relevant for how you would do processing on cifti files, see rewrite.cxx.
12
13 \include datatype.cxx
14 */
15
16 int main(int argc, char** argv)
17 {
18 if (argc < 3)
19 {
20 cout << "usage: " << argv[0] << " <input cifti> <output cifti>" << endl;
21 cout << " rewrite the input cifti file to the output filename, using uint8 and data scaling." << endl;
22 return 1;
23 }
24 try
25 {
26 CiftiFile inputFile(argv[1]);//on-disk reading by default
27 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
28 inputFile.writeFile(argv[2]);//if this is the same filename as the input, CiftiFile actually detects this and reads the input into memory first
29 //otherwise, it will read and write one row at a time, using very little memory
30 //inputFile.setWritingDataTypeNoScaling(NIFTI_TYPE_FLOAT32);//this is how you would revert back to writing as float32 without rescaling
31 } catch (CiftiException& e) {
32 cerr << "Caught CiftiException: " + AString_to_std_string(e.whatString()) << endl;
33 return 1;
34 }
35 return 0;
36 }
0 PROJECT(src)
10
21 SET(HEADERS
32 CiftiFile.h
0
1 PROJECT(Cifti)
20
31 SET(HEADERS
42 CiftiXML.h
261261 map<StructureEnum::Enum, int>::const_iterator iter = m_surfUsed.find(structure);
262262 if (iter == m_surfUsed.end())
263263 {
264 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
264 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
265265 }
266266 CiftiAssertVectorIndex(m_modelsInfo, iter->second);
267267 return m_modelsInfo[iter->second].m_nodeIndices;
273273 map<StructureEnum::Enum, int>::const_iterator iter = m_surfUsed.find(structure);
274274 if (iter == m_surfUsed.end())
275275 {
276 throw CiftiException("getSurfaceMap called for nonexistant structure");//also throw, for consistency
276 throw CiftiException("getSurfaceMap called for nonexistent structure");//also throw, for consistency
277277 }
278278 CiftiAssertVectorIndex(m_modelsInfo, iter->second);
279279 const BrainModelPriv& myModel = m_modelsInfo[iter->second];
379379 map<StructureEnum::Enum, int>::const_iterator iter = m_volUsed.find(structure);
380380 if (iter == m_volUsed.end())
381381 {
382 throw CiftiException("getVolumeStructureMap called for nonexistant structure");//also throw, for consistency
382 throw CiftiException("getVolumeStructureMap called for nonexistent structure");//also throw, for consistency
383383 }
384384 CiftiAssertVectorIndex(m_modelsInfo, iter->second);
385385 const BrainModelPriv& myModel = m_modelsInfo[iter->second];
403403 map<StructureEnum::Enum, int>::const_iterator iter = m_volUsed.find(structure);
404404 if (iter == m_volUsed.end())
405405 {
406 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
406 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
407407 }
408408 CiftiAssertVectorIndex(m_modelsInfo, iter->second);
409409 return m_modelsInfo[iter->second].m_voxelIndicesIJK;
5353 CiftiXML m_xml;//because we need to parse it to set up the dimensions anyway
5454 public:
5555 CiftiOnDiskImpl(const AString& filename);//read-only
56 CiftiOnDiskImpl(const AString& filename, const CiftiXML& xml, const CiftiVersion& version, const bool& swapEndian);//make new empty file with read/write
56 CiftiOnDiskImpl(const AString& filename, const CiftiXML& xml, const CiftiVersion& version, const bool& swapEndian,
57 const int16_t& datatype, const bool& rescale, const double& minval, const double& maxval);//make new empty file with read/write
5758 void getRow(float* dataOut, const std::vector<int64_t>& indexSelect, const bool& tolerateShortRead) const;
5859 void getColumn(float* dataOut, const int64_t& index) const;
5960 const CiftiXML& getCiftiXML() const { return m_xml; }
6162 bool isSwapped() const { return m_nifti.getHeader().isSwapped(); }
6263 void setRow(const float* dataIn, const std::vector<int64_t>& indexSelect);
6364 void setColumn(const float* dataIn, const int64_t& index);
65 void close();
6466 };
6567
6668 class CiftiMemoryImpl : public CiftiFile::WriteImplInterface
134136 {
135137 }
136138
139 CiftiFile::CiftiFile()
140 {
141 m_endianPref = NATIVE;
142 setWritingDataTypeNoScaling();//default argument is float32
143 }
144
137145 CiftiFile::CiftiFile(const AString& fileName)
138146 {
139147 m_endianPref = NATIVE;
148 setWritingDataTypeNoScaling();//default argument is float32
140149 openFile(fileName);
141150 }
142151
143152 void CiftiFile::openFile(const AString& fileName)
144153 {
145 m_writingImpl.reset();
146 m_readingImpl.reset();//to make sure it closes everything first, even if the open throws
147 m_dims.clear();
154 close();//to make sure it closes everything first, even if the open throws
148155 boost::shared_ptr<CiftiOnDiskImpl> newRead(new CiftiOnDiskImpl(pathToAbsolute(fileName)));//this constructor opens existing file read-only
149156 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
150157 m_xml = newRead->getCiftiXML();
160167 m_endianPref = endian;
161168 }
162169
170 void CiftiFile::setWritingDataTypeNoScaling(const int16_t& type)
171 {
172 m_writingDataType = type;//could do some validation here
173 m_doWriteScaling = false;
174 m_minScalingVal = -1.0;//these scaling values should never be used, but don't leave them uninitialized
175 m_maxScalingVal = 1.0;
176 m_writingImpl.reset();//prevent writing to previous writing implementation, let the next set...() set up for writing
177 }
178
179 void CiftiFile::setWritingDataTypeAndScaling(const int16_t& type, const double& minval, const double& maxval)
180 {
181 m_writingDataType = type;//could do some validation here
182 m_doWriteScaling = true;
183 m_minScalingVal = minval;
184 m_maxScalingVal = maxval;
185 m_writingImpl.reset();//prevent writing to previous writing implementation, let the next set...() set up for writing
186 }
187
163188 void CiftiFile::writeFile(const AString& fileName, const CiftiVersion& writingVersion, const ENDIAN& endian)
164189 {
165190 if (m_readingImpl == NULL || m_dims.empty()) throw CiftiException("writeFile called on uninitialized CiftiFile");
166191 bool writeSwapped = shouldSwap(endian);
167 AString canonicalFilename = pathToCanonical(fileName);//NOTE: returns EMPTY STRING for nonexistant file
192 AString canonicalFilename = pathToCanonical(fileName);//NOTE: returns EMPTY STRING for nonexistent file
168193 const CiftiOnDiskImpl* testImpl = dynamic_cast<CiftiOnDiskImpl*>(m_readingImpl.get());
169194 bool collision = false, hadWriter = (m_writingImpl != NULL);
170195 if (testImpl != NULL && canonicalFilename != "" && pathToCanonical(testImpl->getFilename()) == canonicalFilename)
171 {//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
196 {//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
172197 if (m_onDiskVersion == writingVersion && (dontRewrite(endian) || writeSwapped == testImpl->isSwapped())) return;//don't need to copy to itself
173198 collision = true;//we need to copy to memory temporarily
174199 boost::shared_ptr<WriteImplInterface> tempMemory(new CiftiMemoryImpl(m_xml));//because tempRead is a ReadImpl, can't be used to copy to
176201 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
177202 m_writingImpl.reset();//and make it re-magic the writing implementation again if it tries to write again
178203 }
179 boost::shared_ptr<WriteImplInterface> tempWrite(new CiftiOnDiskImpl(pathToAbsolute(fileName), m_xml, writingVersion, writeSwapped));
204 boost::shared_ptr<WriteImplInterface> tempWrite(new CiftiOnDiskImpl(pathToAbsolute(fileName), m_xml, writingVersion, writeSwapped,
205 m_writingDataType, m_doWriteScaling, m_minScalingVal, m_maxScalingVal));
180206 copyImplData(m_readingImpl.get(), tempWrite.get(), m_dims);
181207 if (collision)//if we rewrote the file, we need the handle to the new file, and to dump the temporary in-memory version
182208 {
187213 m_writingImpl = tempWrite;//set the writer too
188214 }
189215 }
216 }
217
218 void CiftiFile::close()
219 {
220 if (m_writingImpl != NULL)
221 {
222 m_writingImpl->close();//only writing implementations should ever throw errors on close, and specifically only on-disk
223 }
224 m_writingImpl.reset();
225 m_readingImpl.reset();
226 m_dims.clear();
227 m_xml = CiftiXML();
228 m_writingFile = "";
229 m_onDiskVersion = CiftiVersion();//for completeness, it gets reset on open anyway
230 m_endianPref = NATIVE;//reset things to defaults
231 setWritingDataTypeNoScaling();//default argument is float32
190232 }
191233
192234 void CiftiFile::convertToInMemory()
230272
231273 void CiftiFile::setCiftiXML(const CiftiXML& xml, const bool useOldMetadata)
232274 {
275 if (xml.getNumberOfDimensions() == 0) throw CiftiException("setCiftiXML called with 0-dimensional CiftiXML");
276 vector<int64_t> xmlDims = xml.getDimensions();
277 for (size_t i = 0; i < xmlDims.size(); ++i)
278 {
279 if (xmlDims[i] < 1) throw CiftiException("cifti xml dimensions must be greater than zero");
280 }
233281 m_readingImpl.reset();//drop old implementation, as it is now invalid due to XML (and therefore matrix size) change
234282 m_writingImpl.reset();
235 if (xml.getNumberOfDimensions() == 0) throw CiftiException("setCiftiXML called with 0-dimensional CiftiXML");
236283 if (useOldMetadata)
237284 {
238285 MetaData newmd = m_xml.getFileMetaData();//make a copy
241288 } else {
242289 m_xml = xml;
243290 }
244 m_dims = m_xml.getDimensions();
245 for (size_t i = 0; i < m_dims.size(); ++i)
246 {
247 if (m_dims[i] < 1) throw CiftiException("cifti xml dimensions must be greater than zero");
248 }
291 m_dims = xmlDims;
249292 }
250293
251294 void CiftiFile::setRow(const float* dataIn, const vector<int64_t>& indexSelect)
299342 CiftiOnDiskImpl* testImpl = dynamic_cast<CiftiOnDiskImpl*>(m_readingImpl.get());
300343 if (testImpl != NULL)
301344 {
302 AString canonicalCurrent = pathToCanonical(testImpl->getFilename());//returns "" if nonexistant, if unlinked while open
345 AString canonicalCurrent = pathToCanonical(testImpl->getFilename());//returns "" if nonexistent, if unlinked while open
303346 if (canonicalCurrent != "" && canonicalCurrent == pathToCanonical(m_writingFile))//these were already absolute
304347 {
305348 convertToInMemory();//save existing data in memory before we clobber file
306349 }
307350 }
308351 }
309 m_writingImpl = boost::shared_ptr<CiftiOnDiskImpl>(new CiftiOnDiskImpl(m_writingFile, m_xml, m_onDiskVersion, shouldSwap(m_endianPref)));//this constructor makes new file for writing
352 m_writingImpl = boost::shared_ptr<CiftiOnDiskImpl>(new CiftiOnDiskImpl(m_writingFile, m_xml, m_onDiskVersion, shouldSwap(m_endianPref),
353 m_writingDataType, m_doWriteScaling, m_minScalingVal, m_maxScalingVal));//this constructor makes new file for writing
310354 if (m_readingImpl != NULL)
311355 {
312356 copyImplData(m_readingImpl.get(), m_writingImpl.get(), m_dims);
411455 if (m_xml.getNumberOfDimensions() + 4 != (int)dimCheck.size()) throw CiftiException("XML does not match number of nifti dimensions in file " + filename + "'");
412456 for (int i = 4; i < (int)dimCheck.size(); ++i)
413457 {
414 if (m_xml.getDimensionLength(i - 4) < 1)//CiftiXML will only let this happen with cifti-1
458 if (m_xml.getDimensionLength(i - 4) < 0)//CiftiXML will only let this happen with cifti-1
415459 {
416460 m_xml.getSeriesMap(i - 4).setLength(dimCheck[i]);//and only in a series map
417461 } else {
423467 }
424468 }
425469
426 CiftiOnDiskImpl::CiftiOnDiskImpl(const AString& filename, const CiftiXML& xml, const CiftiVersion& version, const bool& swapEndian)
470 namespace
471 {
472 void warnForBadExtension(const AString& filename, const CiftiXML& myXML)
473 {
474 char junk[16];
475 int32_t intent_code = myXML.getIntentInfo(CiftiVersion(), junk);//use default writing version to check file extension, older version is missing some intent codes
476 switch (intent_code)
477 {
478 case 3000://unknown
479 if (!AString_endsWith(filename, ".nii"))
480 {
481 cerr << "warning: cifti file of nonstandard mapping combination '" << AString_to_std_string(filename) << "' should be saved ending in .<something>.nii" << endl;
482 }
483 if (AString_endsWith(filename, ".dconn.nii") ||
484 AString_endsWith(filename, ".dtseries.nii") ||
485 AString_endsWith(filename, ".pconn.nii") ||
486 AString_endsWith(filename, ".ptseries.nii") ||
487 AString_endsWith(filename, ".dscalar.nii") ||
488 AString_endsWith(filename, ".dfan.nii") ||
489 AString_endsWith(filename, ".fiberTemp.nii") ||
490 AString_endsWith(filename, ".dlabel.nii") ||
491 AString_endsWith(filename, ".pscalar.nii") ||
492 AString_endsWith(filename, ".pdconn.nii") ||
493 AString_endsWith(filename, ".dpconn.nii") ||
494 AString_endsWith(filename, ".pconnseries.nii") ||
495 AString_endsWith(filename, ".pconnscalar.nii"))
496 {
497 cerr << "warning: cifti file of nonstandard mapping combination '" << AString_to_std_string(filename) << "' should NOT be saved using an already-used cifti extension, "
498 << "please choose a different, reasonable cifti extension ending in .<something>.nii" << endl;
499 }
500 break;
501 case 3001:
502 if (!AString_endsWith(filename, ".dconn.nii"))
503 {
504 cerr << "warning: dense by dense cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .dconn.nii" << endl;
505 }
506 break;
507 case 3002:
508 if (!AString_endsWith(filename, ".dtseries.nii"))
509 {
510 cerr << "warning: series by dense cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .dtseries.nii" << endl;
511 }
512 break;
513 case 3003:
514 if (!AString_endsWith(filename, ".pconn.nii"))
515 {
516 cerr << "warning: parcels by parcels cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .pconn.nii" << endl;
517 }
518 break;
519 case 3004:
520 if (!AString_endsWith(filename, ".ptseries.nii"))
521 {
522 cerr << "warning: series by parcels cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .ptseries.nii" << endl;
523 }
524 break;
525 case 3006://3005 unused in practice
526 if (!(AString_endsWith(filename, ".dscalar.nii") || AString_endsWith(filename, ".dfan.nii") || AString_endsWith(filename, ".fiberTEMP.nii")))
527 {//there are additional special extensions in the standard for this mapping combination (specializations of scalar maps)
528 //also include workbench's fiberTEMP special extension
529 cerr << "warning: scalars by dense cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .dscalar.nii" << endl;
530 }
531 break;
532 case 3007:
533 if (!AString_endsWith(filename, ".dlabel.nii"))
534 {
535 cerr << "warning: labels by dense cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .dlabel.nii" << endl;
536 }
537 break;
538 case 3008:
539 if (!AString_endsWith(filename, ".pscalar.nii"))
540 {
541 cerr << "warning: scalars by parcels cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .pscalar.nii" << endl;
542 }
543 break;
544 case 3009:
545 if (!AString_endsWith(filename, ".pdconn.nii"))
546 {
547 cerr << "warning: dense by parcels cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .pdconn.nii" << endl;
548 }
549 break;
550 case 3010:
551 if (!AString_endsWith(filename, ".dpconn.nii"))
552 {
553 cerr << "warning: parcels by dense cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .dpconn.nii" << endl;
554 }
555 break;
556 case 3011:
557 if (!AString_endsWith(filename, ".pconnseries.nii"))
558 {
559 cerr << "warning: parcels by parcels by series cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .pconnseries.nii" << endl;
560 }
561 break;
562 case 3012:
563 if (!AString_endsWith(filename, ".pconnscalar.nii"))
564 {
565 cerr << "warning: parcels by parcels by scalar cifti file '" << AString_to_std_string(filename) << "' should be saved ending in .pconnscalar.nii" << endl;
566 }
567 break;
568 default:
569 CiftiAssert(0);
570 throw CiftiException("internal error, tell the developers what you just tried to do");
571 }
572 }
573 }
574
575 CiftiOnDiskImpl::CiftiOnDiskImpl(const AString& filename, const CiftiXML& xml, const CiftiVersion& version, const bool& swapEndian,
576 const int16_t& datatype, const bool& rescale, const double& minval, const double& maxval)
427577 {//starts writing new file
578 warnForBadExtension(filename, xml);
428579 NiftiHeader outHeader;
429 outHeader.setDataType(NIFTI_TYPE_FLOAT32);//actually redundant currently, default is float32
580 if (rescale)
581 {
582 outHeader.setDataTypeAndScaleRange(datatype, minval, maxval);
583 } else {
584 outHeader.setDataType(datatype);
585 }
586 if (outHeader.getNumComponents() != 1)
587 {
588 throw CiftiException("cifti cannot be written with multi-component nifti datatypes (i.e., complex, RGB)");
589 }
430590 char intentName[16];
431591 int32_t intentCode = xml.getIntentInfo(version, intentName);
432592 outHeader.setIntent(intentCode, intentName);
454614 m_xml = xml;
455615 }
456616
617 void CiftiOnDiskImpl::close()
618 {
619 m_nifti.close();//lets this throw when there is a writing problem
620 }//don't bother resetting m_xml, this instance is about to be destroyed
621
457622 void CiftiOnDiskImpl::getRow(float* dataOut, const vector<int64_t>& indexSelect, const bool& tolerateShortRead) const
458623 {
459624 m_nifti.readData(dataOut, 5, indexSelect, tolerateShortRead);//5 means 4 reserved (space and time) plus the first cifti dimension
3131 #include "Common/CiftiException.h"
3232 #include "Common/MultiDimIterator.h"
3333 #include "Cifti/CiftiXML.h"
34 #include "Nifti/nifti1.h"
3435
3536 #include "boost/shared_ptr.hpp"
3637
5253 BIG
5354 };
5455
55 CiftiFile() { m_endianPref = NATIVE; }
56
56 CiftiFile();
57
5758 ///starts on-disk reading
5859 explicit CiftiFile(const AString &fileName);
5960
6667 ///does nothing if filename, version, and effective endianness match file currently open, otherwise writes complete file
6768 void writeFile(const AString& fileName, const CiftiVersion& writingVersion = CiftiVersion(), const ENDIAN& endian = ANY);
6869
70 ///closes the underlying file to flush it, so that exceptions can be thrown
71 void close();
72
6973 ///reads file into memory, closes file
7074 void convertToInMemory();
7175
96100
97101 ///for 2D only, if you don't want to pass a vector for indexing
98102 void setRow(const float* dataIn, const int64_t& index);
103
104 ///data type and scaling options - should be set before setRow, etc, to avoid rewriting of file
105 void setWritingDataTypeNoScaling(const int16_t& type = NIFTI_TYPE_FLOAT32);
106 void setWritingDataTypeAndScaling(const int16_t& type, const double& minval, const double& maxval);
99107
100108 //implementation details from here down
101109 class ReadImplInterface
112120 public:
113121 virtual void setRow(const float* dataIn, const std::vector<int64_t>& indexSelect) = 0;
114122 virtual void setColumn(const float* dataIn, const int64_t& index) = 0;
123 virtual void close() {}
115124 virtual ~WriteImplInterface();
116125 };
117126 private:
122131 CiftiXML m_xml;
123132 CiftiVersion m_onDiskVersion;
124133 ENDIAN m_endianPref;
134 bool m_doWriteScaling;
135 int16_t m_writingDataType;
136 double m_minScalingVal, m_maxScalingVal;
125137
126138 void verifyWriteImpl();
127139 static void copyImplData(const ReadImplInterface* from, WriteImplInterface* to, const std::vector<int64_t>& dims);
5757 {
5858 return mystr.mid(first, count);
5959 }
60 inline bool AString_endsWith(const AString& test, const AString& pattern)
61 {
62 return test.endsWith(pattern);
63 }
6064 template <typename T>
6165 AString AString_number(const T& num)
6266 {
9296 {//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
9397 return mystr.substr(first, count);
9498 }
99 inline bool AString_endsWith(const AString& test, const AString& pattern)
100 {
101 return test.substr(test.size() - pattern.size()) == pattern;
102 }
95103 template <typename T>
96104 AString AString_number(const T& num)
97105 {
3939 #include <QFile>
4040 #else
4141 #include "stdio.h"
42 #include "sys/stat.h"
43 #include "sys/types.h"
4244 #include "errno.h"
4345 #define BOOST_FILESYSTEM_VERSION 3
4446 #include "boost/filesystem.hpp"
6971 void close();
7072 void seek(const int64_t& position);
7173 int64_t pos();
74 int64_t size() { return -1; }
7275 void read(void* dataOut, const int64_t& count, int64_t* numRead);
7376 void write(const void* dataIn, const int64_t& count);
7477 ~ZFileImpl();
8790 void close();
8891 void seek(const int64_t& position);
8992 int64_t pos();
93 int64_t size() { return m_file.size(); }
9094 void read(void* dataOut, const int64_t& count, int64_t* numRead);
9195 void write(const void* dataIn, const int64_t& count);
9296 };
103107 void close();
104108 void seek(const int64_t& position);
105109 int64_t pos();
110 int64_t size();
106111 void read(void* dataOut, const int64_t& count, int64_t* numRead);
107112 void write(const void* dataIn, const int64_t& count);
108113 ~StrFileImpl();
185190 return m_impl->pos();
186191 }
187192
193 int64_t BinaryFile::size()
194 {
195 if (m_curMode == NONE) throw CiftiException("file is not open, can't report size");
196 return m_impl->size();
197 }
198
188199 void BinaryFile::write(const void* dataIn, const int64_t& count)
189200 {
190201 CiftiAssert(count >= 0);//not sure about allowing 0
393404 while (total < count)
394405 {
395406 int64_t maxToWrite = min(count - total, CHUNK_SIZE);
396 writeret = m_file.write((const char*)dataIn, maxToWrite);//QFile probably also chokes on large writes
407 writeret = m_file.write(((const char*)dataIn) + total, maxToWrite);//QFile probably also chokes on large writes
397408 if (writeret < 1) break;//0 or -1 means error or eof
398409 total += writeret;
399410 }
493504 return m_curPos;//we can avoid a call here also
494505 }
495506
507 int64_t StrFileImpl::size()
508 {
509 struct stat mystat;
510 int result = fstat(fileno(m_file), &mystat);
511 if (result != 0) return -1;
512 return mystat.st_size;
513 }
514
496515 void StrFileImpl::write(const void* dataIn, const int64_t& count)
497516 {
498517 if (m_file == NULL) throw CiftiException("write called on unopened StrFileImpl");//shouldn't happen
6161 int64_t pos();
6262 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)
6363 void write(const void* dataIn, const int64_t& count);//failure to complete write is always an exception
64 int64_t size();//may return -1 if size cannot be determined efficiently
6465 class ImplInterface
6566 {
6667 protected:
7172 const AString& getFilename() const { return m_fileName; }
7273 virtual void seek(const int64_t& position) = 0;
7374 virtual int64_t pos() = 0;
75 virtual int64_t size() = 0;
7476 virtual void read(void* dataOut, const int64_t& count, int64_t* numRead) = 0;
7577 virtual void write(const void* dataIn, const int64_t& count) = 0;
7678 virtual ~ImplInterface();
0
1 PROJECT(Common)
20
31 SET(HEADERS
42 AString.h
0
1 project (Nifti)
20
31 SET(HEADERS
42 NiftiHeader.h
3636 #include <cstring>
3737 #include <iostream>
3838 #include <limits>
39 #include "stdint.h"
3940
4041 using namespace std;
4142 using namespace boost;
218219 {
219220 for (int j = 0; j < 3; ++j)
220221 {
221 rotmat[i][j] *= m_header.pixdim[i + 1];
222 rotmat[i][j] *= m_header.pixdim[j + 1];
222223 }
223224 }
224225 if (m_header.pixdim[0] < 0.0f)//left handed coordinate system, flip the kvec
238239 ret[1][3] = m_header.qoffset_y;
239240 ret[2][3] = m_header.qoffset_z;
240241 } else {
241 cerr << "found quaternion with length greater than 1 in nifti header" << endl;
242 cerr << "warning: found quaternion with length greater than 1 in nifti header, using ANALYZE coordinates!" << endl;
242243 ret[0][0] = m_header.pixdim[1];
243244 ret[1][1] = m_header.pixdim[2];
244245 ret[2][2] = m_header.pixdim[3];
245246 }
246247 } else {//fall back to analyze and complain
247 cerr << "no sform or qform code found, using ANALYZE coordinates!" << endl;
248 cerr << "warning: no sform or qform code found, using ANALYZE coordinates!" << endl;
248249 ret[0][0] = m_header.pixdim[1];
249250 ret[1][1] = m_header.pixdim[2];
250251 ret[2][2] = m_header.pixdim[3];
264265 case NIFTI_UNITS_MM:
265266 break;
266267 default:
267 cerr << "unrecognized spatial unit in nifti header" << endl;
268 cerr << "warning: unrecognized spatial unit in nifti header" << endl;
268269 }
269270 return ret.getMatrix();
271 }
272
273 double NiftiHeader::getTimeStep() const
274 {
275 int timeUnit = XYZT_TO_TIME(m_header.xyzt_units);
276 double ret = m_header.pixdim[4];
277 switch (timeUnit)
278 {
279 case NIFTI_UNITS_USEC:
280 ret /= 1000000.0;
281 break;
282 case NIFTI_UNITS_MSEC:
283 ret /= 1000.0;
284 break;
285 default:
286 cerr << AString_to_std_string("warning: non-time units code " + AString_number(timeUnit) + " used in nifti header, pretending units are seconds") << endl;
287 case NIFTI_UNITS_SEC:
288 break;
289 }
290 return ret;
270291 }
271292
272293 AString NiftiHeader::toString() const
323344 ret += "qoffset_x: " + AString_number(m_header.qoffset_x) + "\n";
324345 ret += "qoffset_y: " + AString_number(m_header.qoffset_y) + "\n";
325346 ret += "qoffset_z: " + AString_number(m_header.qoffset_z) + "\n";
347 }
348 ret += "effective sform:\n";
349 vector<vector<float> > tempSform = getSForm();
350 for (int i = 0; i < 3; ++i)
351 {
352 for (int j = 0; j < 4; ++j)
353 {
354 ret += " " + AString_number(tempSform[i][j]);
355 }
356 ret += "\n";
326357 }
327358 ret += "xyzt_units: " + AString_number(m_header.xyzt_units) + "\n";
328359 ret += "intent_code: " + AString_number(m_header.intent_code) + "\n";
376407 for (; i < 16; ++i) m_header.intent_name[i] = '\0';
377408 }
378409
410 void NiftiHeader::setDescription(const char descrip[80])
411 {
412 int i;//custom strncpy-like code to fill nulls to the end
413 for (i = 0; i < 80 && descrip[i] != '\0'; ++i) m_header.descrip[i] = descrip[i];
414 for (; i < 80; ++i) m_header.descrip[i] = '\0';
415 }
416
379417 void NiftiHeader::setSForm(const vector<vector<float> >& sForm)
380418 {
381419 CiftiAssert(sForm.size() >= 3);//programmer error to pass badly sized matrix
385423 CiftiAssert(sForm[i].size() >= 4);//ditto
386424 if (sForm[i].size() < 4) throw CiftiException("internal error: setSForm matrix badly sized");
387425 }
388 m_header.xyzt_units = SPACE_TIME_TO_XYZT(NIFTI_UNITS_MM, NIFTI_UNITS_SEC);//overwrite whatever units we read in
426 int timeUnit = XYZT_TO_TIME(m_header.xyzt_units);
427 m_header.xyzt_units = SPACE_TIME_TO_XYZT(NIFTI_UNITS_MM, timeUnit);//overwrite whatever spatial unit we read in
389428 for (int i = 0; i < 4; i++)
390429 {
391430 m_header.srow_x[i] = sForm[0][i];
410449 kvec = -kvec;//because to nifti, "left handed" apparently means "up is down", not "left is right"
411450 }
412451 float rotmat[3][3];
413 rotmat[0][0] = ivec[0]; rotmat[1][0] = jvec[0]; rotmat[2][0] = kvec[0];
414 rotmat[0][1] = ivec[1]; rotmat[1][1] = jvec[1]; rotmat[2][1] = kvec[1];
415 rotmat[0][2] = ivec[2]; rotmat[1][2] = jvec[2]; rotmat[2][2] = kvec[2];
452 rotmat[0][0] = ivec[0]; rotmat[1][0] = ivec[1]; rotmat[2][0] = ivec[2];
453 rotmat[0][1] = jvec[0]; rotmat[1][1] = jvec[1]; rotmat[2][1] = jvec[2];
454 rotmat[0][2] = kvec[0]; rotmat[1][2] = kvec[1]; rotmat[2][2] = kvec[2];
416455 float quat[4];
417456 if (!MathFunctions::matrixToQuatern(rotmat, quat))
418457 {
434473 }
435474 }
436475
476 void NiftiHeader::setTimeStep(const double& seconds)
477 {
478 int spaceUnit = XYZT_TO_SPACE(m_header.xyzt_units);//save the current space units so we don't clobber it...
479 m_header.xyzt_units = SPACE_TIME_TO_XYZT(spaceUnit, NIFTI_UNITS_SEC);//overwrite the time part of the units with seconds
480 m_header.pixdim[4] = seconds;
481 }
482
437483 void NiftiHeader::clearDataScaling()
438484 {
439485 m_header.scl_slope = 1.0;
444490 {
445491 m_header.scl_slope = mult;
446492 m_header.scl_inter = offset;
493 }
494
495 namespace
496 {
497 template<typename T>
498 struct Scaling
499 {
500 double mult, offset;
501 Scaling(const double& minval, const double& maxval)
502 {
503 std::numeric_limits<T> mylimits;
504 double mymin = mylimits.min();
505 if (!mylimits.is_integer) mymin = -mylimits.max();//again, c++11 can use lowest() instead of these lines
506 mult = (maxval - minval) / ((double)mylimits.max() - mymin);//multiplying is the first step of decoding (after byteswap), so start with the range
507 offset = minval - mymin * mult;//offset is added after multiplying the encoded value by mult
508 }
509 };
510 }
511
512 void NiftiHeader::setDataTypeAndScaleRange(const int16_t& type, const double& minval, const double& maxval)
513 {
514 setDataType(type);
515 switch (type)
516 {
517 case NIFTI_TYPE_RGB24:
518 clearDataScaling();//RGB ignores scaling fields
519 break;
520 case DT_BINARY://currently not supported in read/write functions anyway
521 setDataScaling(maxval - minval, minval);//make the two possible decoded values equal to the min and max
522 break;
523 case NIFTI_TYPE_INT8:
524 {
525 Scaling<int8_t> myscale(minval, maxval);
526 setDataScaling(myscale.mult, myscale.offset);
527 break;
528 }
529 case NIFTI_TYPE_UINT8:
530 {
531 Scaling<uint8_t> myscale(minval, maxval);
532 setDataScaling(myscale.mult, myscale.offset);
533 break;
534 }
535 case NIFTI_TYPE_INT16:
536 {
537 Scaling<int16_t> myscale(minval, maxval);
538 setDataScaling(myscale.mult, myscale.offset);
539 break;
540 }
541 case NIFTI_TYPE_UINT16:
542 {
543 Scaling<uint16_t> myscale(minval, maxval);
544 setDataScaling(myscale.mult, myscale.offset);
545 break;
546 }
547 case NIFTI_TYPE_INT32:
548 {
549 Scaling<int32_t> myscale(minval, maxval);
550 setDataScaling(myscale.mult, myscale.offset);
551 break;
552 }
553 case NIFTI_TYPE_UINT32:
554 {
555 Scaling<uint32_t> myscale(minval, maxval);
556 setDataScaling(myscale.mult, myscale.offset);
557 break;
558 }
559 case NIFTI_TYPE_INT64:
560 {
561 Scaling<int64_t> myscale(minval, maxval);
562 setDataScaling(myscale.mult, myscale.offset);
563 break;
564 }
565 case NIFTI_TYPE_UINT64:
566 {
567 Scaling<uint64_t> myscale(minval, maxval);
568 setDataScaling(myscale.mult, myscale.offset);
569 break;
570 }
571 case NIFTI_TYPE_FLOAT32:
572 case NIFTI_TYPE_COMPLEX64:
573 {
574 Scaling<float> myscale(minval, maxval);
575 setDataScaling(myscale.mult, myscale.offset);
576 break;
577 }
578 case NIFTI_TYPE_FLOAT64:
579 case NIFTI_TYPE_COMPLEX128:
580 {
581 Scaling<double> myscale(minval, maxval);
582 setDataScaling(myscale.mult, myscale.offset);
583 break;
584 }
585 case NIFTI_TYPE_FLOAT128:
586 case NIFTI_TYPE_COMPLEX256:
587 {
588 Scaling<long double> myscale(minval, maxval);
589 setDataScaling(myscale.mult, myscale.offset);
590 break;
591 }
592 default:
593 CiftiAssert(0);
594 throw CiftiException("internal error, report what you did to the developers");
595 }
596 }
597
598 int NiftiHeader::getNumComponents() const
599 {
600 switch (getDataType())
601 {
602 case NIFTI_TYPE_RGB24:
603 return 3;
604 break;
605 case NIFTI_TYPE_COMPLEX64:
606 case NIFTI_TYPE_COMPLEX128:
607 case NIFTI_TYPE_COMPLEX256:
608 return 2;
609 break;
610 case DT_BINARY:
611 case NIFTI_TYPE_INT8:
612 case NIFTI_TYPE_UINT8:
613 case NIFTI_TYPE_INT16:
614 case NIFTI_TYPE_UINT16:
615 case NIFTI_TYPE_INT32:
616 case NIFTI_TYPE_UINT32:
617 case NIFTI_TYPE_FLOAT32:
618 case NIFTI_TYPE_INT64:
619 case NIFTI_TYPE_UINT64:
620 case NIFTI_TYPE_FLOAT64:
621 case NIFTI_TYPE_FLOAT128:
622 return 1;
623 break;
624 default:
625 CiftiAssert(0);
626 throw CiftiException("internal error, report what you did to the developers");
627 }
447628 }
448629
449630 void NiftiHeader::read(BinaryFile& inFile)
453634 inFile.read(&buffer1, sizeof(nifti_1_header));
454635 int version = NIFTI2_VERSION(buffer1);
455636 bool swapped = false;
637 Quirks myquirks;
456638 try
457639 {
458640 if (version == 2)
464646 swapped = true;
465647 swapHeaderBytes(buffer2);
466648 }
467 setupFrom(buffer2);
649 myquirks = setupFrom(buffer2, inFile.getFilename());
468650 } else if (version == 1) {
469651 if (NIFTI2_NEEDS_SWAP(buffer1))//yes, this works on nifti-1 also
470652 {
471653 swapped = true;
472654 swapHeaderBytes(buffer1);
473655 }
474 setupFrom(buffer1);
656 myquirks = setupFrom(buffer1, inFile.getFilename());
475657 } else {
476658 throw CiftiException(inFile.getFilename() + " is not a valid NIfTI file");
477659 }
479661 throw CiftiException("error reading NIfTI file " + inFile.getFilename() + ": " + e.whatString());
480662 }
481663 m_extensions.clear();
482 char extender[4];
483 inFile.read(extender, 4);
484 int extensions = 0;//if it has extensions in a format we don't know about, don't try to read them
485 if (version == 1 && extender[0] != 0) extensions = 1;//sadly, this is the only thing nifti-1 says about the extender bytes
486 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:
487 if (extensions == 1)//"extentions match those of NIfTI-1.1 when the extender bytes are 1 0 0 0"
488 {
489 int64_t extStart;
490 if (version == 1)
491 {
492 extStart = 352;
493 } else {
494 CiftiAssert(version == 2);
495 extStart = 544;
496 }
497 CiftiAssert(inFile.pos() == extStart);
498 while(extStart + 2 * sizeof(int32_t) <= (size_t)m_header.vox_offset)
499 {
500 int32_t esize, ecode;
501 inFile.read(&esize, sizeof(int32_t));
502 if (swapped) ByteSwapping::swap(esize);
503 inFile.read(&ecode, sizeof(int32_t));
504 if (swapped) ByteSwapping::swap(ecode);
505 if (esize < 8 || esize + extStart > m_header.vox_offset) break;
506 boost::shared_ptr<NiftiExtension> tempExtension(new NiftiExtension());
507 if ((size_t)esize > 2 * sizeof(int32_t))//don't try to read 0 bytes
664 if (myquirks.no_extender)
665 {
666 int min_offset = 352;
667 if (version == 2) min_offset = 544;
668 cerr << AString_to_std_string("warning: in file '" + inFile.getFilename() + "', vox_offset is " + AString_number(m_header.vox_offset) +
669 ", nifti standard specifies that it should be at least " + AString_number(min_offset) + ", assuming malformed file with no extender") << endl;
670 } else {
671 char extender[4];
672 inFile.read(extender, 4);
673 int extensions = 0;//if it has extensions in a format we don't know about, don't try to read them
674 if (version == 1 && extender[0] != 0) extensions = 1;//sadly, this is the only thing nifti-1 says about the extender bytes
675 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:
676 if (extensions == 1)//"extentions match those of NIfTI-1.1 when the extender bytes are 1 0 0 0"
677 {
678 int64_t extStart;
679 if (version == 1)
508680 {
509 tempExtension->m_bytes.resize(esize - 2 * sizeof(int32_t));
510 inFile.read(tempExtension->m_bytes.data(), esize - 2 * sizeof(int32_t));
681 extStart = 352;
682 } else {
683 CiftiAssert(version == 2);
684 extStart = 544;
511685 }
512 tempExtension->m_ecode = ecode;
513 m_extensions.push_back(tempExtension);
514 extStart += esize;//esize includes the two int32_ts
686 CiftiAssert(inFile.pos() == extStart);
687 while(extStart + 2 * sizeof(int32_t) <= (size_t)m_header.vox_offset)
688 {
689 int32_t esize, ecode;
690 inFile.read(&esize, sizeof(int32_t));
691 if (swapped) ByteSwapping::swap(esize);
692 inFile.read(&ecode, sizeof(int32_t));
693 if (swapped) ByteSwapping::swap(ecode);
694 if (esize < 8 || esize + extStart > m_header.vox_offset) break;
695 boost::shared_ptr<NiftiExtension> tempExtension(new NiftiExtension());
696 if ((size_t)esize > 2 * sizeof(int32_t))//don't try to read 0 bytes
697 {
698 tempExtension->m_bytes.resize(esize - 2 * sizeof(int32_t));
699 inFile.read(tempExtension->m_bytes.data(), esize - 2 * sizeof(int32_t));
700 }
701 tempExtension->m_ecode = ecode;
702 m_extensions.push_back(tempExtension);
703 extStart += esize;//esize includes the two int32_ts
704 }
515705 }
516706 }
517707 m_isSwapped = swapped;//now that we know there were no errors (because they throw), complete the internal state
518708 m_version = version;
519709 }
520710
521 void NiftiHeader::setupFrom(const nifti_1_header& header)
522 {
523 if (header.sizeof_hdr != sizeof(nifti_1_header)) throw CiftiException("incorrect sizeof_hdr");
711 NiftiHeader::Quirks NiftiHeader::setupFrom(const nifti_1_header& header, const AString& filename)
712 {
713 Quirks ret;
714 if (header.sizeof_hdr != sizeof(nifti_1_header)) throw CiftiException("incorrect sizeof_hdr in file '" + filename + "'");
524715 const char magic[] = "n+1\0";//only support single-file nifti
525 if (strncmp(header.magic, magic, 4) != 0) throw CiftiException("incorrect magic");
526 if (header.dim[0] < 1 || header.dim[0] > 7) throw CiftiException("incorrect dim[0]");
716 if (strncmp(header.magic, magic, 4) != 0) throw CiftiException("incorrect magic in file '" + filename + "'");
717 if (header.dim[0] < 1 || header.dim[0] > 7) throw CiftiException("incorrect dim[0] in file '" + filename + "'");
527718 for (int i = 0; i < header.dim[0]; ++i)
528719 {
529 if (header.dim[i + 1] < 1) throw CiftiException("dim[" + AString_number(i + 1) + "] < 1");
530 }
531 if (header.vox_offset < 352) throw CiftiException("incorrect vox_offset");
720 if (header.dim[i + 1] < 1) throw CiftiException("dim[" + AString_number(i + 1) + "] < 1 in file '" + filename + "'");
721 }
722 if (header.vox_offset < 352)
723 {
724 if (header.vox_offset < 348)
725 {
726 throw CiftiException("file '" + filename + "' has invalid vox_offset: " + AString_number(header.vox_offset));
727 }
728 ret.no_extender = true;
729 }
532730 int numBits = typeToNumBits(header.datatype);
533 if (header.bitpix != numBits) throw CiftiException("datatype disagrees with bitpix");
731 if (header.bitpix != numBits) cerr << AString_to_std_string("warning: datatype disagrees with bitpix in file '" + filename + "'") << endl;
534732 m_header.sizeof_hdr = header.sizeof_hdr;//copy in everything, so we don't have to fake anything to print the header as read
535733 for (int i = 0; i < 4; ++i)//mostly using nifti-2 field order to make it easier to find if things are missed
536734 {
573771 m_header.intent_code = header.intent_code;
574772 for (int i = 0; i < 16; ++i) m_header.intent_name[i] = header.intent_name[i];
575773 m_header.dim_info = header.dim_info;
576 }
577
578 void NiftiHeader::setupFrom(const nifti_2_header& header)
579 {
580 if (header.sizeof_hdr != sizeof(nifti_2_header)) throw CiftiException("incorrect sizeof_hdr");
774 return ret;
775 }
776
777 NiftiHeader::Quirks NiftiHeader::setupFrom(const nifti_2_header& header, const AString& filename)
778 {
779 Quirks ret;
780 if (header.sizeof_hdr != sizeof(nifti_2_header)) throw CiftiException("incorrect sizeof_hdr in file '" + filename + "'");
581781 const char magic[] = "n+2\0\r\n\032\n";//only support single-file nifti
582782 for (int i = 0; i < 8; ++i)
583783 {
584 if (header.magic[i] != magic[i]) throw CiftiException("incorrect magic");
585 }
586 if (header.dim[0] < 1 || header.dim[0] > 7) throw CiftiException("incorrect dim[0]");
784 if (header.magic[i] != magic[i]) throw CiftiException("incorrect magic in file '" + filename + "'");
785 }
786 if (header.dim[0] < 1 || header.dim[0] > 7) throw CiftiException("incorrect dim[0] in file '" + filename + "'");
587787 for (int i = 0; i < header.dim[0]; ++i)
588788 {
589 if (header.dim[i + 1] < 1) throw CiftiException("dim[" + AString_number(i + 1) + "] < 1");
590 }
591 if (header.vox_offset < 352) throw CiftiException("incorrect vox_offset");
592 if (header.bitpix != typeToNumBits(header.datatype)) throw CiftiException("datatype disagrees with bitpix");
789 if (header.dim[i + 1] < 1) throw CiftiException("dim[" + AString_number(i + 1) + "] < 1 in file '" + filename + "'");
790 }
791 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
792 if (header.bitpix != typeToNumBits(header.datatype)) cerr << AString_to_std_string("warning: datatype disagrees with bitpix in file '" + filename + "'") << endl;
593793 memcpy(&m_header, &header, sizeof(nifti_2_header));
794 return ret;
594795 }
595796
596797 int NiftiHeader::typeToNumBits(const int64_t& type)
630831 return 256;
631832 break;
632833 default:
633 throw CiftiException("incorrect datatype code");
834 throw CiftiException("incorrect nifti datatype code");
634835 }
635836 }
636837
705906 void NiftiHeader::write(BinaryFile& outFile, const int& version, const bool& swapEndian)
706907 {
707908 if (!canWriteVersion(version)) throw CiftiException("unable to write NIfTI version " + AString_number(version) + " for file " + outFile.getFilename());
909 double junk1, junk2;
910 int16_t datatype = getDataType();
911 if (getDataScaling(junk1, junk2) && ((datatype & 0x70) > 0 || datatype >= 1536))
912 {//that hacky expression is to detect 16, 32, 64, 1536, 1792, and 2048
913 cerr << "warning: writing nifti file with scaling factor and floating point datatype" << endl;
914 }
708915 const char padding[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
709916 int64_t voxOffset;
710917 if (version == 2)
5757
5858 std::vector<int64_t> getDimensions() const;
5959 std::vector<std::vector<float> > getSForm() const;
60 double getTimeStep() const;//seconds
6061 int64_t getDataOffset() const { return m_header.vox_offset; }
6162 int16_t getDataType() const { return m_header.datatype; }
6263 int32_t getIntentCode() const { return m_header.intent_code; }
6364 const char* getIntentName() const { return m_header.intent_name; }//NOTE: 16 BYTES, MAY NOT HAVE A NULL TERMINATOR
65 const char* getDescription() const { return m_header.descrip; }//NOTE: 80 BYTES, MAY NOT HAVE A NULL TERMINATOR
6466 bool getDataScaling(double& mult, double& offset) const;//returns false if scaling not needed
67 int getNumComponents() const;
6568 AString toString() const;
6669
6770 void setDimensions(const std::vector<int64_t>& dimsIn);
6871 void setSForm(const std::vector<std::vector<float> > &sForm);
72 void setTimeStep(const double& seconds);
6973 void setIntent(const int32_t& code, const char name[16]);
74 void setDescription(const char descrip[80]);
7075 void setDataType(const int16_t& type);
7176 void clearDataScaling();
7277 void setDataScaling(const double& mult, const double& offset);
78 void setDataTypeAndScaleRange(const int16_t& type, const double& minval, const double& maxval);
7379 ///get the FSL "scale" space
7480 std::vector<std::vector<float> > getFSLSpace() const;
7581
7682 bool operator==(const NiftiHeader& rhs) const;//for testing purposes
7783 bool operator!=(const NiftiHeader& rhs) const { return !((*this) == rhs); }
7884 private:
85 struct Quirks
86 {
87 bool no_extender;
88 Quirks() { no_extender = false; }
89 };
7990 nifti_2_header m_header;//storage for header values regardless of version
8091 int m_version;
8192 bool m_isSwapped;
8394 static void swapHeaderBytes(nifti_2_header &header);
8495 void prepareHeader(nifti_1_header& header) const;//transform internal state into ready to write header struct
8596 void prepareHeader(nifti_2_header& header) const;
86 void setupFrom(const nifti_1_header& header);//error check provided header, and populate members from it
87 void setupFrom(const nifti_2_header& header);
97 Quirks setupFrom(const nifti_1_header& header, const AString& filename);//error check provided header, and populate members from it
98 Quirks setupFrom(const nifti_2_header& header, const AString& filename);
8899 static int typeToNumBits(const int64_t& type);
89100 int64_t computeVoxOffset(const int& version) const;
90101 };
4141 throw CiftiException("file uses the binary datatype, which is unsupported: " + filename);
4242 }
4343 m_dims = m_header.getDimensions();
44 int64_t filesize = m_file.size();//returns -1 if it can't efficiently determine size
45 int64_t elemCount = getNumComponents();
46 for (int i = 0; i < (int)m_dims.size(); ++i)
47 {
48 elemCount *= m_dims[i];
49 }
50 if (filesize >= 0 && filesize < m_header.getDataOffset() + numBytesPerElem() * elemCount)
51 {
52 throw CiftiException("nifti file is truncated: " + filename);
53 }
4454 }
4555
4656 void NiftiIO::writeNew(const AString& filename, const NiftiHeader& header, const int& version, const bool& withRead, const bool& swapEndian)
6878
6979 int NiftiIO::getNumComponents() const
7080 {
71 switch (m_header.getDataType())
72 {
73 case NIFTI_TYPE_RGB24:
74 return 3;
75 break;
76 case NIFTI_TYPE_COMPLEX64:
77 case NIFTI_TYPE_COMPLEX128:
78 case NIFTI_TYPE_COMPLEX256:
79 return 2;
80 break;
81 case NIFTI_TYPE_INT8:
82 case NIFTI_TYPE_UINT8:
83 case NIFTI_TYPE_INT16:
84 case NIFTI_TYPE_UINT16:
85 case NIFTI_TYPE_INT32:
86 case NIFTI_TYPE_UINT32:
87 case NIFTI_TYPE_FLOAT32:
88 case NIFTI_TYPE_INT64:
89 case NIFTI_TYPE_UINT64:
90 case NIFTI_TYPE_FLOAT64:
91 case NIFTI_TYPE_FLOAT128:
92 return 1;
93 break;
94 default:
95 CiftiAssert(0);
96 throw CiftiException("internal error, report what you did to the developers");
97 }
81 return m_header.getNumComponents();
9882 }
9983
10084 int NiftiIO::numBytesPerElem()
5757 void convertRead(TO* out, FROM* in, const int64_t& count);//for reading from file
5858 template<typename TO, typename FROM>
5959 void convertWrite(TO* out, const FROM* in, const int64_t& count);//for writing to file
60 template<typename TO, typename FROM>
61 static TO clamp(const FROM& in);//deal with integer cast being undefined when converting from outside range
6062 public:
6163 void openRead(const AString& filename);
6264 void writeNew(const AString& filename, const NiftiHeader& header, const int& version = 1, const bool& withRead = false, const bool& swapEndian = false);
239241 {
240242 for (int64_t i = 0; i < count; ++i)
241243 {
242 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
244 out[i] = clamp<TO, long double>(floor(0.5l + offset + mult * (long double)in[i]));//we don't always need that much precision, but it will still be faster than hard drives
243245 }
244246 } else {
245247 for (int64_t i = 0; i < count; ++i)
246248 {
247 out[i] = (TO)floor(0.5 + in[i]);
249 out[i] = clamp<TO, double>(floor(0.5 + in[i]));
248250 }
249251 }
250252 } else {
269271 double mult, offset;
270272 bool doScale = m_header.getDataScaling(mult, offset);
271273 if (std::numeric_limits<TO>::is_integer)//do round to nearest when integer output type
272 {
274 {//TODO: what about NaN?
273275 if (doScale)
274276 {
275277 for (int64_t i = 0; i < count; ++i)
276278 {
277 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
279 out[i] = clamp<TO, long double>(floor(0.5l + ((long double)in[i] - offset) / mult));//we don't always need that much precision, but it will still be faster than hard drives
278280 }
279281 } else {
280282 for (int64_t i = 0; i < count; ++i)
281283 {
282 out[i] = (TO)floor(0.5 + in[i]);
284 out[i] = clamp<TO, double>(floor(0.5 + in[i]));
283285 }
284286 }
285287 } else {
299301 if (m_header.isSwapped()) ByteSwapping::swapArray(out, count);
300302 }
301303
304 template<typename TO, typename FROM>
305 TO NiftiIO::clamp(const FROM& in)
306 {
307 std::numeric_limits<TO> mylimits;
308 if (mylimits.max() < in) return mylimits.max();
309 if (mylimits.is_integer)//c++11 can use lowest() instead of this mess
310 {
311 if (mylimits.min() > in) return mylimits.min();
312 } else {
313 if (-mylimits.max() > in) return -mylimits.max();
314 }
315 return (TO)in;
316 }
302317 }
303318
304319 #endif //__NIFTI_IO_H__