source: trunk/FACT++/src/Fits.cc@ 11575

Last change on this file since 11575 was 11524, checked in by tbretz, 13 years ago
Write now takes a const reference
File size: 14.6 KB
Line 
1// **************************************************************************
2/** @class FactFits
3
4@brief FITS writter for the FACT project.
5
6The FactFits class is able to open, manage and update FITS files.
7
8The file columns should be given to the class before the file is openned. Once
9a file has been created, the structure of its columns cannot be changed. Only
10row can be added.
11
12This class relies on the CCfits and CFitsIO packages.
13
14*/
15// **************************************************************************
16#include "Fits.h"
17
18#include "Time.h"
19#include "Converter.h"
20#include "MessageImp.h"
21
22//for file stats
23#include <sys/stat.h>
24
25using namespace std;
26using namespace CCfits;
27
28// --------------------------------------------------------------------------
29//
30//! This gives a standard variable to the file writter.
31//! This variable should not be related to the DIM service being logged.
32//! @param desc the description of the variable to add
33//! @param dataFormat the FITS data format corresponding to the variable to add.
34//! @param dataPointer the memory location where the variable is stored
35//! @param numDataBytes the number of bytes taken by the variable
36//
37void Fits::AddStandardColumn(const Description& desc, const string &dataFormat, void* dataPointer, long unsigned int numDataBytes)
38{
39 //check if entry already exist
40 for (vector<Description>::const_iterator it=fStandardColDesc.begin(); it != fStandardColDesc.end(); it++)
41 if (it->name == desc.name)
42 return;
43 fStandardColDesc.push_back(desc);
44 fStandardFormats.push_back(dataFormat);
45 fStandardPointers.push_back(dataPointer);
46 fStandardNumBytes.push_back(numDataBytes);
47}
48// --------------------------------------------------------------------------
49//
50//! This gives the file writter access to the DIM data
51//! @param desc a vector containing the description of all the columns to log
52//! @param dataFormat a vector containing the FITS data format of all the columsn to log
53//! @param dataPointer the memory location where the DIM data starts
54//! @param numDataBytes the number of bytes taken by the DIM data.
55//
56void Fits::InitDataColumns(const vector<Description> &desc, vector<string>& dataFormat, void* dataPointer, int numDataBytes)
57{//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
58 if (desc.size() == dataFormat.size())
59 {
60 fDataColDesc = desc;
61 }
62 else
63 {
64 fDataColDesc.clear();
65 for (unsigned int i=0;i<dataFormat.size();i++)
66 {
67 ostringstream stt;
68 stt << "Data" << i;
69 fDataColDesc.push_back(Description(stt.str(), "comment", "unit"));
70 }
71 }
72 fDataFormats = dataFormat;
73 fDataPointer = dataPointer;
74 fDataNumBytes = numDataBytes;
75}
76// --------------------------------------------------------------------------
77//
78//! This looks for a suitable table in the fits file, i.e. that corresponds to the name and column names. (no format check yet)
79//! @param tableName. the base table name to be obtained. If not suitable, numbers are appened to the name
80//! @param allNames. the name of all columns
81//! @param allDataTypes. the data types of all columns
82//! @param allUnits. the units of the columns
83//! @return a pointer to the newly retrieved/created table
84//
85CCfits::Table* Fits::findSuitableTableInFitsFile(const string& tableName,const vector<string>& allNames, const vector<string>& allDataTypes, const vector<string>& allUnits)
86{
87 const multimap< string, CCfits::ExtHDU * >& extMap = fFile->extension();
88 for (int i=0;i<100;i++)
89 {
90 if (i==10)
91 fMess->Error("Already 10 different tables with different formats exist in this file. Please consider re-creating the file entirely (i.e. delete it please)");
92 ostringstream cTableName;
93 cTableName << tableName;
94 if (i != 0)
95 cTableName << "-" << i;
96 //current table name does not exist yet. return its associated fits table newly created
97 if (extMap.find(cTableName.str()) == extMap.end())
98 {
99 for (multimap<string, CCfits::ExtHDU*>::const_iterator it=extMap.begin(); it!= extMap.end(); it++)
100 fMess->Debug(it->first);
101 return fFile->addTable(cTableName.str(), 0, allNames, allDataTypes, allUnits);
102 }
103 CCfits::Table* cTable;
104 cTable = dynamic_cast<CCfits::Table*>(extMap.find(cTableName.str())->second);
105
106 if (!cTable)
107 return NULL;//something wrong happened while getting the table pointer
108
109 //now check that the table columns are the same as the service columns
110 cTable->makeThisCurrent();
111 const map<string, Column*> cMap = cTable->column();
112 for (unsigned int j=0;j<allNames.size();j++)
113 if (cMap.find(allNames[j]) == cMap.end())
114 continue;
115
116 return cTable;
117 }
118 fMess->Error("One hundred trials for new table format failed. aborting");
119 return NULL;
120}
121// --------------------------------------------------------------------------
122//
123//! This opens the FITS file (after the columns have been passed)
124//! @param fileName the filename with complete or relative path of the file to open
125//! @param tableName the name of the table that will receive the logged data.
126//! @param file a pointer to an existing FITS file. If NULL, file will be opened and managed internally
127//! @param fitsCounter a pointer to the integer keeping track of the opened FITS files
128//! @param out a pointer to the MessageImp that should be used to log errors
129//! @param runNumber the runNumber for which this file is opened. 0 means nightly file.
130//
131bool Fits::Open(const string& fileName, const string& tableName, FITS* file, int* fitsCounter, MessageImp* out, int runNumber)
132{
133// if (fMess)
134// delete fMess;
135// fMess = new MessageImp(out);
136 fRunNumber = runNumber;
137 fMess = out;
138 fFileName = fileName;
139 if (file == NULL)
140 {
141 try
142 {
143 fFile = new FITS(fileName, RWmode::Write);
144 }
145 catch (CCfits::FitsException e)
146 {
147 ostringstream str;
148 str << "Opening FITS file " << fileName << ": " << e.message();
149 fMess->Error(str);
150 fFile = NULL;
151 return false;
152 }
153 fOwner = true;
154 fNumOpenFitsFiles = fitsCounter;
155 (*fNumOpenFitsFiles)++;
156 }
157 else
158 {
159 fFile = file;
160 fOwner = false;
161 }
162 //concatenate the standard and data columns
163 //do it the inneficient way first: its easier and faster to code.
164 vector<string> allNames;
165 vector<string> allDataTypes;
166 vector<string> allUnits;
167 fTotalNumBytes = 0;
168 for (unsigned int i=0;i<fStandardColDesc.size();i++)
169 {
170 allNames.push_back(fStandardColDesc[i].name);
171 allDataTypes.push_back(fStandardFormats[i]);
172 allUnits.push_back(fStandardColDesc[i].unit);
173 fTotalNumBytes += fStandardNumBytes[i];
174 }
175 //for (int i=static_cast<int>(fDataColDesc.size())-1;i>=0;i--)
176 for (unsigned int i=0; i<fDataColDesc.size(); i++)
177 {
178 if (fDataColDesc[i].name != "")
179 allNames.push_back(fDataColDesc[i].name);
180 else
181 {
182 ostringstream stt;
183 stt << "Data" << i;
184 allNames.push_back(stt.str());
185 }
186 allDataTypes.push_back(fDataFormats[i]);
187 allUnits.push_back(fDataColDesc[i].unit);
188 }
189 fTotalNumBytes += fDataNumBytes;
190
191 bool updating = false;
192 try
193 {
194 //first, let's check if the table already exist in the file
195 vector<string> tryToLoadName;
196 tryToLoadName.push_back(tableName);
197 fFile->read(tryToLoadName);
198
199// const multimap< string, CCfits::ExtHDU * >& extMap = fFile->extension();
200// if (extMap.find(tableName) == extMap.end())
201// {
202// for (multimap<string, CCfits::ExtHDU*>::const_iterator it=extMap.begin(); it!= extMap.end(); it++)
203// fMess->Debug(it->first);
204// fTable = fFile->addTable(tableName, 0, allNames, allDataTypes, allUnits);
205// }
206// else
207// {
208// fTable = dynamic_cast<CCfits::Table*>(extMap.find(tableName)->second);
209// }
210 fTable = findSuitableTableInFitsFile(tableName, allNames, allDataTypes, allUnits);
211
212 if (!fTable)
213 {
214 fMess->Error("Table " + tableName + " could not be created nor loaded from "+fileName);
215 Close();
216 return false;
217 }
218 fTable->makeThisCurrent();
219 fCopyBuffer = new unsigned char[fTotalNumBytes];
220 fNumRows = fTable->rows();
221 if (fNumRows !=0)
222 {//If the file already existed, then we must load its data to memory before writing to it.
223 BinTable* bTable = dynamic_cast<BinTable*>(fTable);
224 if (!bTable)
225 {
226 fMess->Error("Table " + tableName + " in "+fileName+" could not be converted to a binary table.");
227 Close();
228 return false;
229 }
230 //read the table binary data.
231 vector<string> colName;
232 bTable->readData(true, colName);
233
234 //double check that the data was indeed read from the disk. Go through the fTable instead as colName is empty (yes, it is !)
235 const map<string, Column*> cMap = fTable->column();
236
237 for (map<string, Column*>::const_iterator cMapIt = cMap.begin(); cMapIt != cMap.end(); cMapIt++)
238 {
239 if (!cMapIt->second->isRead())
240 {
241 fMess->Error("Reading column " + cMapIt->first + " back from "+fileName+" failed.");
242 Close();
243 return false;
244 }
245 }
246 updating = true;
247 }
248
249 }
250 catch(CCfits::FitsException e)
251 {
252 ostringstream str;
253 str << "Opening or creating table " << tableName << " in " << fileName << ": " << e.message();
254 fMess->Error(str);
255 fTable = NULL;
256 Close();
257 return false;
258 }
259
260 if (!updating)
261 return WriteHeaderKeys();
262
263 return true;
264}
265// --------------------------------------------------------------------------
266//
267//!This writes a single header key in the currently open file.
268//!@param name the key
269//!@param value the key value
270//!@param a comment explaining the meaning of the key
271template <typename T>
272bool Fits::WriteSingleHeaderKey(const string &name, const T &value, const string &comment)
273{
274 try
275 {
276 fTable->addKey(name, value, comment);
277 }
278 catch (CCfits::FitsException e)
279 {
280 ostringstream str;
281 str << "Could not add header keys in file " << fFileName << " reason: " << e.message();
282 fMess->Error(str);
283 return false;
284 }
285 return true;
286}
287// --------------------------------------------------------------------------
288//
289//! This writes the standard header
290//
291bool Fits::WriteHeaderKeys()
292{
293 if (!fTable)
294 return false;
295
296 const Time now;
297 if (!WriteSingleHeaderKey("EXTREL", 1.0f, "Release Number")) return false;
298 if (!WriteSingleHeaderKey("TELESCOP", "FACT", "Telescope that acquired this data")) return false;
299 if (!WriteSingleHeaderKey("ORIGIN", "ISDC", "Institution that wrote the file")) return false;
300 if (!WriteSingleHeaderKey("CREATOR", "fadctrl", "Program that wrote this file (FACT++ datalogger)")) return false;
301 if (!WriteSingleHeaderKey("PACKAGE", PACKAGE_NAME, "Package name")) return false;
302 if (!WriteSingleHeaderKey("VERSION", PACKAGE_VERSION, "Package description")) return false;
303 if (!WriteSingleHeaderKey("COMPILED", __DATE__" "__TIME__, "Compile time")) return false;
304 if (!WriteSingleHeaderKey("REVISION", REVISION, "SVN revision")) return false;
305 if (!WriteSingleHeaderKey("DATE", now.Iso(), "File creation date")) return false;
306 if (!WriteSingleHeaderKey("NIGHT", now.NightAsInt(), "Night as int")) return false;
307 if (!WriteSingleHeaderKey("TIMESYS", "UTC", "Time systen")) return false;
308 if (!WriteSingleHeaderKey("TSTART", "", "Time of the first receied data")) return false;
309 if (!WriteSingleHeaderKey("TSTOP", "", "Time of the last receied data")) return false;
310 return true;
311}
312// --------------------------------------------------------------------------
313//
314//! This writes one line of data to the file.
315//! @param conv the converter corresponding to the service being logged
316//
317bool Fits::Write(const Converter &conv)
318{
319 //first copy the standard variables to the copy buffer
320 int shift = 0;
321 for (unsigned int i=0;i<fStandardNumBytes.size();i++)
322 {
323 const char *charSrc = reinterpret_cast<char*>(fStandardPointers[i]);
324 reverse_copy(charSrc, charSrc+fStandardNumBytes[i], &fCopyBuffer[shift]);
325 shift += fStandardNumBytes[i];
326 }
327
328 try
329 {
330 //now take care of the DIM data. The Converter is here for that purpose
331 conv.ToFits(&fCopyBuffer[shift], fDataPointer, fDataNumBytes);
332 }
333 catch (const runtime_error &e)
334 {
335 ostringstream str;
336 str << fFileName << ": " << e.what();
337 fMess->Error(str);
338 return false;
339 }
340
341 fTable->makeThisCurrent();
342
343 int status(0);
344 if (fits_insert_rows(fTable->fitsPointer(), fNumRows, 1, &status))
345 {
346 ostringstream str;
347 char text[30];
348 fits_get_errstatus(status, text);
349 str << "Inserting row into " << fFileName << ": " << text << " (fits_insert_rows, rc=" << status << ")";
350 fMess->Error(str);
351 Close();
352 return false;
353 }
354
355 fNumRows++;
356
357 //the first standard variable is the current MjD
358 if (fEndMjD==0)
359 {
360 const double doubleValue = *reinterpret_cast<double*>(fStandardPointers[0]);
361 WriteSingleHeaderKey("TSTART", Time(doubleValue).Iso(),
362 "Time of the first received data");
363 }
364 fEndMjD = *reinterpret_cast<double*>(fStandardPointers[0]);
365
366 //data copied to buffer, can write to fits
367 if (fits_write_tblbytes(fFile->fitsPointer(), fNumRows, 1, fTotalNumBytes, fCopyBuffer, &status))
368 {
369 char text[30];//max length of cfitsio error strings (from doc)
370 fits_get_errstatus(status, text);
371 ostringstream str;
372 str << "Writing FITS row " << fNumRows << " in " << fFileName << ": " << text << " (file_write_tblbytes, rc=" << status << ")";
373 fMess->Error(str);
374 Close();
375 return false;
376 }
377 return true;
378}
379
380// --------------------------------------------------------------------------
381//
382//! This closes the currently openned FITS file.
383//! it also updates the header to reflect the time of the last logged row
384//
385void Fits::Close()
386{
387//WARNING: do NOT delete the table as it gets deleted by the fFile object
388// if (fTable != NULL)
389// delete fTable;
390 if (fFile != NULL && fOwner)
391 {
392// CCfits::FITS* backupFits = fFile;
393// fFile = NULL;
394 WriteSingleHeaderKey("TSTOP", Time(fEndMjD).Iso(), "Time of the last receied data");
395 delete fFile;
396
397 fMess->Info("Closed: "+fFileName);
398 if (fNumOpenFitsFiles != NULL)
399 (*fNumOpenFitsFiles)--;
400 }
401 fFile = NULL;
402 if (fCopyBuffer != NULL)
403 delete [] fCopyBuffer;
404 fCopyBuffer = NULL;
405
406//fMess is the MessageImp part of the dataLogger itself. Thus it should NOT be deleted by the Fits files destructor.
407// if (fMess)
408// delete fMess;
409 fMess = NULL;
410}
411
412// --------------------------------------------------------------------------
413//! Returns the size on the disk of the Fits file being written.
414int Fits::GetWrittenSize() const
415{
416 if (!IsOpen())
417 return 0;
418
419 struct stat st;
420 if (stat(fFileName.c_str(), &st))
421 return 0;
422
423 return st.st_size;
424}
Note: See TracBrowser for help on using the repository browser.