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

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