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

Last change on this file since 11408 was 11405, checked in by lyard, 14 years ago
various improvements of the datalogger
File size: 14.4 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 //As requested by Roland, the reference MjD is 0.0
261 fRefMjD = 0;//* static_cast<double*>(fStandardPointers[0]);
262 if (!updating)
263 return WriteHeaderKeys();
264
265 return true;
266}
267// --------------------------------------------------------------------------
268//
269//!This writes a single header key in the currently open file.
270//!@param name the key
271//!@param value the key value
272//!@param a comment explaining the meaning of the key
273template <typename T>
274bool Fits::WriteSingleHeaderKey(const string &name, const T &value, const string &comment)
275{
276 try
277 {
278 fTable->addKey(name, value, comment);
279 }
280 catch (CCfits::FitsException e)
281 {
282 ostringstream str;
283 str << "Could not add header keys in file " << fFileName << " reason: " << e.message();
284 fMess->Error(str);
285 return false;
286 }
287 return true;
288}
289// --------------------------------------------------------------------------
290//
291//! This writes the standard header
292//
293bool Fits::WriteHeaderKeys()
294{
295 if (!fTable)
296 return false;
297
298 string stringValue = Time().GetAsStr();
299 stringValue[10]= 'T';
300
301 if (!WriteSingleHeaderKey("EXTREL", 1.0f, "Release Number")) return false;
302 if (!WriteSingleHeaderKey("TELESCOP", "FACT", "Telescope that acquired this data")) return false;
303 if (!WriteSingleHeaderKey("ORIGIN", "ISDC", "Institution that wrote the file")) return false;
304 if (!WriteSingleHeaderKey("CREATOR", "FACT++ DataLogger", "Program that wrote this file")) return false;
305 if (!WriteSingleHeaderKey("DATE", stringValue, "File creation data")) return false;
306 if (!WriteSingleHeaderKey("TIMESYS", "TT", "Time frame system")) return false;
307 if (!WriteSingleHeaderKey("TIMEUNIT", "d", "Time unit")) return false;
308 if (!WriteSingleHeaderKey("TIMEREF", "UTC", "Time reference frame")) return false;
309 if (!WriteSingleHeaderKey("MJDREF", fRefMjD, "Modified Julian Date of origin")) return false;
310 if (!WriteSingleHeaderKey("TSTOP", fEndMjD, "Time of the last receied data")) return false;
311 return true;
312}
313// --------------------------------------------------------------------------
314//
315//! This writes one line of data to the file.
316//! @param conv the converter corresponding to the service being logged
317//
318bool Fits::Write(Converter* conv)
319{
320 //first copy the standard variables to the copy buffer
321 int shift = 0;
322 for (unsigned int i=0;i<fStandardNumBytes.size();i++)
323 {
324 const char *charSrc = reinterpret_cast<char*>(fStandardPointers[i]);
325 reverse_copy(charSrc, charSrc+fStandardNumBytes[i], &fCopyBuffer[shift]);
326 shift += fStandardNumBytes[i];
327 }
328
329 try
330 {
331 //now take care of the DIM data. The Converter is here for that purpose
332 conv->ToFits(&fCopyBuffer[shift], fDataPointer, fDataNumBytes);
333 }
334 catch (const runtime_error &e)
335 {
336 ostringstream str;
337 str << fFileName << ": " << e.what();
338 fMess->Error(str);
339 return false;
340 }
341
342 fTable->makeThisCurrent();
343
344 int status(0);
345 if (fits_insert_rows(fTable->fitsPointer(), fNumRows, 1, &status))
346 {
347 ostringstream str;
348 char text[30];
349 fits_get_errstatus(status, text);
350 str << "Inserting row into " << fFileName << ": " << text << " (fits_insert_rows, rc=" << status << ")";
351 fMess->Error(str);
352 Close();
353 return false;
354 }
355
356 fNumRows++;
357
358 //the first standard variable is the current MjD
359 if (fEndMjD==0)
360 {
361 const double doubleValue = *reinterpret_cast<double*>(fStandardPointers[0]);
362 WriteSingleHeaderKey("TSTART", doubleValue,
363 "Time of the first received data");
364 }
365 fEndMjD = *reinterpret_cast<double*>(fStandardPointers[0]);
366
367 //data copied to buffer, can write to fits
368 if (fits_write_tblbytes(fFile->fitsPointer(), fNumRows, 1, fTotalNumBytes, fCopyBuffer, &status))
369 {
370 char text[30];//max length of cfitsio error strings (from doc)
371 fits_get_errstatus(status, text);
372 ostringstream str;
373 str << "Writing FITS row " << fNumRows << " in " << fFileName << ": " << text << " (file_write_tblbytes, rc=" << status << ")";
374 fMess->Error(str);
375 Close();
376 return false;
377 }
378 return true;
379}
380
381// --------------------------------------------------------------------------
382//
383//! This closes the currently openned FITS file.
384//! it also updates the header to reflect the time of the last logged row
385//
386void Fits::Close()
387{
388//WARNING: do NOT delete the table as it gets deleted by the fFile object
389// if (fTable != NULL)
390// delete fTable;
391 if (fFile != NULL && fOwner)
392 {
393// CCfits::FITS* backupFits = fFile;
394// fFile = NULL;
395 WriteSingleHeaderKey("TSTOP", fEndMjD, "Time of the last receied data");
396 delete fFile;
397
398 fMess->Info("Closed: "+fFileName);
399 if (fNumOpenFitsFiles != NULL)
400 (*fNumOpenFitsFiles)--;
401 }
402 fFile = NULL;
403 if (fCopyBuffer != NULL)
404 delete [] fCopyBuffer;
405 fCopyBuffer = NULL;
406
407//fMess is the MessageImp part of the dataLogger itself. Thus it should NOT be deleted by the Fits files destructor.
408// if (fMess)
409// delete fMess;
410 fMess = NULL;
411}
412
413// --------------------------------------------------------------------------
414//! Returns the size on the disk of the Fits file being written.
415int Fits::GetWrittenSize() const
416{
417 if (!IsOpen())
418 return 0;
419
420 struct stat st;
421 if (stat(fFileName.c_str(), &st))
422 return 0;
423
424 return st.st_size;
425}
Note: See TracBrowser for help on using the repository browser.