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

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