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

Last change on this file since 20086 was 19700, checked in by tbretz, 5 years ago
Improved error output
File size: 16.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#include <sys/stat.h> //for file stats
23#include <cstdio> // for file rename
24#include <cerrno>
25
26#include <boost/algorithm/string/predicate.hpp>
27
28using namespace std;
29using namespace CCfits;
30
31// --------------------------------------------------------------------------
32//
33//! This gives a standard variable to the file writter.
34//! This variable should not be related to the DIM service being logged.
35//! @param desc the description of the variable to add
36//! @param dataFormat the FITS data format corresponding to the variable to add.
37//! @param dataPointer the memory location where the variable is stored
38//! @param numDataBytes the number of bytes taken by the variable
39//
40void Fits::AddStandardColumn(const Description& desc, const string &dataFormat, void* dataPointer, long unsigned int numDataBytes)
41{
42 //check if entry already exist
43 for (vector<Description>::const_iterator it=fStandardColDesc.begin(); it != fStandardColDesc.end(); it++)
44 if (it->name == desc.name)
45 return;
46
47 fStandardColDesc.push_back(desc);
48 fStandardFormats.push_back(dataFormat);
49 fStandardPointers.push_back(dataPointer);
50 fStandardNumBytes.push_back(numDataBytes);
51}
52
53// --------------------------------------------------------------------------
54//
55//! This gives the file writter access to the DIM data
56//! @param desc a vector containing the description of all the columns to log
57//! @param dataFormat a vector containing the FITS data format of all the columsn to log
58//! @param dataPointer the memory location where the DIM data starts
59//! @param numDataBytes the number of bytes taken by the DIM data.
60//! @param out Message object to use for propagating messages
61//
62void Fits::InitDataColumns(const vector<Description> &desc, const vector<string>& dataFormat, MessageImp* out)
63{
64 fDataFormats = dataFormat;
65
66 if ((desc.size() == 0) && (dataFormat.size() == 0))
67 {
68 fDataColDesc.clear();
69 return;
70 }
71
72 //we will copy this information here. It duplicates the data, which is not great,
73 // but it is the easiest way of doing it right now
74 if (
75 (desc.size() == dataFormat.size()+1) || // regular service
76 (desc.size() == dataFormat.size()+2) // service with ending string. skipped in fits
77 )
78 {
79 //services have one (or two) more description than columns. skip the first entry while copying as it describes the table itself.
80
81 fDataColDesc.clear();
82
83 fTableDesc = desc[0].comment;
84 if (fTableDesc.size() > 68)
85 {
86 out->Warn("Table description '" + fTableDesc + "' exceeds 68 chars... truncated.");
87 fTableDesc = fTableDesc.substr(0,68);
88 }
89
90 for (unsigned int i=0; i<dataFormat.size(); i++)
91 {
92 string name = desc[i+1].name;
93 if (name.length() > 68)
94 {
95 out->Warn("Column name '" + name + "' exceeds 68 chars... truncated.");
96 name = name.substr(0, 68);
97 }
98
99 string comment = desc[i+1].comment;
100 if (comment.length() + name.length() > 71)
101 {
102 out->Warn("Column '" + name + " / " + comment + "' exceeds 68 chars... truncated.");
103 comment = comment.substr(0,68);
104 }
105
106 string unit = desc[i+1].unit;
107 if (unit.length() > 68)
108 {
109 out->Warn("Unit '" + name + "' exceeds 68 chars... truncated.");
110 unit = comment.substr(0,68);
111 }
112
113 const size_t p = fDataFormats[i].find_last_of('B');
114 if ((boost::iequals(unit, "text") || boost::iequals(unit, "string")) && p!=string::npos)
115 {
116 out->Info("Column '" + name + "' detected to be an ascii string (FITS format 'A').");
117 fDataFormats[i].replace(p, 1, "A");
118 }
119
120 fDataColDesc.push_back(Description(name, comment, unit));
121 }
122 return;
123 }
124
125 {//if we arrived here, this means that the columns descriptions could not be parsed
126 ostringstream str;
127 str << "Expected " << dataFormat.size() << " descriptions of columns, got " << (int)(desc.size())-1 << " for service: ";
128 if (desc.size() > 0)
129 str << desc[0].name;
130 else
131 str << "<unknown>";
132
133 out->Warn(str.str());
134 }
135
136 fDataColDesc.clear();
137 // fDataColDesc.push_back(Description("service", "comment", "unit"));
138 for (unsigned int i=0;i<dataFormat.size();i++)
139 {
140 ostringstream stt;
141 stt << "Data" << i;
142 fDataColDesc.push_back(Description(stt.str(), "", ""));
143 }
144}
145
146// --------------------------------------------------------------------------
147//
148//! This opens the FITS file (after the columns have been passed)
149//! @param fileName the filename with complete or relative path of the file to open
150//! @param tableName the name of the table that will receive the logged data.
151//! @param file a pointer to an existing FITS file. If NULL, file will be opened and managed internally
152//! @param fitsCounter a pointer to the integer keeping track of the opened FITS files
153//! @param out a pointer to the MessageImp that should be used to log errors
154//! @param runNumber the runNumber for which this file is opened. 0 means nightly file.
155//
156bool Fits::Open(const string& fileName, const string& tableName, uint32_t* fitsCounter, MessageImp* out, int runNumber, FITS* file)
157{
158 fRunNumber = runNumber;
159 fMess = out;
160 fFileName = fileName;
161
162 if (fFile)
163 {
164 fMess->Error(fileName+" already open...");
165 return false;
166 }
167
168 fFile = new FitsFile(*fMess);
169
170 if (file == NULL)
171 {
172 if (!fFile->OpenFile(fileName, true))
173 return false;
174
175 fNumOpenFitsFiles = fitsCounter;
176 (*fNumOpenFitsFiles)++;
177 }
178 else
179 {
180 if (!fFile->SetFile(file))
181 return false;
182 }
183
184 //concatenate the standard and data columns
185 //do it the inneficient way first: its easier and faster to code.
186 for (unsigned int i=0;i<fStandardColDesc.size();i++)
187 {
188 fFile->AddColumn(fStandardColDesc[i].name, fStandardFormats[i],
189 fStandardColDesc[i].unit);
190 }
191
192 for (unsigned int i=0; i<fDataColDesc.size(); i++)
193 {
194 string name = fDataColDesc[i].name;
195 if (name.empty())
196 {
197 ostringstream stt;
198 stt << "Data" << i;
199 name = stt.str();
200 }
201//cout << endl << "#####adding column: " << name << " " << fDataFormats[i] << " " << fDataColDesc[i].unit << endl << endl;
202 fFile->AddColumn(name, fDataFormats[i], fDataColDesc[i].unit);
203 }
204
205 try
206 {
207 if (!fFile->OpenNewTable(tableName, 100))
208 {
209 Close();
210 //if the file already exist, then the column names must have changed
211 //let's move the file and try to open it again.
212 string fileNameWithoutFits = fFileName.substr(0, fileName.size()-4);
213 int counter = 0;
214 while (counter < 100)
215 {
216 ostringstream newFileName;
217 newFileName << fileNameWithoutFits << counter << ".fits";
218 ifstream testStream(newFileName.str().c_str());
219 if (!testStream)
220 {
221 if (rename(fFileName.c_str(), newFileName.str().c_str()))
222 return false;
223 break;
224 }
225 counter++;
226 }
227 if (counter == 100)
228 return false;
229 //now we open it again.
230 fFile = new FitsFile(*fMess);
231 if (file == NULL)
232 {
233 if (!fFile->OpenFile(fileName, true))
234 return false;
235 fNumOpenFitsFiles = fitsCounter;
236 (*fNumOpenFitsFiles)++;
237 }
238 else
239 {
240 if (!fFile->SetFile(file))
241 return false;
242 }
243 //YES, we must also redo that thing here...
244 //concatenate the standard and data columns
245 //do it the inneficient way first: its easier and faster to code.
246 for (unsigned int i=0;i<fStandardColDesc.size();i++)
247 {
248 fFile->AddColumn(fStandardColDesc[i].name, fStandardFormats[i],
249 fStandardColDesc[i].unit);
250 }
251
252 for (unsigned int i=0; i<fDataColDesc.size(); i++)
253 {
254 string name = fDataColDesc[i].name;
255 if (name.empty())
256 {
257 ostringstream stt;
258 stt << "Data" << i;
259 name = stt.str();
260 }
261 //cout << endl << "#####adding column: " << name << " " << fDataFormats[i] << " " << fDataColDesc[i].unit << endl << endl;
262 fFile->AddColumn(name, fDataFormats[i], fDataColDesc[i].unit);
263 }
264 if (!fFile->OpenNewTable(tableName, 100))
265 {
266 Close();
267 return false;
268 }
269 }
270
271 fCopyBuffer.resize(fFile->GetDataSize());
272//write header comments
273
274 ostringstream str;
275 for (unsigned int i=0;i<fStandardColDesc.size();i++)
276 {
277 str.str("");
278 str << "TTYPE" << i+1;
279 fFile->WriteKeyNT(str.str(), fStandardColDesc[i].name, fStandardColDesc[i].comment);
280 str.str("");
281 str << "TCOMM" << i+1;
282 fFile->WriteKeyNT(str.str(), fStandardColDesc[i].comment, "");
283 }
284
285 for (unsigned int i=0; i<fDataColDesc.size(); i++)
286 {
287 string name = fDataColDesc[i].name;
288 if (name.empty())
289 {
290 ostringstream stt;
291 stt << "Data" << i;
292 name = stt.str();
293 }
294 str.str("");
295 str << "TTYPE" << i+fStandardColDesc.size()+1;
296 fFile->WriteKeyNT(str.str(), name, fDataColDesc[i].comment);
297 str.str("");
298 str << "TCOMM" << i+fStandardColDesc.size()+1;
299 fFile->WriteKeyNT(str.str(), fDataColDesc[i].comment, "");
300 }
301
302 fFile->WriteKeyNT("COMMENT", fTableDesc, "");
303
304 if (fFile->GetNumRows() == 0)
305 {//if new file, then write header keys -> reset fEndMjD used as flag
306 fEndMjD = 0;
307 }
308 else
309 {//file is beingn updated. Prevent from overriding header keys
310 fEndMjD = Time().Mjd();
311 }
312
313 return fFile->GetNumRows()==0 ? WriteHeaderKeys() : true;
314 }
315 catch (const CCfits::FitsException &e)
316 {
317 cout << "Exception !" << endl;
318 fMess->Error("Opening or creating table '"+tableName+"' in '"+fileName+"': "+e.message());
319
320 fFile->fTable = NULL;
321 Close();
322 return false;
323 }
324}
325
326// --------------------------------------------------------------------------
327//
328//! This writes the standard header
329//
330bool Fits::WriteHeaderKeys()
331{
332 if (!fFile->fTable)
333 return false;
334
335 if (!fFile->WriteDefaultKeys("datalogger"))
336 return false;
337
338 if (!fFile->WriteKeyNT("TSTARTI", 0, "Time when first event received (integral part)") ||
339 !fFile->WriteKeyNT("TSTARTF", 0, "Time when first event received (fractional part)") ||
340 !fFile->WriteKeyNT("TSTOPI", 0, "Time when last event received (integral part)") ||
341 !fFile->WriteKeyNT("TSTOPF", 0, "Time when last event received (fractional part)") ||
342 !fFile->WriteKeyNT("DATE-OBS", 0, "Time when first event received") ||
343 !fFile->WriteKeyNT("DATE-END", 0, "Time when last event received") ||
344 !fFile->WriteKeyNT("RUNID", fRunNumber, "Run number. 0 means not run file"))
345 return false;
346
347 return true;
348}
349void Fits::MoveFileToCorruptedFile()
350{
351 ostringstream corruptName;
352 struct stat st;
353 int append = 0;
354 corruptName << fFileName << "corrupt" << append;
355 while (!stat(corruptName.str().c_str(), &st))
356 {
357 append++;
358 corruptName.str("");
359 corruptName << fFileName << "corrupt" << append;
360 }
361 if (rename(fFileName.c_str(), corruptName.str().c_str()) != 0)
362 {
363 ostringstream str;
364 str << "rename() failed for '" << fFileName << "': " << strerror(errno) << " [errno=" << errno << "]";
365 fMess->Error(str);
366 return;
367 }
368
369 fMess->Message("Renamed file " + fFileName + " to " + corruptName.str());
370
371}
372// --------------------------------------------------------------------------
373//
374//! This writes one line of data to the file.
375//! @param conv the converter corresponding to the service being logged
376//
377bool Fits::Write(const Converter &conv, const void* data)
378{
379 //first copy the standard variables to the copy buffer
380 int shift = 0;
381 for (unsigned int i=0;i<fStandardNumBytes.size();i++)
382 {
383 const char *charSrc = reinterpret_cast<char*>(fStandardPointers[i]);
384 reverse_copy(charSrc, charSrc+fStandardNumBytes[i], fCopyBuffer.data()+shift);
385 shift += fStandardNumBytes[i];
386 }
387 try
388 {
389 //now take care of the DIM data. The Converter is here for that purpose
390 conv.ToFits(fCopyBuffer.data()+shift, data, fCopyBuffer.size()-shift);
391 }
392 catch (const runtime_error &e)
393 {
394 ostringstream str;
395 str << fFile->GetName() << ": " << e.what();
396 fMess->Error(str);
397 return false;
398 }
399
400 // This is not necessary, is it?
401 // fFile->fTable->makeThisCurrent();
402 if (!fFile->AddRow())
403 {
404 Close();
405 MoveFileToCorruptedFile();
406 return false;
407 }
408 if (!fFile->WriteData(fCopyBuffer))
409 {
410 Close();
411 return false;
412 }
413 const double tm = *reinterpret_cast<double*>(fStandardPointers[0]);
414
415 //the first standard variable is the current MjD
416 if (fEndMjD==0)
417 {
418 // FIXME: Check error?
419 fFile->WriteKeyNT("TSTARTI", uint32_t(floor(tm)), "Time when first event received (integral part)");
420 fFile->WriteKeyNT("TSTARTF", fmod(tm, 1), "Time when first event received (fractional part)");
421 fFile->WriteKeyNT("TSTOPI", uint32_t(floor(fEndMjD)), "Time when last event received (integral part)");
422 fFile->WriteKeyNT("TSTOPF", fmod(fEndMjD, 1), "Time when last event received (fractional part)");
423
424 fFile->WriteKeyNT("DATE-OBS", Time(tm+40587).Iso(),
425 "Time when first event received");
426
427 fFile->WriteKeyNT("DATE-END", Time(fEndMjD+40587).Iso(),
428 "Time when last event received");
429 }
430
431 fEndMjD = tm;
432
433 return true;
434}
435
436// --------------------------------------------------------------------------
437//
438//! This closes the currently openned FITS file.
439//! it also updates the header to reflect the time of the last logged row
440//
441void Fits::Close()
442{
443 if (!fFile)
444 return;
445 if (fFile->IsOpen() && fFile->IsOwner())
446 {
447 // FIMXE: Check for error? (It is allowed that fFile is NULL)
448 fFile->WriteKeyNT("TSTOPI", uint32_t(floor(fEndMjD)), "Time when last event received (integral part)");
449 fFile->WriteKeyNT("TSTOPF", fmod(fEndMjD, 1), "Time when last event received (fractional part)");
450
451 fFile->WriteKeyNT("DATE-END", Time(fEndMjD+40587).Iso(),
452 "Time when last event received");
453 }
454 if (fFile->IsOwner())
455 {
456 if (fNumOpenFitsFiles != NULL)
457 (*fNumOpenFitsFiles)--;
458 }
459 const string name = fFile->GetName();
460 delete fFile;
461 fFile = NULL;
462 fMess->Info("Closed: "+name);
463// fMess = NULL;
464}
465
466void Fits::Flush()
467{
468 if (!fFile)
469 return;
470
471 fFile->Flush();
472}
473// --------------------------------------------------------------------------
474//! Returns the size on the disk of the Fits file being written.
475int Fits::GetWrittenSize() const
476{
477 if (!IsOpen())
478 return 0;
479
480 struct stat st;
481 if (stat(fFile->GetName().c_str(), &st))
482 return 0;
483
484 return st.st_size;
485}
486
487/*
488 To be done:
489 - Check the check for column names in opennewtable
490 - If Open return false we end in an infinite loop (at least if
491 the dynamic cats to Bintable fails.
492
493*/
Note: See TracBrowser for help on using the repository browser.