/*========================================================================= * * Copyright NumFOCUS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ #include "itksys/SystemTools.hxx" #include "itkIPLCommonImageIO.h" #include "itkByteSwapper.h" #include "itkSpatialOrientationAdapter.h" #include "itkDirectory.h" #include "itkMetaDataObject.h" #include #include #include #include #include #include #include #include #include // From uiig library "The University of Iowa Imaging Group-UIIG" namespace itk { // Default constructor IPLCommonImageIO::IPLCommonImageIO() { m_SystemByteOrder = ByteSwapper::SystemIsBigEndian() ? IOByteOrderEnum::BigEndian : IOByteOrderEnum::LittleEndian; m_ImageHeader = nullptr; m_FilenameList = new IPLFileNameList; this->SetComponentType(IOComponentEnum::SHORT); } IPLCommonImageIO::~IPLCommonImageIO() { delete m_ImageHeader; delete m_FilenameList; } void IPLCommonImageIO::PrintSelf(std::ostream & os, Indent indent) const { Superclass::PrintSelf(os, indent); } bool IPLCommonImageIO::CanWriteFile(const char *) { return false; } unsigned int IPLCommonImageIO::GetComponentSize() const { return sizeof(short); } void IPLCommonImageIO::Read(void * buffer) { auto * img_buffer = (short *)buffer; auto it = m_FilenameList->begin(); auto itend = m_FilenameList->end(); for (; it != itend; ++it) { std::string curfilename = (*it)->GetImageFileName(); std::ifstream f; this->OpenFileForReading(f, curfilename); f.seekg((*it)->GetSliceOffset(), std::ios::beg); if (!this->ReadBufferAsBinary(f, img_buffer, m_FilenameList->GetXDim() * m_FilenameList->GetYDim() * sizeof(short))) { f.close(); RAISE_EXCEPTION(); } f.close(); // ByteSwapper::SwapRangeFromSystemToBigEndian is set up based on // the FILE endian-ness, not as the name would lead you to believe. // So, on LittleEndian systems, SwapFromSystemToBigEndian will swap. // On BigEndian systems, SwapFromSystemToBigEndian will do nothing. itk::ByteSwapper::SwapRangeFromSystemToBigEndian(img_buffer, m_FilenameList->GetXDim() * m_FilenameList->GetYDim()); img_buffer += m_FilenameList->GetXDim() * m_FilenameList->GetYDim(); } } GEImageHeader * IPLCommonImageIO::ReadHeader(const char *) { // // must be redefined in a child class // return nullptr; } bool IPLCommonImageIO::CanReadFile(const char *) { // // must be redefined in child class or you'll never read anything ;-) // return false; } void IPLCommonImageIO::ReadImageInformation() { std::string FileNameToRead = this->GetFileName(); // // GE images are stored in separate files per slice. // char imagePath[IOCommon::ITK_MAXPATHLEN+1]; // TODO -- use std::string instead of C strings char imageMask[IOCommon::ITK_MAXPATHLEN + 1]; char imagePath[IOCommon::ITK_MAXPATHLEN + 1]; std::string _imagePath = itksys::SystemTools::CollapseFullPath(FileNameToRead.c_str()); FileNameToRead = _imagePath; this->m_ImageHeader = this->ReadHeader(FileNameToRead.c_str()); // // if anything fails in the header read, just let // exceptions propagate up. bool isCT = false; std::string modality = m_ImageHeader->modality; if (modality == "CT") { isCT = true; } AddElementToList(m_ImageHeader->filename, m_ImageHeader->sliceLocation, m_ImageHeader->offset, m_ImageHeader->imageXsize, m_ImageHeader->imageYsize, m_ImageHeader->imageXres, m_ImageHeader->imageYres, m_ImageHeader->seriesNumber, (isCT) ? m_ImageHeader->examNumber : m_ImageHeader->echoNumber); // If CT use examNumber, otherwise use echoNumber. // Add header info to metadictionary itk::MetaDataDictionary & thisDic = this->GetMetaDataDictionary(); std::string classname(this->GetNameOfClass()); itk::EncapsulateMetaData(thisDic, ITK_InputFilterName, classname); itk::EncapsulateMetaData(thisDic, ITK_OnDiskStorageTypeName, std::string("SHORT")); itk::EncapsulateMetaData(thisDic, ITK_OnDiskBitPerPixel, static_cast(16)); // // has to be set before setting dir cosines, // otherwise the vector doesn't get allocated this->SetNumberOfDimensions(3); itk::EncapsulateMetaData(thisDic, ITK_PatientID, std::string(m_ImageHeader->patientId)); itk::EncapsulateMetaData(thisDic, ITK_ExperimentDate, std::string(m_ImageHeader->date)); if (_imagePath.empty()) { RAISE_EXCEPTION(); } strncpy(imagePath, _imagePath.c_str(), sizeof(imagePath)); imagePath[IOCommon::ITK_MAXPATHLEN] = '\0'; strncpy(imageMask, imagePath, sizeof(imageMask)); imageMask[IOCommon::ITK_MAXPATHLEN] = '\0'; char * lastslash = strrchr(imagePath, '/'); if (lastslash == nullptr) { strcpy(imagePath, "."); } else { *lastslash = '\0'; } itk::Directory::Pointer Dir = itk::Directory::New(); if (Dir->Load(imagePath) == 0) { RAISE_EXCEPTION(); } std::vector::size_type i; std::vector::size_type numfiles; GEImageHeader * curImageHeader; for (i = 0, numfiles = Dir->GetNumberOfFiles(); i < numfiles; ++i) { const char * curFname = Dir->GetFile(static_cast(i)); if (curFname == nullptr) { break; } const std::string fullPath = std::string(imagePath) + "/" + curFname; if (FileNameToRead == fullPath) // strcmp(curFname,FileNameToRead) == // 0) { continue; } try { curImageHeader = this->ReadHeader(fullPath.c_str()); } catch (const itk::ExceptionObject &) { // ReadGE4XHeader throws an exception on any error. // So if, for example we run into a subdirectory, it would // throw an exception, and we'd just want to skip it. continue; } if ((((isCT) ? curImageHeader->examNumber : curImageHeader->echoNumber) == m_FilenameList->GetKey2()) && (curImageHeader->seriesNumber == m_FilenameList->GetKey1())) { AddElementToList(curImageHeader->filename, curImageHeader->sliceLocation, curImageHeader->offset, curImageHeader->imageXsize, curImageHeader->imageYsize, curImageHeader->imageXres, curImageHeader->imageYres, curImageHeader->seriesNumber, (isCT) ? curImageHeader->examNumber : curImageHeader->echoNumber); // If CT use examNumber, otherwise use echoNumber. } delete curImageHeader; } // sort image list m_FilenameList->sortImageList(); // // // set the image properties this->SetDimensions(0, m_ImageHeader->imageXsize); this->SetDimensions(1, m_ImageHeader->imageYsize); this->SetDimensions(2, static_cast(m_FilenameList->NumFiles())); this->SetSpacing(0, m_ImageHeader->imageXres); this->SetSpacing(1, m_ImageHeader->imageYres); this->SetSpacing(2, m_ImageHeader->sliceThickness + m_ImageHeader->sliceGap); // // set direction cosines using OrientAdapterType = SpatialOrientationAdapter; SpatialOrientationAdapter::DirectionType dir = OrientAdapterType().ToDirectionCosines(m_ImageHeader->coordinateOrientation); std::vector dirx(3, 0), diry(3, 0), dirz(3, 0); dirx[0] = dir[0][0]; dirx[1] = dir[1][0]; dirx[2] = dir[2][0]; diry[0] = dir[0][1]; diry[1] = dir[1][1]; diry[2] = dir[2][1]; dirz[0] = dir[0][2]; dirz[1] = dir[1][2]; dirz[2] = dir[2][2]; this->SetDirection(0, dirx); this->SetDirection(1, diry); this->SetDirection(2, dirz); this->ModifyImageInformation(); } void IPLCommonImageIO::SortImageListByNameAscend() { m_FilenameList->SetSortOrder(IPLFileNameList::SortByNameAscend); } void IPLCommonImageIO::SortImageListByNameDescend() { m_FilenameList->SetSortOrder(IPLFileNameList::SortByNameDescend); } /** * */ void IPLCommonImageIO::WriteImageInformation() { RAISE_EXCEPTION(); } /** * */ void IPLCommonImageIO::Write(const void *) { RAISE_EXCEPTION(); } int IPLCommonImageIO::GetStringAt(std::ifstream & f, std::streamoff Offset, char * buf, size_t amount, bool throw_exception) { f.seekg(Offset, std::ios::beg); if (f.fail()) { if (throw_exception) { RAISE_EXCEPTION(); } else { return -1; } } if (!this->ReadBufferAsBinary(f, (void *)buf, amount)) { if (throw_exception) { RAISE_EXCEPTION(); } else { return -1; } } return 0; } int IPLCommonImageIO::GetIntAt(std::ifstream & f, std::streamoff Offset, int * ip, bool throw_exception) { int tmp; if (this->GetStringAt(f, Offset, (char *)&tmp, sizeof(int), throw_exception) == 0) { *ip = this->hdr2Int((char *)&tmp); } else { *ip = 0; } return 0; } int IPLCommonImageIO::GetShortAt(std::ifstream & f, std::streamoff Offset, short * ip, bool throw_exception) { short tmp; if (this->GetStringAt(f, Offset, (char *)&tmp, sizeof(short), throw_exception) == 0) { *ip = this->hdr2Short((char *)&tmp); } else { *ip = 0; } return 0; } int IPLCommonImageIO::GetFloatAt(std::ifstream & f, std::streamoff Offset, float * ip, bool throw_exception) { float tmp; if (this->GetStringAt(f, Offset, (char *)&tmp, sizeof(float), throw_exception) == 0) { *ip = this->hdr2Float((char *)&tmp); } else { *ip = 0.0f; } return 0; } int IPLCommonImageIO::GetDoubleAt(std::ifstream & f, std::streamoff Offset, double * ip, bool throw_exception) { double tmp; if (this->GetStringAt(f, Offset, (char *)&tmp, sizeof(double), throw_exception) == 0) { *ip = this->hdr2Double((char *)&tmp); } else { *ip = 0.0; } return 0; } short IPLCommonImageIO::hdr2Short(char * hdr) { short shortValue; memcpy(&shortValue, hdr, sizeof(short)); ByteSwapper::SwapFromSystemToBigEndian(&shortValue); return (shortValue); } int IPLCommonImageIO::hdr2Int(char * hdr) { int intValue; memcpy(&intValue, hdr, sizeof(int)); ByteSwapper::SwapFromSystemToBigEndian(&intValue); return (intValue); } float IPLCommonImageIO::hdr2Float(char * hdr) { float floatValue; memcpy(&floatValue, hdr, 4); ByteSwapper::SwapFromSystemToBigEndian(&floatValue); return (floatValue); } double IPLCommonImageIO::hdr2Double(char * hdr) { double doubleValue; memcpy(&doubleValue, hdr, sizeof(double)); ByteSwapper::SwapFromSystemToBigEndian(&doubleValue); return (doubleValue); } int IPLCommonImageIO::AddElementToList(char const * const filename, const float sliceLocation, const int offset, const int XDim, const int YDim, const float XRes, const float YRes, const int Key1, const int Key2) { if (m_FilenameList->NumFiles() == 0) { m_FilenameList->SetXDim(XDim); m_FilenameList->SetYDim(YDim); m_FilenameList->SetXRes(XRes); m_FilenameList->SetYRes(YRes); m_FilenameList->SetKey1(Key1); m_FilenameList->SetKey2(Key2); } else if (XDim != m_FilenameList->GetXDim() || YDim != m_FilenameList->GetYDim()) { return 0; } else if (itk::Math::NotAlmostEquals(XRes, m_FilenameList->GetXRes()) || itk::Math::NotAlmostEquals(YRes, m_FilenameList->GetYRes())) { return 0; } else if (m_FilenameList->GetKey1() != Key1 || m_FilenameList->GetKey2() != Key2) { return 1; // It is OK for keys to not match, Just don't add. } m_FilenameList->AddElementToList(filename, sliceLocation, offset, XDim, YDim, XRes, YRes, 0, Key1, Key2); return 1; } void IPLCommonImageIO::sortImageListAscend() { m_FilenameList->sortImageListAscend(); } void IPLCommonImageIO::sortImageListDescend() { m_FilenameList->sortImageListDescend(); } int IPLCommonImageIO::statTimeToAscii(void * clock, char * timeString, int len) { auto tclock = (time_t) * ((int *)clock); const char * const asciiTime = ctime(&tclock); strncpy(timeString, asciiTime, len); timeString[len - 1] = '\0'; char * newline; if ((newline = strrchr(timeString, '\n')) != nullptr || (newline = strrchr(timeString, '\r'))) { *newline = '\0'; } return 1; } } // end namespace itk