#include <stdlib.h> #ifndef WIN32 #include <wordexp.h> #endif #include <zlib.h> #include <stdio.h> #include <fstream> #include <sstream> #include <iclFileReader.h> #include <iclConverter.h> #include <iclStrTok.h> /* FileReader.cpp Written by: Michael Götting, Robert Haschke (2006) University of Bielefeld AG Neuroinformatik mgoettin@techfak.uni-bielefeld.de */ using namespace std; namespace icl { inline void replace_newline (string::value_type& c) { if (c == '\n') c = ' '; } //-------------------------------------------------------------------------- string FileReader::hashPattern(const std::string& sFileName) { std::string::size_type iSuffixPos=string::npos; unsigned int nHashes=0; // count number of hashes directly before file suffix analyseHashes (sFileName, nHashes, iSuffixPos); if (nHashes) { // and replace them by [0-9] regular expressions ostringstream oss; for (unsigned int i=1; i <= nHashes; ++i) { oss << sFileName.substr(0, iSuffixPos-nHashes); for (unsigned int j=1; j <= i; ++j) oss << "[0-9]"; oss << sFileName.substr(iSuffixPos) << " "; } return oss.str(); } return sFileName; } //-------------------------------------------------------------------------- FileReader::FileReader(string sPattern) // {{{ open { FUNCTION_LOG(""); #ifndef WIN32 wordexp_t match; char **ppcFiles; // remove newlines from sPattern std::for_each (sPattern.begin(), sPattern.end(), replace_newline); // search for file matching the pattern(s) switch (wordexp (sPattern.c_str(), &match, WRDE_UNDEF)) { case 0: break; case WRDE_BADCHAR: throw ICLException ("illegal chars in pattern (|, &, ;, <, >, (, ), {, }"); break; case WRDE_BADVAL: throw ICLException ("encountered undefined shell variable"); break; case WRDE_NOSPACE: throw ICLException ("out of memory"); break; case WRDE_SYNTAX: throw ICLException ("syntax error, e.g. unbalanced parentheses or quotes"); break; } ppcFiles = match.we_wordv; for (unsigned int i=0; i < match.we_wordc; ++i) { bool bGzipped; switch (getFileType (ppcFiles[i], bGzipped)) { case ioFormatSEQ: readSequenceFile (ppcFiles[i]); break; case ioFormatUnknown: break; // skip unknown file types default: m_vecFileName.push_back(ppcFiles[i]); break; } } wordfree(&match); #else bool bGzipped; switch (getFileType (sPattern, bGzipped)) { case ioFormatSEQ: readSequenceFile (sPattern); break; case ioFormatUnknown: break; // skip unknown file types default: m_vecFileName.push_back(sPattern); break; } #endif this->init (); } // }}} //-------------------------------------------------------------------------- FileReader::FileReader(const string& sPrefix, const string& sType, int iObjStart, int iObjEnd, int iImageStart, int iImageEnd) // {{{ open { FUNCTION_LOG(""); ostringstream ossFileInit; ossFileInit << "." << sType; bool bDummy; ioformat eFormat = getFileType (ossFileInit.str(), bDummy); if (eFormat == ioFormatSEQ || eFormat == ioFormatUnknown) throw ICLException (string("not supported file type ") + sType); //---- Build filename ---- for (int i=iObjStart;i<=iObjEnd;i++) { for (int j=iImageStart;j<=iImageEnd;j++) { ostringstream ossObjectCnt, ossFile; ossFile << sPrefix << i << "__" << j << "." << sType; ossObjectCnt << i << "__" << j; m_vecFileName.push_back(ossFile.str()); m_vecObjectCnt.push_back(ossObjectCnt.str()); } } this->init (); } // }}} //-------------------------------------------------------------------------- FileReader::FileReader(const FileReader& other) : m_poCurImg(0) { FUNCTION_LOG(""); *this = other; } //-------------------------------------------------------------------------- FileReader& FileReader::operator=(const FileReader& other) { // {{{ open FUNCTION_LOG(""); if (!this->m_bBuffered) delete this->m_poCurImg; this->m_vecFileName = other.m_vecFileName; this->m_vecObjectCnt = other.m_vecObjectCnt; this->m_vecImgBuffer = other.m_vecImgBuffer; this->m_bBuffered = other.m_bBuffered; this->m_iCurImg = other.m_iCurImg; this->m_poCurImg = 0; // setup the jpeg error routine once jpgCinfo.err = jpeg_std_error(&jpgErr); jpgErr.error_exit = icl_jpeg_error_exit; return *this; } // }}} //-------------------------------------------------------------------------- FileReader::~FileReader () // {{{ open { FUNCTION_LOG(""); for (ImageBuffer::iterator it=m_vecImgBuffer.begin(), end=m_vecImgBuffer.end(); it != end; ++it) delete *it; if (!m_bBuffered) delete m_poCurImg; } // }}} //-------------------------------------------------------------------------- void FileReader::init() // {{{ open { FUNCTION_LOG(""); m_bIgnoreDesired = true; m_bBuffered = false; m_iCurImg = 0; m_poCurImg = 0; if (m_vecFileName.size () == 0) throw ICLException ("empty file list"); // setup the jpeg error routine once jpgCinfo.err = jpeg_std_error(&jpgErr); jpgErr.error_exit = icl_jpeg_error_exit; } // }}} //-------------------------------------------------------------------------- const ImgBase* FileReader::grab(ImgBase **ppoDst) { // {{{ open if (m_bBuffered) { m_poCurImg = m_vecImgBuffer[m_iCurImg]; } else { readImage (m_vecFileName[m_iCurImg], &m_poCurImg); } // forward to next image next (); if (m_bIgnoreDesired) { m_eDesiredDepth = m_poCurImg->getDepth(); m_oDesiredParams = m_poCurImg->getParams(); } // if we can return the internal image directly (without conversion), do it: if (ppoDst == 0 && (m_bIgnoreDesired || (m_eDesiredDepth == m_poCurImg->getDepth() && m_oDesiredParams == m_poCurImg->getParams()))) return m_poCurImg; // otherwise get pointer to output image instance: ImgBase *poDst = prepareOutput (ppoDst); // and convert the image m_oConverter.apply (m_poCurImg, poDst); return poDst; } // }}} //-------------------------------------------------------------------------- void FileReader::readImage(const string& sFileName, ImgBase** ppoDst) { // {{{ open FUNCTION_LOG(""); //---- Variable definition ---- FileInfo oInfo (sFileName); // set some defaults oInfo.oROI = Rect(); // full ROI try { switch (oInfo.eFileFormat) { case ioFormatICL: case ioFormatPNM: readHeaderPNMICL (oInfo); ensureCompatible (ppoDst, oInfo.eDepth, oInfo.oImgSize, oInfo.iNumChannels, oInfo.eFormat, oInfo.oROI); (*ppoDst)->setTime(oInfo.timeStamp); readDataPNMICL (*ppoDst, oInfo); break; case ioFormatJPG: readHeaderJPG (oInfo); ensureCompatible (ppoDst, oInfo.eDepth, oInfo.oImgSize, oInfo.iNumChannels, oInfo.eFormat, oInfo.oROI); (*ppoDst)->setTime(oInfo.timeStamp); readDataJPG ((*ppoDst)->asImg<icl8u>(), oInfo); break; case ioFormatCSV: readHeaderCSV (oInfo); ensureCompatible (ppoDst, oInfo.eDepth, oInfo.oImgSize, oInfo.iNumChannels, oInfo.eFormat, oInfo.oROI); (*ppoDst)->setTime(oInfo.timeStamp); readDataCSV (*ppoDst, oInfo); break; default: throw ICLException (string("not supported file type")); } } catch (ICLException &e) { if (oInfo.fp) closeFile (oInfo); throw; } closeFile (oInfo); } // }}} //-------------------------------------------------------------------------- FileReader::FileList FileReader::bufferImages (bool bStopOnError) // {{{ open { FUNCTION_LOG(""); FileList vecErrorList; if (m_bBuffered) return vecErrorList; // do not buffer twice // if we enter buffering mode and already read images by calling grab // we must free the internally allocated image first if (m_poCurImg && !m_bBuffered) { delete m_poCurImg; m_poCurImg = 0; } // clear buffer from previous trial for (ImageBuffer::iterator it=m_vecImgBuffer.begin(), end=m_vecImgBuffer.end(); it != end; ++it) delete *it; m_vecImgBuffer.clear(); for (FileList::iterator it=m_vecFileName.begin(), end=m_vecFileName.end(); it != end; ++it) { m_poCurImg = 0; // force reallocation of new image pointer try { readImage (*it, &m_poCurImg); } catch (ICLException &e) { // in any case, report the error vecErrorList.push_back (*it); if (bStopOnError) return vecErrorList; // create some dummy image to insert to buffer vector if (!m_poCurImg) { m_poCurImg = imgNew (depth8u, Size(1,1), formatGray); } } m_vecImgBuffer.push_back (m_poCurImg); } // only on successful reading of all images m_bBuffered = true; return vecErrorList; } // }}} //-------------------------------------------------------------------------- void FileReader::readSequenceFile (const std::string& sFileName) // {{{ open { FUNCTION_LOG(""); string sFile; ifstream streamSeq (sFileName.c_str(),ios::in); if(!streamSeq) { ERROR_LOG("Can't open sequence file: " << sFileName); } while(streamSeq) { getline (streamSeq, sFile); if (!sFile.empty()) { // ignore empty lines m_vecFileName.push_back(sFile); } } streamSeq.close(); } // }}} //-------------------------------------------------------------------------- void FileReader::setCSVHeader (depth d, const ImgParams& p) { // {{{ open m_CSVDepth = d; m_CSVParams = p; } // }}} void FileReader::readHeaderCSV (FileInfo &oInfo) { // {{{ open // use m_CSV parameters as default settings oInfo.eDepth = m_CSVDepth; oInfo.oImgSize = m_CSVParams.getSize(); oInfo.oROI=m_CSVParams.getROI(); oInfo.iNumChannels = m_CSVParams.getChannels(); oInfo.eFormat = m_CSVParams.getFormat(); //oInfo.timeStamp = 0; // overwrite these settings by file name contents int pos=oInfo.sFileName.find("-ICL:",0)+5; StrTok tok(oInfo.sFileName.substr(pos),":"); if(tok.hasMoreTokens()){ StrTok tok2(tok.nextToken(),"x");// WxHxC if(tok2.hasMoreTokens()){ oInfo.oImgSize.width=atoi((tok2.nextToken()).c_str()); // W } if(tok2.hasMoreTokens()){ oInfo.oImgSize.height=atoi((tok2.nextToken()).c_str()); // H } if(tok2.hasMoreTokens()){ oInfo.iNumChannels=atoi((tok2.nextToken()).c_str()); //C } } if(tok.hasMoreTokens()){ oInfo.eDepth=translateDepth(tok.nextToken()); //D } if(tok.hasMoreTokens()){ StrTok tok2(tok.nextToken(),"."); if(tok2.hasMoreTokens()){ oInfo.eFormat=translateFormat(tok2.nextToken()); //F } } } // }}} template<class T> void FileReader::readCSVTmpl(Img<T>* poImg, FileInfo &oInfo) { // {{{ open FUNCTION_LOG(""); //Img16s *poImg16s = poImg->asImg<icl16s>(); T *pc2Buf[3]; pc2Buf[0] = poImg->getData (0); pc2Buf[1] = poImg->getData (1); pc2Buf[2] = poImg->getData (2); openFile (oInfo, "rb"); // open file for reading char *pcBuf=0; int fsize=20*oInfo.oImgSize.width; //20 = max char length of double (should be 14) +1 (komma) + 5 bonus pcBuf=(char*)malloc(fsize*sizeof(char)); for (int c=0;c<oInfo.iNumChannels;c++) { for (int i=0;i<oInfo.oImgSize.height;i++) { char *result = NULL; fgets (pcBuf, fsize,(FILE*)oInfo.fp); result = strtok( pcBuf, ","); for (int j=0;j<oInfo.oImgSize.width;j++) { *pc2Buf[c]=atoi(result);pc2Buf[c]++; result = strtok( NULL, ","); } } } free (pcBuf); } // }}} void FileReader::readDataCSV(ImgBase* poImg, FileInfo &oInfo) { // {{{ open FUNCTION_LOG(""); switch(poImg->getDepth()) { case depth8u: readCSVTmpl<icl8u>(poImg->asImg<icl8u>(),oInfo); break; case depth16s: readCSVTmpl<icl16s>(poImg->asImg<icl16s>(),oInfo); break; case depth32s: readCSVTmpl<icl32s>(poImg->asImg<icl32s>(),oInfo); break; case depth32f: readCSVTmpl<icl32f>(poImg->asImg<icl32f>(),oInfo); break; case depth64f: readCSVTmpl<icl64f>(poImg->asImg<icl64f>(),oInfo); break; default: ICL_INVALID_DEPTH; break; } } // }}} //-------------------------------------------------------------------------- void FileReader::readHeaderPNMICL (FileInfo &oInfo) { // {{{ open FUNCTION_LOG(""); openFile (oInfo, "rb"); // open file for reading char acBuf[1024], *pcBuf; istringstream iss; if (oInfo.eFileFormat != ioFormatICL) { //---- Read the magic number (for non-ICL format only) ---- if (!gzgets (oInfo.fp, acBuf, 1024) || acBuf[0] != 'P') throw InvalidFileFormatException(); switch (acBuf[1]) { case '6': oInfo.eFormat = formatRGB; break; case '5': oInfo.eFormat = formatGray; break; default: throw InvalidFileFormatException(); } } //---- Set default values ---- oInfo.iNumChannels = getChannelsOfFormat(oInfo.eFormat); oInfo.iNumImages = 1; oInfo.eDepth = depth8u; // {{{ Read special header info do { if (!gzgets (oInfo.fp, acBuf, 1024)) throw InvalidFileFormatException(); // skip withe space in beginning of line pcBuf = acBuf; while (*pcBuf && isspace(*pcBuf)) ++pcBuf; if (*pcBuf && *pcBuf != '#') break; // no more comments: break from loop // process comment iss.str (pcBuf+1); string sKey, sValue; iss >> sKey; if (sKey == "NumFeatures" || sKey == "NumImages") { iss >> oInfo.iNumImages; oInfo.iNumChannels *= oInfo.iNumImages; } else if (sKey == "ROI") { iss >> oInfo.oROI.x; iss >> oInfo.oROI.y; iss >> oInfo.oROI.width; iss >> oInfo.oROI.height; continue; } else if (sKey == "ImageDepth") { // ignore image depth for all formats but ICL if (oInfo.eFileFormat != ioFormatICL) continue; iss >> sValue; // might throw an InvalidDepthException oInfo.eDepth = translateDepth (sValue); continue; } else if (sKey == "Format") { iss >> sValue; // might throw an InvalidFormatException oInfo.eFormat = translateFormat(sValue.c_str()); } else if (sKey == "TimeStamp") { Time::value_type t; iss >> t; oInfo.timeStamp = Time::microSeconds(t); continue; } //---- Is num channels in depence to the format ---- if (getChannelsOfFormat(oInfo.eFormat) != oInfo.iNumChannels) oInfo.eFormat = formatMatrix; } while (true); // }}} // read image size iss.str (pcBuf); iss >> oInfo.oImgSize.width; iss >> oInfo.oImgSize.height; oInfo.oImgSize.height = oInfo.oImgSize.height / oInfo.iNumImages; // skip line with maximal pixel value if (!gzgets (oInfo.fp, acBuf, 1024)) throw InvalidFileFormatException(); } // }}} void FileReader::readDataPNMICL(ImgBase* poImg, FileInfo &oInfo) { // {{{ open FUNCTION_LOG(""); if (oInfo.iNumImages == oInfo.iNumChannels || oInfo.eFileFormat == ioFormatICL) { // file format is non-interleaved, i.e. grayscale or icl proprietary int iDim = poImg->getDim () * getSizeOf (poImg->getDepth ()); for (int i=0;i<oInfo.iNumChannels;i++) { if (gzread (oInfo.fp, poImg->getDataPtr(i), iDim) != iDim) throw InvalidFileFormatException (); } } else if (poImg->getDepth() == depth8u) { // file format is interleaved, i.e. RGB or something similar Img8u *poImg8u = poImg->asImg<icl8u>(); int iLines = oInfo.oImgSize.height; int iDim = 3 * oInfo.oImgSize.width; icl8u *pcBuf = new icl8u[iDim]; icl8u *pc; for (int i=0;i<oInfo.iNumImages;i++) { icl8u *pcR = poImg8u->getData (i*3); icl8u *pcG = poImg8u->getData (i*3+1); icl8u *pcB = poImg8u->getData (i*3+2); for (int l=0; l<iLines; l++) { if (gzread (oInfo.fp, pcBuf, iDim) != iDim) throw InvalidFileFormatException (); pc=pcBuf; for (int c=0; c<oInfo.oImgSize.width; ++c, ++pcR, ++pcG, ++pcB) { *pcR = *pc++; *pcG = *pc++; *pcB = *pc++; } // for cols (deinterleave) } // for lines } // for images delete[] pcBuf; } else { ERROR_LOG ("This should not happen."); } } // }}} //-------------------------------------------------------------------------- void FileReader::readHeaderJPG (FileInfo &oInfo) { // {{{ open FUNCTION_LOG(""); openFile (oInfo, "rb"); // open file for reading /* SETUP ERROR HANDLING: * * Our example here shows how to override the "error_exit" method so that * control is returned to the library's caller when a fatal error occurs, * rather than calling exit() as the standard error_exit method does. * * We use C's setjmp/longjmp facility to return control. This means that the * routine which calls the JPEG library must first execute a setjmp() call to * establish the return point. We want the replacement error_exit to do a * longjmp(). But we need to make the setjmp buffer accessible to the * error_exit routine. To do this, we use a private extension of the * standard JPEG error handler object. */ if (setjmp(jpgErr.setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error. * We need to clean up the JPEG object and signal the error to the caller */ jpeg_destroy_decompress(&jpgCinfo); throw InvalidFileFormatException(); } /* Step 1: Initialize the JPEG decompression object. */ jpeg_create_decompress(&jpgCinfo); /* Step 2: specify data source (eg, a file) */ jpeg_stdio_src(&jpgCinfo, (FILE*) oInfo.fp); /* request to save comments */ jpeg_save_markers (&jpgCinfo, JPEG_COM, 1024); /* Step 3: read file parameters with jpeg_read_header() */ (void) jpeg_read_header(&jpgCinfo, TRUE); /* evaluate markers, i.e. comments */ for (jpeg_saved_marker_ptr pMarker = jpgCinfo.marker_list; pMarker; pMarker = pMarker->next) { if (pMarker->marker != JPEG_COM) continue; char acBuf[1025] = ""; memcpy (acBuf, pMarker->data, pMarker->data_length); acBuf[pMarker->data_length] = '\0'; // terminating null istringstream iss (acBuf); string sKey, sValue; iss >> sKey; if (sKey == "TimeStamp") { Time::value_type t; iss >> t; oInfo.timeStamp = Time::microSeconds(t); } else if (sKey == "ROI") { iss >> oInfo.oROI.x; iss >> oInfo.oROI.y; iss >> oInfo.oROI.width; iss >> oInfo.oROI.height; } } /* Step 4: set parameters for decompression */ /* Step 5: Start decompressor */ (void) jpeg_start_decompress(&jpgCinfo); /* After jpeg_start_decompress() we have the correct scaled * output image dimensions available, as well as the output colormap * if we asked for color quantization. */ oInfo.eDepth = depth8u; // can only read depth8u oInfo.oImgSize.width = jpgCinfo.output_width; oInfo.oImgSize.height = jpgCinfo.output_height; switch (jpgCinfo.out_color_space) { case JCS_GRAYSCALE: oInfo.eFormat = formatGray; break; case JCS_RGB: oInfo.eFormat = formatRGB; break; case JCS_YCbCr: oInfo.eFormat = formatYUV; break; default: throw ICLException("unknown color space"); } oInfo.iNumChannels = getChannelsOfFormat (oInfo.eFormat); } // }}} //-------------------------------------------------------------------------- void FileReader::readDataJPG(Img<icl8u>* poImg, FileInfo &oInfo) // {{{ open { FUNCTION_LOG(""); icl8u *pcBuf = 0; // update jump context to allow proper throw if (setjmp(jpgErr.setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error. * We need to clean up the JPEG object and signal the error to the caller */ jpeg_destroy_decompress(&jpgCinfo); if (oInfo.iNumChannels == 3) delete[] pcBuf; throw InvalidFileFormatException(); } ICLASSERT (jpgCinfo.output_components == oInfo.iNumChannels); int iRowDim = jpgCinfo.output_width * jpgCinfo.output_components; icl8u *pcR=0, *pcG=0, *pcB=0; if (oInfo.iNumChannels == 1) pcBuf = poImg->getData (0); else if (oInfo.iNumChannels == 3) { pcBuf = new icl8u[iRowDim]; pcR = poImg->getData (0); pcG = poImg->getData (1); pcB = poImg->getData (2); } else {ERROR_LOG ("This should not happen."); return;} /* Step 6: while (scan lines remain to be read) */ /* jpeg_read_scanlines(...); */ while (jpgCinfo.output_scanline < jpgCinfo.output_height) { /* jpeg_read_scanlines expects an array of pointers to scanlines. * Here the array is only one element long, but you could ask for * more than one scanline at a time if that's more convenient. */ (void) jpeg_read_scanlines(&jpgCinfo, &pcBuf, 1); if (oInfo.iNumChannels == 1) pcBuf += iRowDim; else { // deinterleave three channel data icl8u *pc = pcBuf; for (int c=0; c<oInfo.oImgSize.width; ++c, ++pcR, ++pcG, ++pcB) { *pcR = *pc++; *pcG = *pc++; *pcB = *pc++; } } } /* Step 7: Finish decompression */ (void) jpeg_finish_decompress(&jpgCinfo); /* At this point you may want to check to see whether any corrupt-data * warnings occurred (test whether jpgErr.pub.num_warnings is nonzero). */ /* Step 8: Release JPEG decompression object */ jpeg_destroy_decompress(&jpgCinfo); if (oInfo.iNumChannels == 3) delete[] pcBuf; } // }}} //-------------------------------------------------------------------------- bool FileReader::findFile (const std::string& sFile, FileList::iterator& itList) { // {{{ open FUNCTION_LOG(""); // search starting from itList to end FileList::iterator found = find (itList, m_vecFileName.end(), sFile); if (found != m_vecFileName.end()) itList = found; // search second part from begin to itList else if ( (found = find (m_vecFileName.begin(), itList, sFile)) != itList) itList = found; else return false; // item not found return true; } // }}} //-------------------------------------------------------------------------- void FileReader::removeFiles (const FileList& vecFiles) { // {{{ open FUNCTION_LOG(""); FileList::iterator itList = m_vecFileName.begin(); for (FileList::const_iterator itDel=vecFiles.begin (), end=vecFiles.end(); itDel != end; ++itDel) { if (findFile (*itDel, itList)) { if (m_bBuffered) { // delete image at corresponding position ImageBuffer::iterator itBuf = m_vecImgBuffer.begin() + (itList-m_vecFileName.begin()); delete *itBuf; m_vecImgBuffer.erase (itBuf); } // as well as file name itself m_vecFileName.erase (itList); } } } // }}} } // namespace icl