source: trunk/Mars/mcore/fits.h@ 15267

Last change on this file since 15267 was 15267, checked in by tbretz, 12 years ago
It seems there is a problem with the defines used by CINT in AcLiC and there seems to be a problem with NameMangling. Having GetRow overloaded does not seem to work. It needs a different name seen by root.
File size: 24.7 KB
Line 
1#ifndef MARS_fits
2#define MARS_fits
3
4#ifdef __CINT__
5#define int8_t Char_t
6#define int16_t Short_t
7#define int32_t Int_t
8#define int64_t Long64_t
9#define uint8_t UChar_t
10#define uint16_t UShort_t
11#define uint32_t UInt_t
12#define uint64_t ULong64_t
13#else
14#include <stdint.h>
15#endif
16
17#include <map>
18#include <string>
19#include <sstream>
20#include <algorithm>
21
22#ifdef __EXCEPTIONS
23#include <stdexcept>
24#endif
25
26#ifdef __CINT__
27#define off_t size_t
28#endif
29
30#if !defined(__MARS__) && !defined(__CINT__)
31#include <unordered_map>
32#endif
33
34#ifndef __MARS__
35#include <vector>
36#include <iomanip>
37#include <iostream>
38#define gLog cerr
39#define ___err___ ""
40#define ___warn___ ""
41#define ___all___ ""
42#else
43#include "MLog.h"
44#include "MLogManip.h"
45#define ___err___ err
46#define ___warn___ warn
47#define ___all___ all
48#endif
49
50#if defined(HAVE_ZLIB) || defined(__CINT__)
51#include "izstream.h"
52#else
53#include <fstream>
54#define izstream ifstream
55#warning Support for zipped FITS files disabled.
56#endif
57
58#include "checksum.h"
59
60#ifndef __MARS__
61namespace std
62{
63#else
64using namespace std;
65#endif
66
67class fits : public izstream
68{
69public:
70 struct Entry
71 {
72 char type;
73 string value;
74 string comment;
75
76 template<typename T>
77 T Get() const
78 {
79 T t;
80
81 istringstream str(value);
82 str >> t;
83
84 return t;
85 }
86 };
87
88 struct Table
89 {
90 off_t offset;
91
92 string name;
93 size_t bytes_per_row;
94 size_t num_rows;
95 size_t num_cols;
96
97 struct Column
98 {
99 size_t offset;
100 size_t num;
101 size_t size;
102 char type;
103 string unit;
104 };
105
106 typedef map<string, Entry> Keys;
107 typedef map<string, Column> Columns;
108
109 Columns cols;
110 Keys keys;
111
112 int64_t datasum;
113
114 string Trim(const string &str, char c=' ') const
115 {
116 // Trim Both leading and trailing spaces
117 const size_t pstart = str.find_first_not_of(c); // Find the first character position after excluding leading blank spaces
118 const size_t pend = str.find_last_not_of(c); // Find the first character position from reverse af
119
120 // if all spaces or empty return an empty string
121 if (string::npos==pstart || string::npos==pend)
122 return string();
123
124 return str.substr(pstart, pend-pstart+1);
125 }
126
127 bool Check(const string &key, char type, const string &value="") const
128 {
129 const Keys::const_iterator it = keys.find(key);
130 if (it==keys.end())
131 {
132 ostringstream str;
133 str << "Key '" << key << "' not found.";
134#ifdef __EXCEPTIONS
135 throw runtime_error(str.str());
136#else
137 gLog << ___err___ << "ERROR - " << str.str() << endl;
138 return false;
139#endif
140 }
141
142 if (it->second.type!=type)
143 {
144 ostringstream str;
145 str << "Wrong type for key '" << key << "': expected " << type << ", found " << it->second.type << ".";
146#ifdef __EXCEPTIONS
147 throw runtime_error(str.str());
148#else
149 gLog << ___err___ << "ERROR - " << str.str() << endl;
150 return false;
151#endif
152 }
153
154 if (!value.empty() && it->second.value!=value)
155 {
156 ostringstream str;
157 str << "Wrong value for key '" << key << "': expected " << value << ", found " << it->second.value << ".";
158#ifdef __EXCEPTIONS
159 throw runtime_error(str.str());
160#else
161 gLog << ___err___ << "ERROR - " << str.str() << endl;
162 return false;
163#endif
164 }
165
166 return true;
167 }
168
169 Keys ParseBlock(const vector<string> &vec) const
170 {
171 map<string,Entry> rc;
172
173 for (unsigned int i=0; i<vec.size(); i++)
174 {
175 const string key = Trim(vec[i].substr(0,8));
176 // Keywords without a value, like COMMENT / HISTORY
177 if (vec[i].substr(8,2)!="= ")
178 continue;
179
180 char type = 0;
181
182 string com;
183 string val = Trim(vec[i].substr(10));
184 if (val[0]=='\'')
185 {
186 // First skip all '' in the string
187 size_t p = 1;
188 while (1)
189 {
190 const size_t pp = val.find_first_of('\'', p);
191 if (pp==string::npos)
192 break;
193
194 p = val[pp+1]=='\'' ? pp+2 : pp+1;
195 }
196
197 // Now find the comment
198 const size_t ppp = val.find_first_of('/', p);
199
200 // Set value, comment and type
201 com = ppp==string::npos ? "" : Trim(val.substr(ppp+1));
202 val = Trim(val.substr(1, p-2));
203 type = 'T';
204 }
205 else
206 {
207 const size_t p = val.find_first_of('/');
208
209 com = Trim(val.substr(p+2));
210 val = Trim(val.substr(0, p));
211
212 if (val.empty() || val.find_first_of('T')!=string::npos || val.find_first_of('F')!=string::npos)
213 type = 'B';
214 else
215 type = val.find_last_of('.')==string::npos ? 'I' : 'F';
216 }
217
218 const Entry e = { type, val, com };
219 rc[key] = e;
220 }
221
222 return rc;
223 }
224
225 Table() : offset(0) { }
226 Table(const vector<string> &vec, off_t off) :
227 offset(off), keys(ParseBlock(vec))
228 {
229 if (!Check("XTENSION", 'T', "BINTABLE") ||
230 !Check("NAXIS", 'I', "2") ||
231 !Check("BITPIX", 'I', "8") ||
232 !Check("PCOUNT", 'I', "0") ||
233 !Check("GCOUNT", 'I', "1") ||
234 !Check("EXTNAME", 'T') ||
235 !Check("NAXIS1", 'I') ||
236 !Check("NAXIS2", 'I') ||
237 !Check("TFIELDS", 'I'))
238 return;
239
240 bytes_per_row = Get<size_t>("NAXIS1");
241 num_rows = Get<size_t>("NAXIS2");
242 num_cols = Get<size_t>("TFIELDS");
243 datasum = Get<int64_t>("DATASUM", -1);
244
245 size_t bytes = 0;
246 for (size_t i=1; i<=num_cols; i++)
247 {
248 ostringstream num;
249 num << i;
250
251 if (!Check("TTYPE"+num.str(), 'T') ||
252 !Check("TFORM"+num.str(), 'T'))
253 return;
254
255 const string id = Get<string>("TTYPE"+num.str());
256 const string fmt = Get<string>("TFORM"+num.str());
257 const string unit = Get<string>("TUNIT"+num.str(), "");
258
259 istringstream sin(fmt);
260 int n = 0;
261 sin >> n;
262 if (!sin)
263 n = 1;
264
265 const char type = fmt[fmt.length()-1];
266
267 size_t size = 0;
268 switch (type)
269 {
270 // We could use negative values to mark floats
271 // otheriwse we could just cast them to int64_t?
272 case 'L': // logical
273 case 'A': // char
274 case 'B': size = 1; break; // byte
275 case 'I': size = 2; break; // short
276 case 'J': size = 4; break; // int
277 case 'K': size = 8; break; // long long
278 case 'E': size = 4; break; // float
279 case 'D': size = 8; break; // double
280 // case 'X': size = n; break; // bits (n=number of bytes needed to contain all bits)
281 // case 'C': size = 8; break; // complex float
282 // case 'M': size = 16; break; // complex double
283 // case 'P': size = 8; break; // array descriptor (32bit)
284 // case 'Q': size = 16; break; // array descriptor (64bit)
285 default:
286 {
287 ostringstream str;
288 str << "FITS format TFORM='" << fmt << "' not yet supported.";
289#ifdef __EXCEPTIONS
290 throw runtime_error(str.str());
291#else
292 gLog << ___err___ << "ERROR - " << str.str() << endl;
293 return;
294#endif
295 }
296 }
297
298 const Table::Column col = { bytes, n, size, type, unit };
299
300 cols[id] = col;
301 bytes += n*size;
302 }
303
304 if (bytes!=bytes_per_row)
305 {
306#ifdef __EXCEPTIONS
307 throw runtime_error("Column size mismatch");
308#else
309 gLog << ___err___ << "ERROR - Column size mismatch" << endl;
310 return;
311#endif
312 }
313
314 name = Get<string>("EXTNAME");
315 }
316
317 void PrintKeys(bool display_all=false) const
318 {
319 for (Keys::const_iterator it=keys.begin(); it!=keys.end(); it++)
320 {
321 if (!display_all &&
322 (it->first.substr(0, 6)=="TTYPE" ||
323 it->first.substr(0, 6)=="TFORM" ||
324 it->first.substr(0, 6)=="TUNIT" ||
325 it->first=="TFIELDS" ||
326 it->first=="XTENSION" ||
327 it->first=="NAXIS" ||
328 it->first=="BITPIX" ||
329 it->first=="PCOUNT" ||
330 it->first=="GCOUNT")
331 )
332 continue;
333
334 gLog << ___all___ << setw(2) << it->second.type << '|' << it->first << '=' << it->second.value << '/' << it->second.comment << '|' << endl;
335 }}
336
337 void PrintColumns() const
338 {
339 typedef map<pair<size_t, string>, Column> Sorted;
340
341 Sorted sorted;
342
343 for (Columns::const_iterator it=cols.begin(); it!=cols.end(); it++)
344 sorted[make_pair(it->second.offset, it->first)] = it->second;
345
346 for (Sorted::const_iterator it=sorted.begin(); it!=sorted.end(); it++)
347 {
348 gLog << ___all___ << setw(6) << it->second.offset << "| ";
349 gLog << it->second.num << 'x';
350 switch (it->second.type)
351 {
352 case 'A': gLog << "char(8)"; break;
353 case 'L': gLog << "bool(8)"; break;
354 case 'B': gLog << "byte(8)"; break;
355 case 'I': gLog << "short(16)"; break;
356 case 'J': gLog << "int(32)"; break;
357 case 'K': gLog << "int(64)"; break;
358 case 'E': gLog << "float(32)"; break;
359 case 'D': gLog << "double(64)"; break;
360 }
361 gLog << ": " << it->first.second << " [" << it->second.unit << "]" << endl;
362 }
363 }
364
365 operator bool() const { return !name.empty(); }
366
367 bool HasKey(const string &key) const
368 {
369 return keys.find(key)!=keys.end();
370 }
371
372 bool HasColumn(const string& col) const
373 {
374 return cols.find(col)!=cols.end();
375 }
376
377 const Columns &GetColumns() const
378 {
379 return cols;
380 }
381
382 const Keys &GetKeys() const
383 {
384 return keys;
385 }
386
387 // Values of keys are always signed
388 template<typename T>
389 T Get(const string &key) const
390 {
391 const map<string,Entry>::const_iterator it = keys.find(key);
392 if (it==keys.end())
393 {
394 ostringstream str;
395 str << "Key '" << key << "' not found." << endl;
396#ifdef __EXCEPTIONS
397 throw runtime_error(str.str());
398#else
399 gLog << ___err___ << "ERROR - " << str.str() << endl;
400 return T();
401#endif
402 }
403 return it->second.Get<T>();
404 }
405
406 // Values of keys are always signed
407 template<typename T>
408 T Get(const string &key, const T &deflt) const
409 {
410 const map<string,Entry>::const_iterator it = keys.find(key);
411 return it==keys.end() ? deflt :it->second.Get<T>();
412 }
413
414 size_t GetN(const string &key) const
415 {
416 const Columns::const_iterator it = cols.find(key);
417 return it==cols.end() ? 0 : it->second.num;
418 }
419 };
420
421private:
422 Table fTable;
423
424 typedef pair<void*, Table::Column> Address;
425 typedef vector<Address> Addresses;
426 //map<void*, Table::Column> fAddresses;
427 Addresses fAddresses;
428
429#if defined(__MARS__) || defined(__CINT__)
430 typedef map<string, void*> Pointers;
431#else
432 typedef unordered_map<string, void*> Pointers;
433#endif
434 Pointers fPointers;
435
436 vector<vector<char>> fGarbage;
437
438 vector<char> fBufferRow;
439 vector<char> fBufferDat;
440
441 size_t fRow;
442
443 Checksum fChkHeader;
444 Checksum fChkData;
445
446 bool ReadBlock(vector<string> &vec)
447 {
448 int endtag = 0;
449 for (int i=0; i<36; i++)
450 {
451 char c[81];
452 c[80] = 0;
453 read(c, 80);
454 if (!good())
455 break;
456
457 fChkHeader.add(c, 80);
458
459// if (c[0]==0)
460// return vector<string>();
461
462 string str(c);
463
464// if (!str.empty())
465// cout << setw(2) << i << "|" << str << "|" << (endtag?'-':'+') << endl;
466
467 if (endtag==2 || str=="END ")
468 {
469 endtag = 2; // valid END tag found
470 continue;
471 }
472
473 if (endtag==1 || str==" ")
474 {
475 endtag = 1; // end tag not found, but expected to be there
476 continue;
477 }
478
479 vec.push_back(str);
480 }
481
482 // Make sure that no empty vector is returned
483 if (endtag && vec.size()%36==0)
484 vec.push_back(string("END = '' / "));
485
486 return endtag==2;
487 }
488
489 string Compile(const string &key, int16_t i=-1)
490 {
491 if (i<0)
492 return key;
493
494 ostringstream str;
495 str << key << i;
496 return str.str();
497 }
498
499public:
500 fits(const string &fname, bool force=false) : izstream(fname.c_str())
501 {
502 char simple[10];
503 read(simple, 10);
504 if (!good())
505 return;
506
507 if (memcmp(simple, "SIMPLE = ", 10))
508 {
509 clear(rdstate()|ios::badbit);
510#ifdef __EXCEPTIONS
511 throw runtime_error("File is not a FITS file.");
512#else
513 gLog << ___err___ << "ERROR - File is not a FITS file." << endl;
514 return;
515#endif
516 }
517
518 seekg(0);
519
520 while (good())
521 {
522 vector<string> block;
523 while (1)
524 {
525 // FIXME: Set limit on memory consumption
526 const int rc = ReadBlock(block);
527 if (!good())
528 {
529 clear(rdstate()|ios::badbit);
530#ifdef __EXCEPTIONS
531 throw runtime_error("FITS file corrupted.");
532#else
533 gLog << ___err___ << "ERROR - FITS file corrupted." << endl;
534 return;
535#endif
536 }
537
538 if (block.size()%36)
539 {
540 if (!rc && !force)
541 {
542 clear(rdstate()|ios::badbit);
543#ifdef __EXCEPTIONS
544 throw runtime_error("END keyword missing in FITS header.");
545#else
546 gLog << ___err___ << "ERROR - END keyword missing in FITS file... file might be corrupted." << endl;
547 return;
548#endif
549 }
550 break;
551 }
552 }
553
554 if (block.size()==0)
555 break;
556
557 if (block[0].substr(0, 9)=="SIMPLE =")
558 {
559 fChkHeader.reset();
560 continue;
561 }
562
563 if (block[0].substr(0, 9)=="XTENSION=")
564 {
565 // FIXME: Check for table name
566
567 fTable = Table(block, tellg());
568 fRow = (size_t)-1;
569
570 //fTable.PrintKeys();
571
572 if (!fTable)
573 {
574 clear(rdstate()|ios::badbit);
575 return;
576 }
577
578 fBufferRow.resize(fTable.bytes_per_row + 8-fTable.bytes_per_row%4);
579 fBufferDat.resize(fTable.bytes_per_row);
580
581 /*
582 // Next table should start at:
583 const size_t size = fTable.bytes_per_row*fTable.num_rows;
584 const size_t blks = size/(36*80);
585 const size_t rest = size%(36*80);
586
587 seekg((blks+(rest>0?1:0))*(36*80), ios::cur);
588 if (!good())
589 gLog << ___err___ << "File seems to be incomplete (less data than expected from header)." << endl;
590
591 fRow = fTable.num_rows;
592 */
593
594 break;
595 }
596 }
597 }
598
599 uint8_t ReadRow(size_t row)
600 {
601 // if (row!=fRow+1) // Fast seeking is ensured by izstream
602 seekg(fTable.offset+row*fTable.bytes_per_row);
603
604 // For the checksum we need everything to be correctly aligned
605 const uint8_t offset = (row*fTable.bytes_per_row)%4;
606
607 auto ib = fBufferRow.begin();
608 auto ie = fBufferRow.end();
609 *ib++ = 0;
610 *ib++ = 0;
611 *ib++ = 0;
612 *ib = 0;
613
614 *--ie = 0;
615 *--ie = 0;
616 *--ie = 0;
617 *--ie = 0;
618 *--ie = 0;
619 *--ie = 0;
620 *--ie = 0;
621 *--ie = 0;
622
623 read(fBufferRow.data()+offset, fTable.bytes_per_row);
624 //fin.clear(fin.rdstate()&~ios::eofbit);
625
626 if (row==fRow+1)
627 fChkData.add(fBufferRow);
628
629 fRow = row;
630
631 return offset;
632 }
633
634 template<size_t N>
635 void revcpy(char *dest, const char *src, int num)
636 {
637 const char *pend = src + num*N;
638 for (const char *ptr = src; ptr<pend; ptr+=N, dest+=N)
639 reverse_copy(ptr, ptr+N, dest);
640 }
641
642#if !defined(__MARS__) && !defined(__CINT__)
643 bool GetRow(size_t row, bool check=true)
644#else
645 bool GetRowNum(size_t row, bool check=true)
646#endif
647 {
648 if (check && row>=fTable.num_rows)
649 return false;
650
651 const uint8_t offset = ReadRow(row);
652 if (!good())
653 return good();
654
655 const char *ptr = fBufferRow.data() + offset;
656
657 for (Addresses::const_iterator it=fAddresses.begin(); it!=fAddresses.end(); it++)
658 {
659 const Table::Column &c = it->second;
660
661 const char *src = ptr + c.offset;
662 char *dest = reinterpret_cast<char*>(it->first);
663
664 // Let the compiler do some optimization by
665 // knowing that we only have 1, 2, 4 and 8
666 switch (c.size)
667 {
668 case 1: memcpy (dest, src, c.num*c.size); break;
669 case 2: revcpy<2>(dest, src, c.num); break;
670 case 4: revcpy<4>(dest, src, c.num); break;
671 case 8: revcpy<8>(dest, src, c.num); break;
672 }
673 }
674
675 return good();
676 }
677
678 bool GetNextRow(bool check=true)
679 {
680#if !defined(__MARS__) && !defined(__CINT__)
681 return GetRow(fRow+1, check);
682#else
683 return GetRowNum(fRow+1, check);
684#endif
685 }
686
687 bool SkipNextRow()
688 {
689 seekg(fTable.offset+(++fRow)*fTable.bytes_per_row);
690 return good();
691 }
692
693 static bool Compare(const Address &p1, const Address &p2)
694 {
695 return p1.first>p2.first;
696 }
697
698 template<class T, class S>
699 const T &GetAs(const string &name)
700 {
701 return *reinterpret_cast<S*>(fPointers[name]);
702 }
703
704 void *SetPtrAddress(const string &name)
705 {
706 if (fTable.cols.count(name)==0)
707 {
708 ostringstream str;
709 str <<"SetPtrAddress('" << name << "') - Column not found." << endl;
710#ifdef __EXCEPTIONS
711 throw runtime_error(str.str());
712#else
713 gLog << ___err___ << "ERROR - " << str.str() << endl;
714 return NULL;
715#endif
716 }
717
718 Pointers::const_iterator it = fPointers.find(name);
719 if (it!=fPointers.end())
720 return it->second;
721
722 fGarbage.push_back(vector<char>(fTable.cols[name].size*fTable.cols[name].num));
723
724 void *ptr = fGarbage.back().data();
725
726 fPointers[name] = ptr;
727 fAddresses.push_back(make_pair(ptr, fTable.cols[name]));
728 sort(fAddresses.begin(), fAddresses.end(), Compare);
729 return ptr;
730 }
731
732 template<typename T>
733 bool SetPtrAddress(const string &name, T *ptr, size_t cnt)
734 {
735 if (fTable.cols.count(name)==0)
736 {
737 ostringstream str;
738 str << "SetPtrAddress('" << name << "') - Column not found." << endl;
739#ifdef __EXCEPTIONS
740 throw runtime_error(str.str());
741#else
742 gLog << ___err___ << "ERROR - " << str.str() << endl;
743 return false;
744#endif
745 }
746
747 if (sizeof(T)!=fTable.cols[name].size)
748 {
749 ostringstream str;
750 str << "SetPtrAddress('" << name << "') - Element size mismatch: expected "
751 << fTable.cols[name].size << " from header, got " << sizeof(T) << endl;
752#ifdef __EXCEPTIONS
753 throw runtime_error(str.str());
754#else
755 gLog << ___err___ << "ERROR - " << str.str() << endl;
756 return false;
757#endif
758 }
759
760 if (cnt!=fTable.cols[name].num)
761 {
762 ostringstream str;
763 str << "SetPtrAddress('" << name << "') - Element count mismatch: expected "
764 << fTable.cols[name].num << " from header, got " << cnt << endl;
765#ifdef __EXCEPTIONS
766 throw runtime_error(str.str());
767#else
768 gLog << ___err___ << "ERROR - " << str.str() << endl;
769 return false;
770#endif
771 }
772
773 // if (fAddresses.count(ptr)>0)
774 // gLog << warn << "SetPtrAddress('" << name << "') - Pointer " << ptr << " already assigned." << endl;
775
776 //fAddresses[ptr] = fTable.cols[name];
777 fPointers[name] = ptr;
778 fAddresses.push_back(make_pair(ptr, fTable.cols[name]));
779 sort(fAddresses.begin(), fAddresses.end(), Compare);
780 return true;
781 }
782
783 template<class T>
784 bool SetRefAddress(const string &name, T &ptr)
785 {
786 return SetPtrAddress(name, &ptr, sizeof(ptr)/sizeof(T));
787 }
788
789 template<typename T>
790 bool SetVecAddress(const string &name, vector<T> &vec)
791 {
792 return SetPtrAddress(name, vec.data(), vec.size());
793 }
794
795 template<typename T>
796 T Get(const string &key) const
797 {
798 return fTable.Get<T>(key);
799 }
800
801 template<typename T>
802 T Get(const string &key, const string &deflt) const
803 {
804 return fTable.Get<T>(key, deflt);
805 }
806
807 bool SetPtrAddress(const string &name, void *ptr)
808 {
809 if (fTable.cols.count(name)==0)
810 {
811 ostringstream str;
812 str <<"SetPtrAddress('" << name << "') - Column not found." << endl;
813#ifdef __EXCEPTIONS
814 throw runtime_error(str.str());
815#else
816 gLog << ___err___ << "ERROR - " << str.str() << endl;
817 return false;
818#endif
819 }
820
821 // if (fAddresses.count(ptr)>0)
822 // gLog << warn << "SetPtrAddress('" << name << "') - Pointer " << ptr << " already assigned." << endl;
823
824 //fAddresses[ptr] = fTable.cols[name];
825 fPointers[name] = ptr;
826 fAddresses.push_back(make_pair(ptr, fTable.cols[name]));
827 sort(fAddresses.begin(), fAddresses.end(), Compare);
828 return true;
829 }
830
831 bool HasKey(const string &key) const { return fTable.HasKey(key); }
832 bool HasColumn(const string& col) const { return fTable.HasColumn(col);}
833 const Table::Columns &GetColumns() const { return fTable.GetColumns();}
834 const Table::Keys &GetKeys() const { return fTable.GetKeys();}
835
836 int64_t GetInt(const string &key) const { return fTable.Get<int64_t>(key); }
837 uint64_t GetUInt(const string &key) const { return fTable.Get<uint64_t>(key); }
838 double GetFloat(const string &key) const { return fTable.Get<double>(key); }
839 string GetStr(const string &key) const { return fTable.Get<string>(key); }
840
841 size_t GetN(const string &key) const
842 {
843 return fTable.GetN(key);
844 }
845
846 size_t GetNumRows() const { return fTable.num_rows; }
847 size_t GetRow() const { return fRow==(size_t)-1 ? 0 : fRow; }
848
849 operator bool() const { return fTable && fTable.offset!=0; }
850
851 void PrintKeys() const { fTable.PrintKeys(); }
852 void PrintColumns() const { fTable.PrintColumns(); }
853
854 bool IsHeaderOk() const { return fTable.datasum<0?false:(fChkHeader+Checksum(fTable.datasum)).valid(); }
855 bool IsFileOk() const { return (fChkHeader+fChkData).valid(); }
856
857};
858
859#ifndef __MARS__
860};
861#endif
862#endif
Note: See TracBrowser for help on using the repository browser.