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

Last change on this file since 14560 was 14238, checked in by tbretz, 12 years ago
Removed the obsolete data pointer argument from InitDataColumns
File size: 14.1 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 " << 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("File 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 return false;
211 }
212
213 fCopyBuffer.resize(fFile->GetDataSize());
214//write header comments
215 ostringstream str;
216 for (unsigned int i=0;i<fStandardColDesc.size();i++)
217 {
218 str.str("");
219 str << "TTYPE" << i+1;
220 fFile->WriteKeyNT(str.str(), fStandardColDesc[i].name, fStandardColDesc[i].comment);
221 str.str("");
222 str << "TCOMM" << i+1;
223 fFile->WriteKeyNT(str.str(), fStandardColDesc[i].comment, "");
224 }
225
226 for (unsigned int i=0; i<fDataColDesc.size(); i++)
227 {
228 string name = fDataColDesc[i].name;
229 if (name.empty())
230 {
231 ostringstream stt;
232 stt << "Data" << i;
233 name = stt.str();
234 }
235 str.str("");
236 str << "TTYPE" << i+fStandardColDesc.size()+1;
237 fFile->WriteKeyNT(str.str(), name, fDataColDesc[i].comment);
238 str.str("");
239 str << "TCOMM" << i+fStandardColDesc.size()+1;
240 fFile->WriteKeyNT(str.str(), fDataColDesc[i].comment, "");
241 }
242 fFile->WriteKeyNT("COMMENT", fTableDesc, "");
243
244 if (fFile->GetNumRows() == 0)
245 {//if new file, then write header keys -> reset fEndMjD used as flag
246 fEndMjD = 0;
247 }
248 else
249 {//file is beingn updated. Prevent from overriding header keys
250 fEndMjD = Time().Mjd();
251 }
252 return fFile->GetNumRows()==0 ? WriteHeaderKeys() : true;
253 }
254 catch (const CCfits::FitsException &e)
255 {
256 fMess->Error("Opening or creating table '"+tableName+"' in '"+fileName+"': "+e.message());
257
258 fFile->fTable = NULL;
259 Close();
260 return false;
261 }
262}
263
264// --------------------------------------------------------------------------
265//
266//! This writes the standard header
267//
268bool Fits::WriteHeaderKeys()
269{
270 if (!fFile->fTable)
271 return false;
272
273 if (!fFile->WriteDefaultKeys("datalogger"))
274 return false;
275
276 if (!fFile->WriteKeyNT("TSTARTI", 0, "Time when first event received (integral part)") ||
277 !fFile->WriteKeyNT("TSTARTF", 0, "Time when first event received (fractional part)") ||
278 !fFile->WriteKeyNT("TSTOPI", 0, "Time when last event received (integral part)") ||
279 !fFile->WriteKeyNT("TSTOPF", 0, "Time when last event received (fractional part)") ||
280 !fFile->WriteKeyNT("DATE-OBS", 0, "Time when first event received") ||
281 !fFile->WriteKeyNT("DATE-END", 0, "Time when last event received") ||
282 !fFile->WriteKeyNT("RUNID", fRunNumber, "Run number. 0 means not run file"))
283 return false;
284
285 return true;
286}
287void Fits::MoveFileToCorruptedFile()
288{
289 ostringstream corruptName;
290 struct stat st;
291 int append = 0;
292 corruptName << fFileName << "corrupt" << append;
293 while (!stat(corruptName.str().c_str(), &st))
294 {
295 append++;
296 corruptName.str("");
297 corruptName << fFileName << "corrupt" << append;
298 }
299 if (rename(fFileName.c_str(), corruptName.str().c_str()) != 0)
300 {
301 ostringstream str;
302 str << "rename() failed for '" << fFileName << "': " << strerror(errno) << " [errno=" << errno << "]";
303 fMess->Error(str);
304 return;
305 }
306
307 fMess->Message("Renamed file " + fFileName + " to " + corruptName.str());
308
309}
310// --------------------------------------------------------------------------
311//
312//! This writes one line of data to the file.
313//! @param conv the converter corresponding to the service being logged
314//
315bool Fits::Write(const Converter &conv, const void* data)
316{
317 //first copy the standard variables to the copy buffer
318 int shift = 0;
319 for (unsigned int i=0;i<fStandardNumBytes.size();i++)
320 {
321 const char *charSrc = reinterpret_cast<char*>(fStandardPointers[i]);
322 reverse_copy(charSrc, charSrc+fStandardNumBytes[i], fCopyBuffer.data()+shift);
323 shift += fStandardNumBytes[i];
324 }
325
326 try
327 {
328 //now take care of the DIM data. The Converter is here for that purpose
329 conv.ToFits(fCopyBuffer.data()+shift, data, fCopyBuffer.size()-shift);
330 }
331 catch (const runtime_error &e)
332 {
333 ostringstream str;
334 str << fFile->GetName() << ": " << e.what();
335 fMess->Error(str);
336 return false;
337 }
338
339 // This is not necessary, is it?
340 // fFile->fTable->makeThisCurrent();
341
342 if (!fFile->AddRow())
343 {
344 Close();
345 MoveFileToCorruptedFile();
346 return false;
347 }
348
349 if (!fFile->WriteData(fCopyBuffer))
350 {
351 Close();
352 return false;
353 }
354
355 const double tm = *reinterpret_cast<double*>(fStandardPointers[0]);
356
357 //the first standard variable is the current MjD
358 if (fEndMjD==0)
359 {
360 // FIXME: Check error?
361 fFile->WriteKeyNT("TSTARTI", uint32_t(floor(tm)), "Time when first event received (integral part)");
362 fFile->WriteKeyNT("TSTARTF", fmod(tm, 1), "Time when first event received (fractional part)");
363 fFile->WriteKeyNT("TSTOPI", uint32_t(floor(fEndMjD)), "Time when last event received (integral part)");
364 fFile->WriteKeyNT("TSTOPF", fmod(fEndMjD, 1), "Time when last event received (fractional part)");
365
366 fFile->WriteKeyNT("DATE-OBS", Time(tm+40587).Iso(),
367 "Time when first event received");
368
369 fFile->WriteKeyNT("DATE-END", Time(fEndMjD+40587).Iso(),
370 "Time when last event received");
371 }
372
373 fEndMjD = tm;
374
375 return true;
376}
377
378// --------------------------------------------------------------------------
379//
380//! This closes the currently openned FITS file.
381//! it also updates the header to reflect the time of the last logged row
382//
383void Fits::Close()
384{
385 if (!fFile)
386 return;
387
388 if (fFile->IsOpen() && fFile->IsOwner())
389 {
390 // FIMXE: Check for error? (It is allowed that fFile is NULL)
391 fFile->WriteKeyNT("TSTOPI", uint32_t(floor(fEndMjD)), "Time when last event received (integral part)");
392 fFile->WriteKeyNT("TSTOPF", fmod(fEndMjD, 1), "Time when last event received (fractional part)");
393
394 fFile->WriteKeyNT("DATE-END", Time(fEndMjD+40587).Iso(),
395 "Time when last event received");
396 }
397
398 if (fFile->IsOwner())
399 {
400 if (fNumOpenFitsFiles != NULL)
401 (*fNumOpenFitsFiles)--;
402 }
403
404 const string name = fFile->GetName();
405
406 delete fFile;
407 fFile = NULL;
408
409 fMess->Info("Closed: "+name);
410
411// fMess = NULL;
412}
413
414void Fits::Flush()
415{
416 if (!fFile)
417 return;
418
419 fFile->Flush();
420}
421// --------------------------------------------------------------------------
422//! Returns the size on the disk of the Fits file being written.
423int Fits::GetWrittenSize() const
424{
425 if (!IsOpen())
426 return 0;
427
428 struct stat st;
429 if (stat(fFile->GetName().c_str(), &st))
430 return 0;
431
432 return st.st_size;
433}
434
435/*
436 To be done:
437 - Check the check for column names in opennewtable
438 - If Open return false we end in an infinite loop (at least if
439 the dynamic cats to Bintable fails.
440
441*/
Note: See TracBrowser for help on using the repository browser.