/*LICENSE_START*/
/*
* Copyright (c) 2014, Washington University School of Medicine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "CiftiFile.h"
#include "Common/CiftiAssert.h"
#include "Common/MultiDimArray.h"
#include "NiftiIO.h"
#ifdef CIFTILIB_USE_QT
#include <QFileInfo>
#else
//use boost filesystem, because cross-platform filesystem support with POSIX is absurd
#define BOOST_FILESYSTEM_VERSION 3
#include "boost/filesystem.hpp"
#endif
#include <iostream>
using namespace std;
using namespace boost;
using namespace cifti;
//private implementation classes, helpers
namespace
{
class CiftiOnDiskImpl : public CiftiFile::WriteImplInterface
{
mutable NiftiIO m_nifti;//because file objects aren't stateless (current position), so reading "changes" them
CiftiXML m_xml;//because we need to parse it to set up the dimensions anyway
public:
CiftiOnDiskImpl(const AString& filename);//read-only
CiftiOnDiskImpl(const AString& filename, const CiftiXML& xml, const CiftiVersion& version, const bool& swapEndian,
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; }
AString getFilename() const { return m_nifti.getFilename(); }
bool isSwapped() const { return m_nifti.getHeader().isSwapped(); }
void setRow(const float* dataIn, const std::vector<int64_t>& indexSelect);
void setColumn(const float* dataIn, const int64_t& index);
void close();
};
class CiftiMemoryImpl : public CiftiFile::WriteImplInterface
{
MultiDimArray<float> m_array;
public:
CiftiMemoryImpl(const CiftiXML& xml);
void getRow(float* dataOut, const std::vector<int64_t>& indexSelect, const bool& tolerateShortRead) const;
void getColumn(float* dataOut, const int64_t& index) const;
bool isInMemory() const { return true; }
void setRow(const float* dataIn, const std::vector<int64_t>& indexSelect);
void setColumn(const float* dataIn, const int64_t& index);
};
bool shouldSwap(const CiftiFile::ENDIAN& endian)
{
if (ByteSwapping::isBigEndian())
{
if (endian == CiftiFile::LITTLE) return true;
} else {
if (endian == CiftiFile::BIG) return true;
}
return false;//default for all other enum values is to write native endian
}
bool dontRewrite(const CiftiFile::ENDIAN& endian)
{
return (endian == CiftiFile::ANY);
}
AString pathToAbsolute(const AString& mypath)
{
#ifdef CIFTILIB_USE_QT
return QFileInfo(mypath).absoluteFilePath();
#else
#ifdef CIFTILIB_BOOST_NO_FSV3
return filesystem::complete(AString_to_std_string(mypath)).file_string();
#else
return filesystem::absolute(AString_to_std_string(mypath)).native();
#endif
#endif
}
AString pathToCanonical(const AString& mypath)
{
#ifdef CIFTILIB_USE_QT
return QFileInfo(mypath).canonicalFilePath();
#else
#ifdef CIFTILIB_BOOST_NO_FSV3
return filesystem::complete(AString_to_std_string(mypath)).file_string();
#else
#ifdef CIFTILIB_BOOST_NO_CANONICAL
filesystem::path temp = AString_to_std_string(mypath);
if (!filesystem::exists(temp)) return "";
return absolute(temp).normalize().native();
#else
string temp = AString_to_std_string(mypath);
if (!filesystem::exists(temp)) return "";
return filesystem::canonical(temp).native();
#endif
#endif
#endif
}
}
CiftiFile::ReadImplInterface::~ReadImplInterface()
{
}
CiftiFile::WriteImplInterface::~WriteImplInterface()
{
}
CiftiFile::CiftiFile()
{
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)
{
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();
m_dims = m_xml.getDimensions();
m_onDiskVersion = m_xml.getParsedVersion();
}
void CiftiFile::setWritingFile(const AString& fileName, const CiftiVersion& writingVersion, const ENDIAN& endian)
{
m_writingFile = pathToAbsolute(fileName);//always resolve paths as soon as they enter CiftiFile, in case some clown changes directory before writing data
m_writingImpl.reset();//prevent writing to previous writing implementation, let the next set...() set up for writing
m_onDiskVersion = writingVersion;
m_endianPref = endian;
}
void CiftiFile::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 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 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
copyImplData(m_readingImpl.get(), tempMemory.get(), m_dims);
m_readingImpl = tempMemory;//we are about to make the old reading impl very unhappy, replace it so that if we get an error while writing, we hang onto the memory version
m_writingImpl.reset();//and make it re-magic the writing implementation again if it tries to write again
}
boost::shared_ptr<WriteImplInterface> tempWrite(new CiftiOnDiskImpl(pathToAbsolute(fileName), m_xml, writingVersion, writeSwapped,
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
{
m_onDiskVersion = writingVersion;//also record the current version number
m_readingImpl = tempWrite;//replace the temporary memory version
if (hadWriter)//if it was in read-write mode
{
m_writingImpl = tempWrite;//set the writer too
}
}
}
void CiftiFile::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;
if (m_readingImpl == NULL || m_dims.empty())//not set up yet
{
m_writingFile = "";//make sure it doesn't do on-disk when set...() is called
return;
}
boost::shared_ptr<WriteImplInterface> tempWrite(new CiftiMemoryImpl(m_xml));//if we get an error while reading, free the memory immediately, and don't leave m_readingImpl and m_writingImpl pointing to different things
copyImplData(m_readingImpl.get(), tempWrite.get(), m_dims);
m_writingImpl = tempWrite;
m_readingImpl = tempWrite;
}
bool CiftiFile::isInMemory() const
{
if (m_readingImpl == NULL)
{
return (m_writingFile == "");//return what it would be if verifyWriteImpl() was called
} else {
return m_readingImpl->isInMemory();
}
}
void CiftiFile::getRow(float* dataOut, const vector<int64_t>& indexSelect, const bool& tolerateShortRead) const
{
if (m_dims.empty()) throw CiftiException("getRow called on uninitialized CiftiFile");
if (m_readingImpl == NULL) return;//NOT an error because we are pretending to have a matrix already, while we are waiting for setRow to actually start writing the file
m_readingImpl->getRow(dataOut, indexSelect, tolerateShortRead);
}
void CiftiFile::getColumn(float* dataOut, const int64_t& index) const
{
if (m_dims.empty()) throw CiftiException("getColumn called on uninitialized CiftiFile");
if (m_dims.size() != 2) throw CiftiException("getColumn called on non-2D CiftiFile");
if (m_readingImpl == NULL) return;//NOT an error because we are pretending to have a matrix already, while we are waiting for setRow to actually start writing the file
m_readingImpl->getColumn(dataOut, index);
}
void CiftiFile::setCiftiXML(const CiftiXML& xml, const bool useOldMetadata)
{
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 (useOldMetadata)
{
MetaData newmd = m_xml.getFileMetaData();//make a copy
m_xml = xml;//because this will overwrite the metadata
m_xml.setFileMetaData(newmd);
} else {
m_xml = xml;
}
m_dims = xmlDims;
}
void CiftiFile::setRow(const float* dataIn, const vector<int64_t>& indexSelect)
{
verifyWriteImpl();
m_writingImpl->setRow(dataIn, indexSelect);
}
void CiftiFile::setColumn(const float* dataIn, const int64_t& index)
{
verifyWriteImpl();
if (m_dims.size() != 2) throw CiftiException("setColumn called on non-2D CiftiFile");
m_writingImpl->setColumn(dataIn, index);
}
//single-index functions
void CiftiFile::getRow(float* dataOut, const int64_t& index, const bool& tolerateShortRead) const
{
if (m_dims.empty()) throw CiftiException("getRow called on uninitialized CiftiFile");
if (m_dims.size() != 2) throw CiftiException("getRow with single index called on non-2D CiftiFile");
if (m_readingImpl == NULL) return;//NOT an error because we are pretending to have a matrix already, while we are waiting for setRow to actually start writing the file
vector<int64_t> tempvec(1, index);//could use a member if we need more speed
m_readingImpl->getRow(dataOut, tempvec, tolerateShortRead);
}
void CiftiFile::setRow(const float* dataIn, const int64_t& index)
{
verifyWriteImpl();
if (m_dims.size() != 2) throw CiftiException("setRow with single index called on non-2D CiftiFile");
vector<int64_t> tempvec(1, index);//could use a member if we need more speed
m_writingImpl->setRow(dataIn, tempvec);
}
//*///end single-index functions
void CiftiFile::verifyWriteImpl()
{//this is where the magic happens - we want to emulate being a simple in-memory file, but actually be reading/writing on-disk when possible
if (m_writingImpl != NULL) return;
CiftiAssert(!m_dims.empty());//if the xml hasn't been set, then we can't do anything meaningful
if (m_dims.empty()) throw CiftiException("setRow or setColumn attempted on uninitialized CiftiFile");
if (m_writingFile == "")
{
if (m_readingImpl != NULL)
{
convertToInMemory();
} else {
m_writingImpl = boost::shared_ptr<CiftiMemoryImpl>(new CiftiMemoryImpl(m_xml));
}
} else {//NOTE: m_onDiskVersion gets set in setWritingFile
if (m_readingImpl != NULL)
{
CiftiOnDiskImpl* testImpl = dynamic_cast<CiftiOnDiskImpl*>(m_readingImpl.get());
if (testImpl != NULL)
{
AString canonicalCurrent = pathToCanonical(testImpl->getFilename());//returns "" if 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),
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);
}
}
m_readingImpl = m_writingImpl;//read-only implementations are set up in specialized functions
}
void CiftiFile::copyImplData(const ReadImplInterface* from, WriteImplInterface* to, const vector<int64_t>& dims)
{
vector<int64_t> iterateDims(dims.begin() + 1, dims.end());
vector<float> scratchRow(dims[0]);
for (MultiDimIterator<int64_t> iter(iterateDims); !iter.atEnd(); ++iter)
{
from->getRow(scratchRow.data(), *iter, false);
to->setRow(scratchRow.data(), *iter);
}
}
CiftiMemoryImpl::CiftiMemoryImpl(const CiftiXML& xml)
{
CiftiAssert(xml.getNumberOfDimensions() != 0);
m_array.resize(xml.getDimensions());
}
void CiftiMemoryImpl::getRow(float* dataOut, const vector<int64_t>& indexSelect, const bool&) const
{
const float* ref = m_array.get(1, indexSelect);
int64_t rowSize = m_array.getDimensions()[0];//we don't accept 0-D CiftiXML, so this will always work
for (int64_t i = 0; i < rowSize; ++i)
{
dataOut[i] = ref[i];
}
}
void CiftiMemoryImpl::getColumn(float* dataOut, const int64_t& index) const
{
CiftiAssert(m_array.getDimensions().size() == 2);//otherwise, CiftiFile shouldn't have called this
const float* ref = m_array.get(2, vector<int64_t>());//empty vector is intentional, only 2 dimensions exist, so no more to select from
int64_t rowSize = m_array.getDimensions()[0];
int64_t colSize = m_array.getDimensions()[1];
CiftiAssert(index >= 0 && index < rowSize);//because we are doing the indexing math manually for speed
for (int64_t i = 0; i < colSize; ++i)
{
dataOut[i] = ref[index + rowSize * i];
}
}
void CiftiMemoryImpl::setRow(const float* dataIn, const vector<int64_t>& indexSelect)
{
float* ref = m_array.get(1, indexSelect);
int64_t rowSize = m_array.getDimensions()[0];//we don't accept 0-D CiftiXML, so this will always work
for (int64_t i = 0; i < rowSize; ++i)
{
ref[i] = dataIn[i];
}
}
void CiftiMemoryImpl::setColumn(const float* dataIn, const int64_t& index)
{
CiftiAssert(m_array.getDimensions().size() == 2);//otherwise, CiftiFile shouldn't have called this
float* ref = m_array.get(2, vector<int64_t>());//empty vector is intentional, only 2 dimensions exist, so no more to select from
int64_t rowSize = m_array.getDimensions()[0];
int64_t colSize = m_array.getDimensions()[1];
CiftiAssert(index >= 0 && index < rowSize);//because we are doing the indexing math manually for speed
for (int64_t i = 0; i < colSize; ++i)
{
ref[index + rowSize * i] = dataIn[i];
}
}
CiftiOnDiskImpl::CiftiOnDiskImpl(const AString& filename)
{//opens existing file for reading
m_nifti.openRead(filename);//read-only, so we don't need write permission to read a cifti file
if (m_nifti.getNumComponents() != 1) throw CiftiException("complex or rgb datatype found in file '" + filename + "', these are not supported in cifti");
const NiftiHeader& myHeader = m_nifti.getHeader();
int numExts = (int)myHeader.m_extensions.size(), whichExt = -1;
for (int i = 0; i < numExts; ++i)
{
if (myHeader.m_extensions[i]->m_ecode == NIFTI_ECODE_CIFTI)
{
whichExt = i;
break;
}
}
if (whichExt == -1) throw CiftiException("no cifti extension found in file '" + filename + "'");
m_xml.readXML(myHeader.m_extensions[whichExt]->m_bytes);
vector<int64_t> dimCheck = m_nifti.getDimensions();
if (dimCheck.size() < 5) throw CiftiException("invalid dimensions in cifti file '" + filename + "'");
for (int i = 0; i < 4; ++i)
{
if (dimCheck[i] != 1) throw CiftiException("non-singular dimension #" + AString_number(i + 1) + " in cifti file '" + filename + "'");
}
if (m_xml.getParsedVersion().hasReversedFirstDims())
{
while (dimCheck.size() < 6) dimCheck.push_back(1);//just in case
int64_t temp = dimCheck[4];//note: nifti dim[5] is the 5th dimension, index 4 in this vector
dimCheck[4] = dimCheck[5];
dimCheck[5] = temp;
m_nifti.overrideDimensions(dimCheck);
}
if (m_xml.getNumberOfDimensions() + 4 != (int)dimCheck.size()) throw CiftiException("XML does not match number of nifti dimensions in file " + filename + "'");
for (int i = 4; i < (int)dimCheck.size(); ++i)
{
if (m_xml.getDimensionLength(i - 4) < 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 {
if (m_xml.getDimensionLength(i - 4) != dimCheck[i])
{
throw CiftiException("xml and nifti header disagree on matrix dimensions");
}
}
}
}
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)
{
default:
cerr << "warning: unhandled cifti type in extension warning check, tell the developers what you just tried to do" << endl;
CiftiAssert(0);//yes, let it fall through to "unknown" in release so that it at least looks for .nii
//-fallthrough
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 of the form .<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;
}
}
}
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;
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);
boost::shared_ptr<NiftiExtension> outExtension(new NiftiExtension());
outExtension->m_ecode = NIFTI_ECODE_CIFTI;
outExtension->m_bytes = xml.writeXMLToVector(version);
outHeader.m_extensions.push_back(outExtension);
vector<int64_t> matrixDims = xml.getDimensions();
vector<int64_t> niftiDims(4, 1);//the reserved space and time dims
niftiDims.insert(niftiDims.end(), matrixDims.begin(), matrixDims.end());
if (version.hasReversedFirstDims())
{
vector<int64_t> headerDims = niftiDims;
while (headerDims.size() < 6) headerDims.push_back(1);//just in case
int64_t temp = headerDims[4];
headerDims[4] = headerDims[5];
headerDims[5] = temp;
outHeader.setDimensions(headerDims);//give the header the reversed dimensions
m_nifti.writeNew(filename, outHeader, 2, true, swapEndian);
m_nifti.overrideDimensions(niftiDims);//and then tell the nifti reader to use the correct dimensions
} else {
outHeader.setDimensions(niftiDims);
m_nifti.writeNew(filename, outHeader, 2, true, swapEndian);
}
m_xml = xml;
}
void CiftiOnDiskImpl::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
}
void CiftiOnDiskImpl::getColumn(float* dataOut, const int64_t& index) const
{
CiftiAssert(m_xml.getNumberOfDimensions() == 2);//otherwise this shouldn't be called
CiftiAssert(index >= 0 && index < m_xml.getDimensionLength(CiftiXML::ALONG_ROW));
vector<int64_t> indexSelect(2);
indexSelect[0] = index;
int64_t colLength = m_xml.getDimensionLength(CiftiXML::ALONG_COLUMN);
for (int64_t i = 0; i < colLength; ++i)//assume if they really want getColumn on disk, they don't want their pagecache obliterated, so read it 1 element at a time
{
indexSelect[1] = i;
m_nifti.readData(dataOut + i, 4, indexSelect);//4 means just the 4 reserved dimensions, so 1 element of the matrix
}
}
void CiftiOnDiskImpl::setRow(const float* dataIn, const vector<int64_t>& indexSelect)
{
m_nifti.writeData(dataIn, 5, indexSelect);
}
void CiftiOnDiskImpl::setColumn(const float* dataIn, const int64_t& index)
{
CiftiAssert(m_xml.getNumberOfDimensions() == 2);//otherwise this shouldn't be called
CiftiAssert(index >= 0 && index < m_xml.getDimensionLength(CiftiXML::ALONG_ROW));
vector<int64_t> indexSelect(2);
indexSelect[0] = index;
int64_t colLength = m_xml.getDimensionLength(CiftiXML::ALONG_COLUMN);
for (int64_t i = 0; i < colLength; ++i)//don't do RMW, so write it 1 element at a time
{
indexSelect[1] = i;
m_nifti.writeData(dataIn + i, 4, indexSelect);//4 means just the 4 reserved dimensions, so 1 element of the matrix
}
}