// ************************************************************************** /** @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, std::string dataFormat, void* dataPointer, long unsigned int numDataBytes) { //check if entry already exist for (std::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(std::vector desc, std::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; } 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. std::vector allNames; std::vector allDataTypes; std::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 { std::stringstream 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 { std::string factTableName = "FACT-" + tableName; fTable = fFile->addTable(factTableName, 0, allNames, allDataTypes, allUnits); 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"); return; } //read the table binary data. std::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 !) std::map cMap = fTable->column(); std::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"); return; } } updating = true; } } catch(CCfits::FitsError e) { std::stringstream str; str << "Could not open or create FITS table " << tableName << " in file " << fileName << " reason: " << e.message(); fMess->Error(str); fTable = NULL; } //As requested by Roland, the reference MjD is 0.0 fRefMjD = 0;//* static_cast(fStandardPointers[0]); if (!updating) WriteHeaderKeys(); } template void Fits::WriteSingleHeaderKey(string name, T value, string comment) { try { fTable->addKey(name, value, comment); } catch (CCfits::FitsError e) { std::stringstream str; str << "Could not add header keys in file " << fFileName << " reason: " << e.message(); fMess->Error(str); } } // -------------------------------------------------------------------------- // //! This writes the standard header // void Fits::WriteHeaderKeys() { if (!fTable) return; std::string name; std::string comment; // float floatValue; // double doubleValue; std::string stringValue; WriteSingleHeaderKey("EXTREL", 1.0f, "Release Number"); WriteSingleHeaderKey("TELESCOP", "FACT", "Telescope that acquired this data"); WriteSingleHeaderKey("ORIGIN", "ISDC", "Institution that wrote the file"); WriteSingleHeaderKey("CREATOR", "FACT++ DataLogger", "Program that wrote this file"); stringValue = Time().GetAsStr(); stringValue[10]= 'T'; WriteSingleHeaderKey("DATE", stringValue, "File creation data"); WriteSingleHeaderKey("TIMESYS", "TT", "Time frame system"); WriteSingleHeaderKey("TIMEUNIT", "d", "Time unit"); WriteSingleHeaderKey("TIMEREF", "UTC", "Time reference frame"); WriteSingleHeaderKey("MJDREF", fRefMjD, "Modified Julian Date of origin"); } // -------------------------------------------------------------------------- // //! This writes one line of data to the file. //! @param conv the converter corresponding to the service being logged // void Fits::Write(Converter* conv) { // try // { fTable->makeThisCurrent(); int status(0); if (fits_insert_rows(fTable->fitsPointer(), fNumRows, 1, &status)) { std::stringstream str; str << "Could not insert row in file " << fFileName << ". cfitsio error code: " << status; fMess->Error(str); } // fTable->insertRows(fNumRows); // } // catch(CCfits::FitsError e) // { // std::stringstream str; // str << "Could not insert row in file " << fFileName << " reason: " << e.message(); // fMess->Error(str); // } fNumRows++; //the first standard variable is the current MjD if (fEndMjD == 0) { double doubleValue = *static_cast(fStandardPointers[0]);// - fRefMjD; WriteSingleHeaderKey("TSTART", doubleValue, "Time of the first received data"); } fEndMjD = * static_cast(fStandardPointers[0]); //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]); // for (int j=0; j(fStandardPointers[i])[fStandardNumBytes[i]-(j+1)]; shift+= fStandardNumBytes[i]; } //now take care of the DIM data. The Converter is here for that purpose conv->ToFits(static_cast(&fCopyBuffer[shift]), fDataPointer, fDataNumBytes); //data copied to buffer, can write to fits // int status = 0; //TODO check the status after the write operation fits_write_tblbytes(fFile->fitsPointer(), fNumRows, 1, fTotalNumBytes, fCopyBuffer, &status); if (status) { char text[30];//max length of cfitsio error strings (from doc) fits_get_errstatus(status, text); std::stringstream str; str << "Error while writing FITS row in " << fFileName << ". Message: " << text << " [" << status << "]"; fMess->Error(str); } //This forces the writting of each row to the disk. Otherwise all rows are written when the file is closed. ///TODO check whether this consumes too much resources or not. If it does, flush every N write operations instead /* try { fFile->flush(); } catch (CCfits::FitsError e) { std::stringstream str; str << "Error while flushing bytes to disk. File: " << fFileName << " reason: " << e.message(); fMess->Error(str); } */} // -------------------------------------------------------------------------- // //! 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) 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; } // -------------------------------------------------------------------------- // int Fits::GetWrittenSize() { if (!IsOpen()) return 0; struct stat st; if (stat(fFileName.c_str(), &st)) return 0; else return st.st_size; }