source: trunk/Mars/mbase/fits.h@ 11711

Last change on this file since 11711 was 11711, checked in by tbretz, 13 years ago
Fixed a bug when the END tag was the first in a block
File size: 18.6 KB
Line 
1#ifndef MARS_fits
2#define MARS_fits
3
4#include <stdint.h>
5
6#include <map>
7#include <string>
8#include <sstream>
9#include <algorithm>
10
11#ifdef __EXCEPTIONS
12#include <stdexcept>
13#endif
14
15#ifdef __CINT__
16#define off_t size_t
17#endif
18
19#ifndef __MARS__
20#include <iomanip>
21#define gLog cerr
22#define err ""
23#define all ""
24#else
25#include "MLog.h"
26#include "MLogManip.h"
27#endif
28
29#ifdef HAVE_ZLIB
30#include "izstream.h"
31#else
32#include <fstream>
33#define izstream ifstream
34#warning Support for zipped FITS files disabled.
35#endif
36
37namespace std
38{
39
40class fits : public izstream
41{
42public:
43 struct Entry
44 {
45 char type;
46 string value;
47 string comment;
48
49 template<typename T>
50 T Get() const
51 {
52 T t;
53
54 istringstream str(value);
55 str >> t;
56
57 return t;
58 }
59 };
60
61 struct Table
62 {
63 off_t offset;
64
65 string name;
66 size_t bytes_per_row;
67 size_t num_rows;
68 size_t num_cols;
69
70 struct Column
71 {
72 size_t offset;
73 size_t num;
74 size_t size;
75 char type;
76 };
77
78 typedef map<string, Entry> Keys;
79 typedef map<string, Column> Columns;
80
81 Columns cols;
82 Keys keys;
83
84 string Trim(const string &str, char c=' ') const
85 {
86 // Trim Both leading and trailing spaces
87 const size_t pstart = str.find_first_not_of(c); // Find the first character position after excluding leading blank spaces
88 const size_t pend = str.find_last_not_of(c); // Find the first character position from reverse af
89
90 // if all spaces or empty return an empty string
91 if (string::npos==pstart || string::npos==pend)
92 return string();
93
94 return str.substr(pstart, pend-pstart+1);
95 }
96
97 bool Check(const string &key, char type, const string &value="") const
98 {
99 const Keys::const_iterator it = keys.find(key);
100 if (it==keys.end())
101 {
102 ostringstream str;
103 str << "Key '" << key << "' not found.";
104#ifdef __EXCEPTIONS
105 throw runtime_error(str.str());
106#else
107 gLog << err << "ERROR - " << str.str() << endl;
108 return false;
109#endif
110 }
111
112 if (it->second.type!=type)
113 {
114 ostringstream str;
115 str << "Wrong type for key '" << key << "': expected " << type << ", found " << it->second.type << ".";
116#ifdef __EXCEPTIONS
117 throw runtime_error(str.str());
118#else
119 gLog << err << "ERROR - " << str.str() << endl;
120 return false;
121#endif
122 }
123
124 if (!value.empty() && it->second.value!=value)
125 {
126 ostringstream str;
127 str << "Wrong value for key '" << key << "': expected " << value << ", found " << it->second.value << ".";
128#ifdef __EXCEPTIONS
129 throw runtime_error(str.str());
130#else
131 gLog << err << "ERROR - " << str.str() << endl;
132 return false;
133#endif
134 }
135
136 return true;
137 }
138
139 Keys ParseBlock(const vector<string> &vec) const
140 {
141 map<string,Entry> rc;
142
143 for (unsigned int i=0; i<vec.size(); i++)
144 {
145 const string key = Trim(vec[i].substr(0,8));
146 // Keywords without a value, like COMMENT / HISTORY
147 if (vec[i].substr(8,2)!="= ")
148 continue;
149
150 char type = 0;
151
152 string com;
153 string val = Trim(vec[i].substr(10));
154 if (val[0]=='\'')
155 {
156 // First skip all '' in the string
157 size_t p = 1;
158 while (1)
159 {
160 const size_t pp = val.find_first_of('\'', p);
161 if (pp==string::npos)
162 break;
163
164 p = val[pp+1]=='\'' ? pp+2 : pp+1;
165 }
166
167 // Now find the comment
168 const size_t ppp = val.find_first_of('/', p);
169
170 // Set value, comment and type
171 com = ppp==string::npos ? "" : Trim(val.substr(ppp+1));
172 val = Trim(val.substr(1, p-2));
173 type = 'T';
174 }
175 else
176 {
177 const size_t p = val.find_first_of('/');
178
179 com = Trim(val.substr(p+2));
180 val = Trim(val.substr(0, p));
181
182 if (val.empty() || val.find_first_of('T')!=string::npos || val.find_first_of('F')!=string::npos)
183 type = 'B';
184 else
185 type = val.find_last_of('.')==string::npos ? 'I' : 'F';
186 }
187
188 const Entry e = { type, val, com };
189 rc[key] = e;
190 }
191
192 return rc;
193 }
194
195 Table() : offset(0) { }
196 Table(const vector<string> &vec, off_t off) :
197 offset(off), keys(ParseBlock(vec))
198 {
199 if (!Check("XTENSION", 'T', "BINTABLE") ||
200 !Check("NAXIS", 'I', "2") ||
201 !Check("BITPIX", 'I', "8") ||
202 !Check("PCOUNT", 'I', "0") ||
203 !Check("GCOUNT", 'I', "1") ||
204 !Check("EXTNAME", 'T') ||
205 !Check("NAXIS1", 'I') ||
206 !Check("NAXIS2", 'I') ||
207 !Check("TFIELDS", 'I'))
208 return;
209
210 bytes_per_row = Get<size_t>("NAXIS1");
211 num_rows = Get<size_t>("NAXIS2");
212 num_cols = Get<size_t>("TFIELDS");
213
214 size_t bytes = 0;
215 for (size_t i=1; i<=num_cols; i++)
216 {
217 ostringstream num;
218 num << i;
219
220 if (!Check("TTYPE"+num.str(), 'T') ||
221 !Check("TFORM"+num.str(), 'T'))
222 return;
223
224 const string id = Get<string>("TTYPE"+num.str());
225 const string fmt = Get<string>("TFORM"+num.str());
226
227 istringstream sin(fmt);
228 int n = 0;
229 sin >> n;
230 if (!sin)
231 n = 1;
232
233 const char type = fmt[fmt.length()-1];
234
235 size_t size = 0;
236 switch (type)
237 {
238 // We could use negative values to mark floats
239 // otheriwse we could just cast them to int64_t?
240 case 'L': size = 1; break; // logical
241 // case 'X': size = n; break; // bits (n=number of bytes needed to contain all bits)
242 case 'B': size = 1; break; // byte
243 case 'I': size = 2; break; // short
244 case 'J': size = 4; break; // int
245 case 'K': size = 8; break; // long long
246 case 'E': size = 4; break; // float
247 case 'D': size = 8; break; // double
248 // case 'C': size = 8; break; // complex float
249 // case 'M': size = 16; break; // complex double
250 // case 'P': size = 8; break; // array descriptor (32bit)
251 // case 'Q': size = 16; break; // array descriptor (64bit)
252 default:
253 {
254 ostringstream str;
255 str << "FITS format TFORM='" << fmt << "' not yet supported.";
256#ifdef __EXCEPTIONS
257 throw runtime_error(str.str());
258#else
259 gLog << err << "ERROR - " << str.str() << endl;
260 return;
261#endif
262 }
263 }
264
265 const Table::Column col = { bytes, n, size, type };
266
267 cols[id] = col;
268 bytes += n*size;
269 }
270
271 if (bytes!=bytes_per_row)
272 {
273#ifdef __EXCEPTIONS
274 throw runtime_error("Column size mismatch");
275#else
276 gLog << err << "ERROR - Column size mismatch" << endl;
277 return;
278#endif
279 }
280
281 name = Get<string>("EXTNAME");
282 }
283
284 void PrintKeys() const
285 {
286 for (Keys::const_iterator it=keys.begin(); it!=keys.end(); it++)
287 gLog << all << setw(2) << it->second.type << '|' << it->first << '=' << it->second.value << '/' << it->second.comment << '|' << endl;
288 }
289
290 void PrintColumns() const
291 {
292 typedef map<pair<size_t, string>, Column> Sorted;
293
294 Sorted sorted;
295
296 for (Columns::const_iterator it=cols.begin(); it!=cols.end(); it++)
297 sorted[make_pair(it->second.offset, it->first)] = it->second;
298
299 for (Sorted::const_iterator it=sorted.begin(); it!=sorted.end(); it++)
300 gLog << all << setw(6) << it->second.offset << "| " << it->first.second << " [" << it->second.type << '=' << it->second.num << 'x' << it->second.size << "byte]" << endl;
301 }
302
303 operator bool() const { return !name.empty(); }
304
305 bool HasKey(const string &key) const
306 {
307 return keys.find(key)!=keys.end();
308 }
309
310 // Values of keys are always signed
311 template<typename T>
312 T Get(const string &key) const
313 {
314 const map<string,Entry>::const_iterator it = keys.find(key);
315 if (it==keys.end())
316 {
317 ostringstream str;
318 str << "Key '" << key << "' not found." << endl;
319#ifdef __EXCEPTIONS
320 throw runtime_error(str.str());
321#else
322 gLog << err << "ERROR - " << str.str() << endl;
323 return 0;
324#endif
325 }
326 return it->second.Get<T>();
327 }
328
329 size_t GetN(const string &key) const
330 {
331 const Columns::const_iterator it = cols.find(key);
332 return it==cols.end() ? 0 : it->second.num;
333 }
334 };
335
336private:
337 Table fTable;
338
339 typedef pair<void*, Table::Column> Address;
340 typedef vector<Address> Addresses;
341 //map<void*, Table::Column> fAddresses;
342 Addresses fAddresses;
343
344 vector<char> fBufferRow;
345 vector<char> fBufferDat;
346
347 size_t fRow;
348
349 vector<string> ReadBlock(vector<string> &vec)
350 {
351 bool endtag = false;
352 for (int i=0; i<36; i++)
353 {
354 char c[81];
355 c[80] = 0;
356 read(c, 80);
357 if (!good())
358 break;
359
360 if (c[0]==0)
361 return vector<string>();
362
363 string str(c);
364
365// if (!str.empty())
366// cout << setw(2) << i << "|" << str << "|" << (endtag?'-':'+') << endl;
367
368 if (str=="END ")
369 {
370 endtag = true;
371
372 // Make sure that no empty vector is returned
373 if (vec.size()%36==0)
374 vec.push_back(string("END = '' / "));
375 }
376
377 if (endtag)
378 continue;
379
380 vec.push_back(str);
381 }
382
383 return vec;
384 }
385
386 string Compile(const string &key, int16_t i=-1)
387 {
388 if (i<0)
389 return key;
390
391 ostringstream str;
392 str << key << i;
393 return str.str();
394 }
395
396public:
397 fits(const string &fname) : izstream(fname.c_str())
398 {
399 char simple[10];
400 read(simple, 10);
401 if (!good())
402 return;
403
404 if (memcmp(simple, "SIMPLE = ", 10))
405 {
406 clear(rdstate()|ios::badbit);
407#ifdef __EXCEPTIONS
408 throw runtime_error("File is not a FITS file.");
409#else
410 gLog << err << "ERROR - File is not a FITS file." << endl;
411 return;
412#endif
413 }
414
415 seekg(0);
416
417 while (good())
418 {
419 vector<string> block;
420 while (1)
421 {
422 // FIXME: Set limit on memory consumption
423 ReadBlock(block);
424 if (!good())
425 {
426 clear(rdstate()|ios::badbit);
427#ifdef __EXCEPTIONS
428 throw runtime_error("FITS file corrupted.");
429#else
430 gLog << err << "ERROR - FITS file corrupted." << endl;
431 return;
432#endif
433 }
434
435 if (block.size()%36)
436 break;
437 }
438
439 if (block.size()==0)
440 break;
441
442 if (block[0].substr(0, 9)=="SIMPLE =")
443 continue;
444
445 if (block[0].substr(0, 9)=="XTENSION=")
446 {
447 // FIXME: Check for table name
448
449 fTable = Table(block, tellg());
450 fRow = (size_t)-1;
451
452 //fTable.PrintKeys();
453
454 if (!fTable)
455 {
456 clear(rdstate()|ios::badbit);
457 return;
458 }
459
460 fBufferRow.resize(fTable.bytes_per_row);
461 fBufferDat.resize(fTable.bytes_per_row);
462
463 /*
464 // Next table should start at:
465 const size_t size = fTable.bytes_per_row*fTable.num_rows;
466 const size_t blks = size/(36*80);
467 const size_t rest = size%(36*80);
468
469 seekg((blks+(rest>0?1:0))*(36*80), ios::cur);
470 if (!good())
471 gLog << err << "File seems to be incomplete (less data than expected from header)." << endl;
472
473 fRow = fTable.num_rows;
474 */
475
476 break;
477 }
478 }
479 }
480
481 void ReadRow(size_t row)
482 {
483 // if (row!=fRow+1) // Fast seeking is ensured by izstream
484 seekg(fTable.offset+row*fTable.bytes_per_row);
485
486 fRow = row;
487
488 read(fBufferRow.data(), fBufferRow.size());
489 //fin.clear(fin.rdstate()&~ios::eofbit);
490 }
491
492 template<size_t N>
493 void revcpy(char *dest, const char *src, int num)
494 {
495 const char *pend = src + num*N;
496 for (const char *ptr = src; ptr<pend; ptr+=N, dest+=N)
497 reverse_copy(ptr, ptr+N, dest);
498 }
499
500 bool GetRow(size_t row)
501 {
502 ReadRow(row);
503 if (!good())
504 return good();
505
506 for (Addresses::const_iterator it=fAddresses.begin(); it!=fAddresses.end(); it++)
507 {
508 const Table::Column &c = it->second;
509
510 const char *src = fBufferRow.data() + c.offset;
511 char *dest = reinterpret_cast<char*>(it->first);
512
513 // Let the compiler do some optimization by
514 // knowing the we only have 1, 2, 4 and 8
515 switch (c.size)
516 {
517 case 1: memcpy (dest, src, c.num*c.size); break;
518 case 2: revcpy<2>(dest, src, c.num); break;
519 case 4: revcpy<4>(dest, src, c.num); break;
520 case 8: revcpy<8>(dest, src, c.num); break;
521 }
522 }
523
524 return good();
525 }
526
527 bool GetNextRow()
528 {
529 return GetRow(fRow+1);
530 }
531
532 bool SkipNextRow()
533 {
534 seekg(fTable.offset+(++fRow)*fTable.bytes_per_row);
535 return good();
536 }
537
538 static bool Compare(const Address &p1, const Address &p2)
539 {
540 return p1.first>p2.first;
541 }
542
543 template<typename T>
544 bool SetPtrAddress(const string &name, T *ptr, size_t cnt)
545 {
546 if (fTable.cols.count(name)==0)
547 {
548 ostringstream str;
549 str <<"SetPtrAddress('" << name << "') - Column not found." << endl;
550#ifdef __EXCEPTIONS
551 throw runtime_error(str.str());
552#else
553 gLog << err << "ERROR - " << str.str() << endl;
554 return false;
555#endif
556 }
557
558 if (sizeof(T)!=fTable.cols[name].size)
559 {
560 ostringstream str;
561 str << "SetPtrAddress('" << name << "') - Element size mismatch: expected "
562 << fTable.cols[name].size << " from header, got " << sizeof(T) << endl;
563#ifdef __EXCEPTIONS
564 throw runtime_error(str.str());
565#else
566 gLog << err << "ERROR - " << str.str() << endl;
567 return false;
568#endif
569 }
570
571 if (cnt!=fTable.cols[name].num)
572 {
573 ostringstream str;
574 str << "SetPtrAddress('" << name << "') - Element count mismatch: expected "
575 << fTable.cols[name].num << " from header, got " << cnt << endl;
576#ifdef __EXCEPTIONS
577 throw runtime_error(str.str());
578#else
579 gLog << err << "ERROR - " << str.str() << endl;
580 return false;
581#endif
582 }
583
584 // if (fAddresses.count(ptr)>0)
585 // gLog << warn << "SetPtrAddress('" << name << "') - Pointer " << ptr << " already assigned." << endl;
586
587 //fAddresses[ptr] = fTable.cols[name];
588 fAddresses.push_back(make_pair(ptr, fTable.cols[name]));
589 sort(fAddresses.begin(), fAddresses.end(), Compare);
590 return true;
591 }
592
593 template<class T>
594 bool SetRefAddress(const string &name, T &ptr)
595 {
596 return SetPtrAddress(name, &ptr, sizeof(ptr)/sizeof(T));
597 }
598
599 template<typename T>
600 bool SetVecAddress(const string &name, vector<T> &vec)
601 {
602 return SetPtrAddress(name, vec.data(), vec.size());
603 }
604
605 template<typename T>
606 T Get(const string &key) const
607 {
608 return fTable.Get<T>(key);
609 }
610
611 bool SetPtrAddress(const string &name, void *ptr)
612 {
613 if (fTable.cols.count(name)==0)
614 {
615 ostringstream str;
616 str <<"SetPtrAddress('" << name << "') - Column not found." << endl;
617#ifdef __EXCEPTIONS
618 throw runtime_error(str.str());
619#else
620 gLog << err << "ERROR - " << str.str() << endl;
621 return false;
622#endif
623 }
624
625 // if (fAddresses.count(ptr)>0)
626 // gLog << warn << "SetPtrAddress('" << name << "') - Pointer " << ptr << " already assigned." << endl;
627
628 //fAddresses[ptr] = fTable.cols[name];
629 fAddresses.push_back(make_pair(ptr, fTable.cols[name]));
630 sort(fAddresses.begin(), fAddresses.end(), Compare);
631 return true;
632 }
633
634 bool HasKey(const string &key) const { return fTable.HasKey(key); }
635 int64_t GetInt(const string &key) const { return fTable.Get<int64_t>(key); }
636 uint64_t GetUInt(const string &key) const { return fTable.Get<uint64_t>(key); }
637 double GetFloat(const string &key) const { return fTable.Get<double>(key); }
638 string GetStr(const string &key) const { return fTable.Get<string>(key); }
639
640 size_t GetN(const string &key) const
641 {
642 return fTable.GetN(key);
643 }
644
645 size_t GetNumRows() const { return fTable.num_rows; }
646 size_t GetRow() const { return fRow; }
647
648 operator bool() const { return fTable && fTable.offset!=0; }
649
650 void PrintKeys() const { fTable.PrintKeys(); }
651 void PrintColumns() const { fTable.PrintColumns(); }
652};
653
654};
655#endif
Note: See TracBrowser for help on using the repository browser.