// ************************************************************************** /** @class FactFits @brief FITS writter for the FACT project. The FactFits class is able to open, manage and update FITS files. The file columns should be given to the class before the file is openned. Once a file has been created, the structure of its columns cannot be changed. Only row can be added. This class relies on the CCfits and CFitsIO packages. */ // ************************************************************************** #include "Fits.h" #include "Time.h" #include "Converter.h" #include "MessageImp.h" //for file stats #include using namespace std; using namespace CCfits; // -------------------------------------------------------------------------- // //! This gives a standard variable to the file writter. //! This variable should not be related to the DIM service being logged. //! @param desc the description of the variable to add //! @param dataFormat the FITS data format corresponding to the variable to add. //! @param dataPointer the memory location where the variable is stored //! @param numDataBytes the number of bytes taken by the variable // void Fits::AddStandardColumn(Description& desc, string dataFormat, void* dataPointer, long unsigned int numDataBytes) { //check if entry already exist for (vector::iterator it=fStandardColDesc.begin(); it != fStandardColDesc.end(); it++) if (it->name == desc.name) return; fStandardColDesc.push_back(desc); fStandardFormats.push_back(dataFormat); fStandardPointers.push_back(dataPointer); fStandardNumBytes.push_back(numDataBytes); } // -------------------------------------------------------------------------- // //! This gives the file writter access to the DIM data //! @param desc a vector containing the description of all the columns to log //! @param dataFormat a vector containing the FITS data format of all the columsn to log //! @param dataPointer the memory location where the DIM data starts //! @param numDataBytes the number of bytes taken by the DIM data. // void Fits::InitDataColumns(vector desc, vector& dataFormat, void* dataPointer, int numDataBytes) {//we will copy this information here. It duplicates the data, which is not great, but it is the easiest way of doing it right now if (desc.size() == dataFormat.size()) { fDataColDesc = desc; } else { fDataColDesc.clear(); for (unsigned int i=0;iError(str); fFile = NULL; return false; } fOwner = true; fNumOpenFitsFiles = fitsCounter; (*fNumOpenFitsFiles)++; } else { fFile = file; fOwner = false; } //concatenate the standard and data columns //do it the inneficient way first: its easier and faster to code. vector allNames; vector allDataTypes; vector allUnits; fTotalNumBytes = 0; for (unsigned int i=0;i(fDataColDesc.size())-1;i>=0;i--) for (int i=0; i< static_cast(fDataColDesc.size()); i++) { if (fDataColDesc[i].name != "") allNames.push_back(fDataColDesc[i].name); else { ostringstream stt; stt << "Data" << i; allNames.push_back(stt.str()); } allDataTypes.push_back(fDataFormats[i]); allUnits.push_back(fDataColDesc[i].unit); } fTotalNumBytes += fDataNumBytes; bool updating = false; try { string factTableName = tableName; //first, let's check if the table already exist in the file vector tryToLoadName; tryToLoadName.push_back(factTableName); fFile->read(tryToLoadName); const multimap< string, CCfits::ExtHDU * >& extMap = fFile->extension(); if (extMap.find(factTableName) == extMap.end()) { for (multimap::const_iterator it=extMap.begin(); it!= extMap.end(); it++) fMess->Debug(it->first); fTable = fFile->addTable(factTableName, 0, allNames, allDataTypes, allUnits); } else { fTable = dynamic_cast(extMap.find(factTableName)->second); } if (!fTable) { fMess->Error("The table " + factTableName + " could not be created nor loaded. skipping it"); Close(); return false; } fTable->makeThisCurrent(); fCopyBuffer = new unsigned char[fTotalNumBytes]; fNumRows = fTable->rows(); if (fNumRows !=0) {//If the file already existed, then we must load its data to memory before writing to it. BinTable* bTable = dynamic_cast(fTable); if (!bTable) { fMess->Error("The table " + factTableName + " could not be converted to a binary table. skipping"); Close(); return false; } //read the table binary data. vector colName; bTable->readData(true, colName); //double check that the data was indeed read from the disk. Go through the fTable instead as colName is empty (yes, it is !) map cMap = fTable->column(); map::iterator cMapIt; for (cMapIt = cMap.begin(); cMapIt != cMap.end(); cMapIt++) { if (!cMapIt->second->isRead()) { fMess->Error("Column " + cMapIt->first + "Could not be read back from the disk"); Close(); return false; } } updating = true; } } catch(CCfits::FitsException e) { ostringstream str; str << "Could not open or create FITS table " << tableName << " in file " << fileName << " reason: " << e.message(); fMess->Error(str); fTable = NULL; Close(); return false; } //As requested by Roland, the reference MjD is 0.0 fRefMjD = 0;//* static_cast(fStandardPointers[0]); if (!updating) return WriteHeaderKeys(); return true; } // -------------------------------------------------------------------------- // //!This writes a single header key in the currently open file. //!@param name the key //!@param value the key value //!@param a comment explaining the meaning of the key template bool Fits::WriteSingleHeaderKey(string name, T value, string comment) { try { fTable->addKey(name, value, comment); } catch (CCfits::FitsException e) { ostringstream str; str << "Could not add header keys in file " << fFileName << " reason: " << e.message(); fMess->Error(str); return false; } return true; } // -------------------------------------------------------------------------- // //! This writes the standard header // bool Fits::WriteHeaderKeys() { if (!fTable) return false; string name; string comment; string stringValue; if (!WriteSingleHeaderKey("EXTREL", 1.0f, "Release Number")) return false; if (!WriteSingleHeaderKey("TELESCOP", "FACT", "Telescope that acquired this data")) return false; if (!WriteSingleHeaderKey("ORIGIN", "ISDC", "Institution that wrote the file")) return false; if (!WriteSingleHeaderKey("CREATOR", "FACT++ DataLogger", "Program that wrote this file")) return false; stringValue = Time().GetAsStr(); stringValue[10]= 'T'; if (!WriteSingleHeaderKey("DATE", stringValue, "File creation data")) return false; if (!WriteSingleHeaderKey("TIMESYS", "TT", "Time frame system")) return false; if (!WriteSingleHeaderKey("TIMEUNIT", "d", "Time unit")) return false; if (!WriteSingleHeaderKey("TIMEREF", "UTC", "Time reference frame")) return false; if (!WriteSingleHeaderKey("MJDREF", fRefMjD, "Modified Julian Date of origin")) return false; if (!WriteSingleHeaderKey("TSTOP", fEndMjD, "Time of the last receied data")) return false; return true; } // -------------------------------------------------------------------------- // //! This writes one line of data to the file. //! @param conv the converter corresponding to the service being logged // bool Fits::Write(Converter* conv) { //first copy the standard variables to the copy buffer int shift = 0; for (unsigned int i=0;i(fStandardPointers[i]); reverse_copy(charSrc, charSrc+fStandardNumBytes[i], &fCopyBuffer[shift]); shift += fStandardNumBytes[i]; } try { //now take care of the DIM data. The Converter is here for that purpose conv->ToFits(&fCopyBuffer[shift], fDataPointer, fDataNumBytes); } catch (const runtime_error &e) { ostringstream str; str << fFileName << ": " << e.what(); fMess->Error(str); return false; } fTable->makeThisCurrent(); int status(0); if (fits_insert_rows(fTable->fitsPointer(), fNumRows, 1, &status)) { ostringstream str; str << "Inserting row into " << fFileName << " failed (fits_insert_rows, rc=" << status << ")"; fMess->Error(str); Close(); return false; } fNumRows++; //the first standard variable is the current MjD if (fEndMjD==0) { const double doubleValue = *reinterpret_cast(fStandardPointers[0]); WriteSingleHeaderKey("TSTART", doubleValue, "Time of the first received data"); } fEndMjD = *reinterpret_cast(fStandardPointers[0]); //data copied to buffer, can write to fits if (fits_write_tblbytes(fFile->fitsPointer(), fNumRows, 1, fTotalNumBytes, fCopyBuffer, &status)) { char text[30];//max length of cfitsio error strings (from doc) fits_get_errstatus(status, text); ostringstream str; str << "Writing FITS row " << fNumRows << " in " << fFileName << ": " << text << " (file_write_tblbytes, rc=" << status << ")"; fMess->Error(str); Close(); return false; } return true; } // -------------------------------------------------------------------------- // //! This closes the currently openned FITS file. //! it also updates the header to reflect the time of the last logged row // void Fits::Close() { //WARNING: do NOT delete the table as it gets deleted by the fFile object // if (fTable != NULL) // delete fTable; WriteSingleHeaderKey("TSTOP", fEndMjD, "Time of the last receied data"); if (fFile != NULL && fOwner) { // fFile->flush(); delete fFile; } fFile = NULL; if (fCopyBuffer != NULL) delete [] fCopyBuffer; fCopyBuffer = NULL; if (fOwner && fNumOpenFitsFiles != NULL) (*fNumOpenFitsFiles)--; //fMess is the MessageImp part of the dataLogger itself. Thus it should NOT be deleted by the Fits files destructor. // if (fMess) // delete fMess; fMess = NULL; } // -------------------------------------------------------------------------- //! Returns the size on the disk of the Fits file being written. int Fits::GetWrittenSize() const { if (!IsOpen()) return 0; struct stat st; if (stat(fFileName.c_str(), &st)) return 0; else return st.st_size; }