source: trunk/Mars/mcore/DrsCalib.h@ 17253

Last change on this file since 17253 was 17248, checked in by tbretz, 11 years ago
Moved the night argument to the end so that it can be omoitted
File size: 44.4 KB
Line 
1#ifndef MARS_DrsCalib
2#define MARS_DrsCalib
3
4#include <math.h> // fabs
5#include <errno.h> // errno
6
7#ifndef MARS_fits
8#include "fits.h"
9#endif
10
11#ifndef MARS_ofits
12#include "ofits.h"
13#endif
14
15#ifdef __MARS__
16#include "MTime.h"
17#endif
18
19class DrsCalibrate
20{
21protected:
22 uint64_t fNumEntries;
23
24 size_t fNumSamples;
25 size_t fNumChannels;
26
27 std::vector<int64_t> fSum;
28 std::vector<int64_t> fSum2;
29
30public:
31 DrsCalibrate() : fNumEntries(0), fNumSamples(0), fNumChannels(0)
32 {
33 fSum.reserve(1024*1440);
34 fSum2.reserve(1024*1440);
35 }
36
37 void Reset()
38 {
39 fNumEntries = 0;
40 fNumSamples = 0;
41 fNumChannels = 0;
42
43 fSum.clear();
44 fSum2.clear();
45 }
46
47 void InitSize(uint16_t channels, uint16_t samples)
48 {
49 fNumChannels = channels;
50 fNumSamples = samples;
51
52 fSum.assign(samples*channels, 0);
53 fSum2.assign(samples*channels, 0);
54 }
55
56 void AddRel(const int16_t *val, const int16_t *start)
57 {
58 /*
59 for (size_t ch=0; ch<fNumChannels; ch++)
60 {
61 const int16_t &spos = start[ch];
62 if (spos<0)
63 continue;
64
65 const size_t pos = ch*1024;
66 for (size_t i=0; i<1024; i++)
67 {
68 // Value is relative to trigger
69 // Abs is corresponding index relative to DRS pipeline
70 const size_t rel = pos + i;
71 const size_t abs = pos + (spos+i)%1024;
72
73 const int64_t v = val[rel];
74
75 fSum[abs] += v;
76 fSum2[abs] += v*v;
77 }
78 }*/
79
80 // This version is 2.5 times faster because the compilers optimization
81 // is not biased by the evaluation of %1024
82 for (size_t ch=0; ch<fNumChannels; ch++)
83 {
84 const int16_t &spos = start[ch];
85 if (spos<0)
86 continue;
87
88 const size_t pos = ch*1024;
89
90 const int16_t *beg_val = val + pos;
91 int64_t *beg_sum = fSum.data() + pos;
92 int64_t *beg_sum2 = fSum2.data() + pos;
93
94 const int16_t *pval = beg_val; // val[rel]
95 int64_t *psum = beg_sum + spos; // fSum[abs]
96 int64_t *psum2 = beg_sum2 + spos; // fSum2[abs]
97
98 while (psum<beg_sum+1024)
99 {
100 const int64_t v = *pval++;
101
102 *psum++ += v;
103 *psum2++ += v*v;
104 }
105
106 psum = beg_sum;
107 psum2 = beg_sum2;
108
109 while (pval<beg_val+1024)
110 {
111 const int64_t v = *pval++;
112
113 *psum++ += v;
114 *psum2++ += v*v;
115 }
116 }
117
118 fNumEntries++;
119 }
120
121 void AddRel(const int16_t *val, const int16_t *start,
122 const int32_t *offset, const uint32_t scale)
123 {
124 /*
125 for (size_t ch=0; ch<fNumChannels; ch++)
126 {
127 const int16_t spos = start[ch];
128 if (spos<0)
129 continue;
130
131 const size_t pos = ch*1024;
132
133 for (size_t i=0; i<fNumSamples; i++)
134 {
135 // Value is relative to trigger
136 // Offset is relative to DRS pipeline
137 // Abs is corresponding index relative to DRS pipeline
138 const size_t rel = pos + i;
139 const size_t abs = pos + (spos+i)%1024;
140
141 const int64_t v = int64_t(val[rel])*scale-offset[abs];
142
143 fSum[abs] += v;
144 fSum2[abs] += v*v;
145 }
146 }*/
147
148 // This version is 2.5 times faster because the compilers optimization
149 // is not biased by the evaluation of %1024
150 for (size_t ch=0; ch<fNumChannels; ch++)
151 {
152 const int16_t &spos = start[ch];
153 if (spos<0)
154 continue;
155
156 const size_t pos = ch*1024;
157
158 const int16_t *beg_val = val + pos;
159 const int32_t *beg_offset = offset + pos;
160 int64_t *beg_sum = fSum.data() + pos;
161 int64_t *beg_sum2 = fSum2.data() + pos;
162
163
164 const int16_t *pval = beg_val; // val[rel]
165 const int32_t *poffset = beg_offset + spos; // offset[abs]
166 int64_t *psum = beg_sum + spos; // fSum[abs]
167 int64_t *psum2 = beg_sum2 + spos; // fSum2[abs]
168
169 while (psum<beg_sum+1024)
170 {
171 const int64_t v = int64_t(*pval++)*scale - *poffset++;
172
173 *psum++ += v;
174 *psum2++ += v*v;
175 }
176
177 psum = beg_sum;
178 psum2 = beg_sum2;
179 poffset = beg_offset;
180
181 while (pval<beg_val+1024)
182 {
183 const int64_t v = int64_t(*pval++)*scale - *poffset++;
184
185 *psum++ += v;
186 *psum2++ += v*v;
187 }
188 }
189
190 fNumEntries++;
191 }
192 /*
193 void AddAbs(const int16_t *val, const int16_t *start,
194 const int32_t *offset, const uint32_t scale)
195 {
196 for (size_t ch=0; ch<fNumChannels; ch++)
197 {
198 const int16_t spos = start[ch];
199 if (spos<0)
200 continue;
201
202 const size_t pos = ch*fNumSamples;
203
204 for (size_t i=0; i<fNumSamples; i++)
205 {
206 // Value is relative to trigger
207 // Offset is relative to DRS pipeline
208 // Abs is corresponding index relative to DRS pipeline
209 const size_t rel = pos + i;
210 const size_t abs = pos + (spos+i)%1024;
211
212 const int64_t v = int64_t(val[rel])*scale-offset[abs];
213
214 fSum[rel] += v;
215 fSum2[rel] += v*v;
216 }
217 }
218
219 fNumEntries++;
220 }*/
221
222 void AddAbs(const int16_t *val, const int16_t *start,
223 const int32_t *offset, const uint32_t scale)
224 {
225 /*
226 // 1440 without tm, 1600 with tm
227 for (size_t ch=0; ch<fNumChannels; ch++)
228 {
229 const int16_t spos = start[ch];
230 if (spos<0)
231 continue;
232
233 const size_t pos = ch*fNumSamples;
234 const size_t drs = ch>1439 ? ((ch-1440)*9+8)*1024 : ch*1024;
235
236 for (size_t i=0; i<fNumSamples; i++)
237 {
238 // Value is relative to trigger
239 // Offset is relative to DRS pipeline
240 // Abs is corresponding index relative to DRS pipeline
241 const size_t rel = pos + i;
242 const size_t abs = drs + (spos+i)%1024;
243
244 const int64_t v = int64_t(val[rel])*scale-offset[abs];
245
246 fSum[rel] += v;
247 fSum2[rel] += v*v;
248 }
249 }*/
250
251 // This version is 1.5 times faster because the compilers optimization
252 // is not biased by the evaluation of %1024
253 for (size_t ch=0; ch<fNumChannels; ch++)
254 {
255 const int16_t &spos = start[ch];
256 if (spos<0)
257 continue;
258
259 const size_t pos = ch*fNumSamples;
260
261 const int32_t *beg_offset = offset + ch*1024;
262 const int16_t *beg_val = val + pos;
263 int64_t *beg_sum = fSum.data() + pos;
264 int64_t *beg_sum2 = fSum2.data() + pos;
265
266
267 const int16_t *pval = beg_val; // val[rel]
268 const int32_t *poffset = beg_offset + spos; // offset[abs]
269 int64_t *psum = beg_sum; // fSum[rel]
270 int64_t *psum2 = beg_sum2; // fSum2[rel]
271
272 if (spos+fNumSamples>1024)
273 {
274 while (poffset<beg_offset+1024)
275 {
276 const int64_t v = int64_t(*pval++)*scale - *poffset++;
277
278 *psum++ += v;
279 *psum2++ += v*v;
280 }
281
282 poffset = beg_offset;
283 }
284
285 while (psum<beg_sum+fNumSamples)
286 {
287 const int64_t v = int64_t(*pval++)*scale - *poffset++;
288
289 *psum++ += v;
290 *psum2++ += v*v;
291 }
292 }
293
294 fNumEntries++;
295 }
296
297
298 static void ApplyCh(float *vec, const int16_t *val, int16_t start, uint32_t roi,
299 const int32_t *offset, const uint32_t scaleabs,
300 const int64_t *gain, const uint64_t scalegain)
301 {
302 if (start<0)
303 {
304 memset(vec, 0, roi);
305 return;
306 }
307 /*
308 for (size_t i=0; i<roi; i++)
309 {
310 // Value is relative to trigger
311 // Offset is relative to DRS pipeline
312 // Abs is corresponding index relative to DRS pipeline
313 const size_t abs = (start+i)%1024;
314
315 const int64_t v =
316 + int64_t(val[i])*scaleabs-offset[abs]
317 ;
318
319 const int64_t div = gain[abs];
320 vec[i] = div==0 ? 0 : double(v)*scalegain/div;
321 }
322 */
323
324 // This version is faster because the compilers optimization
325 // is not biased by the evaluation of %1024
326 // (Here we are dominated by numerics... improvement ~10%)
327 const int32_t *poffset = offset + start; // offset[abs]
328 const int64_t *pgain = gain + start; // gain[abs]
329 const int16_t *pval = val; // val[rel]
330 float *pvec = vec; // vec[rel]
331
332 if (start+roi>1024)
333 {
334 while (poffset<offset+1024)
335 {
336 const int64_t v =
337 + int64_t(*pval++)*scaleabs - *poffset++
338 ;
339
340 *pvec++ = *pgain==0 ? 0 : double(v)*scalegain / *pgain;
341
342 pgain++;
343 }
344
345 poffset = offset;
346 pgain = gain;
347 }
348
349 while (pvec<vec+roi)
350 {
351 const int64_t v =
352 + int64_t(*pval++)*scaleabs - *poffset++
353 ;
354
355 *pvec++ = *pgain==0 ? 0 : double(v)*scalegain / *pgain;
356
357 pgain++;
358 }
359 }
360
361 static void ApplyCh(float *vec, const int16_t *val, int16_t start, uint32_t roi,
362 const int32_t *offset, const uint32_t scaleabs,
363 const int64_t *gain, const uint64_t scalegain,
364 const int64_t *trgoff, const uint64_t scalerel)
365 {
366 if (start<0)
367 {
368 memset(vec, 0, roi);
369 return;
370 }
371 /*
372 for (size_t i=0; i<roi; i++)
373 {
374 // Value is relative to trigger
375 // Offset is relative to DRS pipeline
376 // Abs is corresponding index relative to DRS pipeline
377 const size_t abs = (start+i)%1024;
378
379 const int64_t v =
380 + (int64_t(val[i])*scaleabs-offset[abs])*scalerel
381 - trgoff[i]
382 ;
383
384 const int64_t div = gain[abs]*scalerel;
385 vec[i] = div==0 ? 0 : double(v)*scalegain/div;
386 }
387 */
388 // (Here we are dominated by numerics... improvement ~10%)
389 const int32_t *poffset = offset + start; // offset[abs]
390 const int64_t *pgain = gain + start; // gain[abs]
391 const int16_t *pval = val; // val[rel]
392 const int64_t *ptrgoff = trgoff; // trgoff[rel]
393 float *pvec = vec; // vec[rel]
394
395 if (start+roi>1024)
396 {
397 while (poffset<offset+1024)
398 {
399 const int64_t v =
400 + (int64_t(*pval++)*scaleabs - *poffset++)*scalerel
401 - *ptrgoff++;
402 ;
403
404 const int64_t div = *pgain * scalerel;
405 *pvec++ = div==0 ? 0 : double(v)*scalegain / div;
406
407 pgain++;
408 }
409
410 poffset = offset;
411 pgain = gain;
412 }
413
414 while (pvec<vec+roi)
415 {
416 const int64_t v =
417 + (int64_t(*pval++)*scaleabs - *poffset++)*scalerel
418 - *ptrgoff++;
419 ;
420
421 const int64_t div = *pgain * scalerel;
422 *pvec++ = div==0 ? 0 : double(v)*scalegain / div;
423
424 pgain++;
425 }
426 }
427
428 static double FindStep(const size_t ch0, const float *vec, int16_t roi, const int16_t pos, const uint16_t *map=NULL)
429 {
430 // We have about 1% of all cases which are not ahndled here,
431 // because the baseline jumps up just before the readout window
432 // and down just after it. In this cases we could determine the jump
433 // from the board time difference or throw the event away.
434 if (pos==0 || pos>=roi)
435 return 0;
436
437 double step = 0; // before
438 double rms = 0; // before
439 int cnt = 0;
440
441 // Exclude TM channel
442 for (int p=0; p<8; p++)
443 {
444 const size_t hw = ch0+p;
445 const size_t sw = (map?map[hw]:hw)*roi + pos;
446
447 const double diff = vec[sw]-vec[sw-1];
448
449 step += diff;
450 rms += (vec[sw]-vec[sw-1])*(vec[sw]-vec[sw-1]);
451
452 cnt++;
453 }
454
455 return cnt==0 ? 0 : step/cnt;
456 }
457
458 static void SubtractStep(const size_t ch0, const double avg, float *vec, int16_t roi, int32_t pos, const uint16_t *map=NULL)
459 {
460 if (pos==0 || pos>=roi)
461 return;
462
463 const int begin = avg>0 ? pos : 0;
464 const int end = avg>0 ? roi : pos;
465
466 const double sub = fabs(avg);
467
468 for (int p=0; p<9; p++)
469 {
470 for (int j=begin; j<end; j++)
471 {
472 const size_t hw = ch0+p;
473 const size_t sw = (map?map[hw]:hw)*roi + j;
474
475 vec[sw] -= sub;
476 }
477 }
478 }
479
480 struct Step
481 {
482 Step() : avg(0), rms(0), pos(0), cnt(0) { }
483 double avg;
484 double rms;
485 double pos;
486 uint16_t cnt;
487
488 static bool sort(const Step &s, const Step &r) { return s.avg<r.avg; }
489 };
490
491 static Step AverageSteps(const std::vector<Step>::iterator beg, const std::vector<Step>::iterator end)
492 {
493 Step rc;
494 for (auto it=beg; it!=end; it++)
495 {
496 rc.pos += it->pos;
497 rc.avg += it->avg;
498 rc.rms += it->avg*it->avg;
499 }
500
501 rc.cnt = end-beg;
502
503 rc.pos /= rc.cnt;
504 rc.avg /= rc.cnt;
505 rc.rms /= rc.cnt;
506 rc.rms -= rc.avg*rc.avg;
507 rc.rms = rc.rms<0 ? 0 : sqrt(rc.rms);
508
509 return rc;
510 }
511
512
513 static Step CorrectStep(float *vec, uint16_t nch, uint16_t roi,
514 const int16_t *prev, const int16_t *start,
515 const int16_t offset, const uint16_t *map=NULL)
516 {
517
518 std::vector<Step> list;
519 list.reserve(nch);
520
521 // Fill steps into array
522 // Exclude broken pixels?
523 // Remove maximum and minimum patches (4max and 4min)?
524 for (size_t ch=0; ch<nch; ch += 9)
525 {
526 if (prev[ch]<0 || start[ch]<0)
527 continue;
528
529 const int16_t dist = (prev[ch]-start[ch]+1024+offset)%1024;
530 const double step = FindStep(ch, vec, roi, dist, map);
531 if (step==0)
532 continue;
533
534 Step rc;
535 rc.pos = dist;
536 rc.avg = step;
537 list.push_back(rc);
538 }
539
540 if (list.empty())
541 return Step();
542
543 Step rc = AverageSteps(list.begin(), list.begin()+list.size());;
544
545 if (rc.avg==0)
546 return Step();
547
548 // std::cout << " A0=" << rc.avg << " rms=" << rc.rms << std::endl;
549 if (rc.rms>5)
550 {
551 sort(list.begin(), list.end(), Step::sort);
552
553 //for (auto it=list.begin(); it!=list.end(); it++)
554 // std::cout << " " << it->avg << std::endl;
555
556 const size_t skip = list.size()/10;
557 rc = AverageSteps(list.begin()+skip, list.begin()+list.size()-skip);
558
559 // std::cout << " A1=" << rc.avg << " rms=" << rc.rms << std::endl;
560 }
561
562 for (size_t ch=0; ch<nch; ch += 9)
563 {
564 const int16_t dist = (prev[ch]-start[ch]+1024+offset)%1024;
565 SubtractStep(ch, rc.avg, vec, roi, dist, map);
566 }
567
568 return rc;
569 }
570
571 static void RemoveSpikes(float *vec, uint32_t roi)
572 {
573 if (roi<4)
574 return;
575
576 for (size_t ch=0; ch<1440; ch++)
577 {
578 float *p = vec + ch*roi;
579
580 for (size_t i=1; i<roi-2; i++)
581 {
582 if (p[i]-p[i-1]>25 && p[i]-p[i+1]>25)
583 {
584 p[i] = (p[i-1]+p[i+1])/2;
585 }
586
587 if (p[i]-p[i-1]>22 && fabs(p[i]-p[i+1])<4 && p[i+1]-p[i+2]>22)
588 {
589 p[i] = (p[i-1]+p[i+2])/2;
590 p[i+1] = p[i];
591 }
592 }
593 }
594 }
595
596 static void RemoveSpikes2(float *vec, uint32_t roi)//from Werner
597 {
598 if (roi<4)
599 return;
600
601 for (size_t ch=0; ch<1440; ch++)
602 {
603 float *p = vec + ch*roi;
604
605 std::vector<float> Ameas(p, p+roi);
606
607 std::vector<float> diff(roi);
608 for (size_t i=1; i<roi-1; i++)
609 diff[i] = (p[i-1] + p[i+1])/2 - p[i];
610
611 //std::vector<float> N1mean(roi);
612 //for (size_t i=1; i<roi-1; i++)
613 // N1mean[i] = (p[i-1] + p[i+1])/2;
614
615 const float fract = 0.8;
616
617 for (size_t i=0; i<roi-3; i++)
618 {
619 if (diff[i]<5)
620 continue;
621
622 if (Ameas[i+2] - (Ameas[i] + Ameas[i+3])/2 > 10)
623 {
624 p[i+1]= (Ameas[i+3] - Ameas[i])/3 + Ameas[i];
625 p[i+2]= 2*(Ameas[i+3] - Ameas[i])/3 + Ameas[i];
626
627 i += 3;
628
629 continue;
630 }
631
632 if ( (diff[i+1]<-diff[i]*fract*2) && (diff[i+2]>10) )
633 {
634 p[i+1] = (Ameas[i]+Ameas[i+2])/2;
635 diff[i+2] = (p[i+1] + Ameas[i+3])/2 - Ameas[i+2];
636
637 i += 2;
638 }
639
640 // const float x = Ameas[i] - N1mean[i];
641 // if (x > -5.)
642 // continue;
643
644 // if (Ameas[i+2] - (Ameas[i] + Ameas[i+3])/2. > 10.)
645 // {
646 // p[i+1]= (Ameas[i+3] - Ameas[i])/3 + Ameas[i];
647 // p[i+2]= 2*(Ameas[i+3] - Ameas[i])/3 + Ameas[i];
648 // i += 3;
649 // continue;
650 // }
651
652 // const float xp = Ameas[i+1] - N1mean[i+1];
653 // const float xpp = Ameas[i+2] - N1mean[i+2];
654
655 // if ( (xp > -2.*x*fract) && (xpp < -10.) )
656 // {
657 // p[i+1] = N1mean[i+1];
658 // N1mean[i+2] = Ameas[i+1] - Ameas[i+3]/2;
659 //
660 // i += 2;
661 // }
662 }
663 }
664 }
665
666 static void RemoveSpikes3(float *vec, uint32_t roi)//from Werner
667 {
668 const float SingleCandidateTHR = -10.;
669 const float DoubleCandidateTHR = -5.;
670
671 const std::vector<float> src(vec, vec+roi);
672
673 std::vector<float> diff(roi);
674 for (size_t i=1; i<roi-1; i++)
675 diff[i] = src[i] - (src[i-1] + src[i+1])/2;
676
677 // find the spike and replace it by mean value of neighbors
678 for (unsigned int i=1; i<roi-3; i++)
679 {
680 // Speed up (no leading edge)
681 if (diff[i]>=DoubleCandidateTHR)
682 continue;
683
684 //bool checkDouble = false;
685
686 // a single spike candidate
687 if (diff[i]<SingleCandidateTHR)
688 {
689 // check consistency with a single channel spike
690 if (diff[i+1] > -1.6*diff[i])
691 {
692 vec[i+1] = (src[i] + src[i+2]) / 2;
693
694 i += 2;
695
696 /*** NEW ***/
697 continue;
698 /*** NEW ***/
699 }
700 /*
701 else
702 {
703 // do nothing - not really a single spike,
704 // but check if it is a double
705 checkDouble = true;
706 }*/
707 }
708
709 // a double spike candidate
710 //if (diff[i]>DoubleCandidateTHR || checkDouble == 1)
711 {
712 // check the consistency with a double spike
713 if ((diff[i+1] > -DoubleCandidateTHR) &&
714 (diff[i+2] > -DoubleCandidateTHR))
715 {
716 vec[i+1] = (src[i+3] - src[i])/3 + src[i];
717 vec[i+2] = 2*(src[i+3] - src[i])/3 + src[i];
718
719 //vec[i] = (src[i-1] + src[i+2]) / 2.;
720 //vec[i+1] = (src[i-1] + src[i+2]) / 2.;
721
722 //do not care about the next sample it was the spike
723 i += 3;
724 }
725 }
726 }
727 }
728
729 static void SlidingAverage(float *const vec, const uint32_t roi, const uint16_t w)
730 {
731 if (w==0 || w>roi)
732 return;
733
734 for (float *pix=vec; pix<vec+1440*roi; pix += roi)
735 {
736 for (float *ptr=pix; ptr<pix+roi-w; ptr++)
737 {
738 for (float *p=ptr+1; p<ptr+w; p++)
739 *ptr += *p;
740 *ptr /= w;
741 }
742 }
743 }
744
745 std::pair<std::vector<double>,std::vector<double> > GetSampleStats() const
746 {
747 if (fNumEntries==0)
748 return make_pair(std::vector<double>(),std::vector<double>());
749
750 std::vector<double> mean(fSum.size());
751 std::vector<double> error(fSum.size());
752
753 std::vector<int64_t>::const_iterator it = fSum.begin();
754 std::vector<int64_t>::const_iterator i2 = fSum2.begin();
755 std::vector<double>::iterator im = mean.begin();
756 std::vector<double>::iterator ie = error.begin();
757
758 while (it!=fSum.end())
759 {
760 *im = /*cnt<fResult.size() ? fResult[cnt] :*/ double(*it)/fNumEntries;
761 *ie = sqrt(double(*i2*int64_t(fNumEntries) - *it * *it))/fNumEntries;
762
763 im++;
764 ie++;
765 it++;
766 i2++;
767 }
768
769
770 /*
771 valarray<double> ...
772
773 mean /= fNumEntries;
774 error = sqrt(error/fNumEntries - mean*mean);
775 */
776
777 return make_pair(mean, error);
778 }
779
780 void GetSampleStats(float *ptr, float scale) const
781 {
782 const size_t sz = fNumSamples*fNumChannels;
783
784 if (fNumEntries==0)
785 {
786 memset(ptr, 0, sizeof(float)*sz*2);
787 return;
788 }
789
790 std::vector<int64_t>::const_iterator it = fSum.begin();
791 std::vector<int64_t>::const_iterator i2 = fSum2.begin();
792
793 while (it!=fSum.end())
794 {
795 *ptr = scale*double(*it)/fNumEntries;
796 *(ptr+sz) = scale*sqrt(double(*i2*int64_t(fNumEntries) - *it * *it))/fNumEntries;
797
798 ptr++;
799 it++;
800 i2++;
801 }
802 }
803
804 static double GetPixelStats(float *ptr, const float *data, uint16_t roi)
805 {
806 if (roi==0)
807 return -1;
808
809 // Skip first 10 samples
810 const uint beg = roi>10 ? 10 : 0;
811 const uint end = roi-beg>5 ? roi-5 : roi;
812 const uint len = end-beg;
813
814 double max = 0;
815 double patch = 0;
816 for (uint i=0; i<1440; i++)
817 {
818 const float *vec = data+i*roi;
819
820 uint pos = beg;
821 double sum = vec[beg];
822 double sum2 = vec[beg]*vec[beg];
823
824 for (uint j=beg+1; j<end; j++)
825 {
826 sum += vec[j];
827 sum2 += vec[j]*vec[j];
828
829 if (vec[j]>vec[pos])
830 pos = j;
831 }
832 sum /= len;
833 sum2 /= len;
834
835 if (i%9!=8 && vec[pos]>0)
836 patch += vec[pos];
837
838 if (i%9==8)
839 {
840 patch /= 8;
841 if (patch > max)
842 max = patch;
843 patch = 0;
844 }
845
846 //if (i%9!=8 && vec[pos]>max)
847 // max = vec[pos];
848
849 *(ptr+0*1440+i) = sum;
850 *(ptr+1*1440+i) = sqrt(sum2 - sum * sum);
851 *(ptr+2*1440+i) = vec[pos];
852 *(ptr+3*1440+i) = pos;
853 }
854
855 return max;
856 }
857
858 static void GetPixelMax(float *max, const float *data, uint16_t roi, int32_t first, int32_t last)
859 {
860 if (roi==0 || first<0 || last<0 || first>=roi || last>=roi || last<first)
861 return;
862
863 for (int i=0; i<1440; i++)
864 {
865 const float *beg = data+i*roi+first;
866 const float *end = data+i*roi+last;
867
868 const float *pmax = beg;
869
870 for (const float *ptr=beg+1; ptr<=end; ptr++)
871 if (*ptr>*pmax)
872 pmax = ptr;
873
874 max[i] = *pmax;
875 }
876 }
877
878 const std::vector<int64_t> &GetSum() const { return fSum; }
879
880 uint64_t GetNumEntries() const { return fNumEntries; }
881};
882
883class DrsCalibrateTime
884{
885public:
886 uint64_t fNumEntries;
887
888 size_t fNumSamples;
889 size_t fNumChannels;
890
891 std::vector<std::pair<double, double>> fStat;
892
893public:
894 DrsCalibrateTime() : fNumEntries(0), fNumSamples(0), fNumChannels(0)
895 {
896 InitSize(160, 1024);
897 }
898
899 DrsCalibrateTime(const DrsCalibrateTime &p) : fNumEntries(p.fNumEntries), fNumSamples(p.fNumSamples), fNumChannels(p.fNumChannels), fStat(p.fStat)
900 {
901 }
902 virtual ~DrsCalibrateTime()
903 {
904 }
905
906 double Sum(uint32_t i) const { return fStat[i].first; }
907 double W(uint32_t i) const { return fStat[i].second; }
908
909 virtual void InitSize(uint16_t channels, uint16_t samples)
910 {
911 fNumChannels = channels;
912 fNumSamples = samples;
913
914 fNumEntries = 0;
915
916 fStat.clear();
917
918 fStat.resize(samples*channels);
919 }
920
921 void AddT(const float *val, const int16_t *start, signed char edge=0)
922 {
923 if (fNumSamples!=1024 || fNumChannels!=160)
924 return;
925
926 // Rising or falling edge detection has the advantage that
927 // we are much less sensitive to baseline shifts
928
929 for (size_t ch=0; ch<160; ch++)
930 {
931 const size_t tm = ch*9+8;
932
933 const int16_t spos = start[tm];
934 if (spos<0)
935 continue;
936
937 const size_t pos = ch*1024;
938
939 double p_prev = 0;
940 int32_t i_prev = -1;
941
942 for (size_t i=0; i<1024-1; i++)
943 {
944 const size_t rel = tm*1024 + i;
945
946 const float &v0 = val[rel]; //-avg;
947 const float &v1 = val[rel+1];//-avg;
948
949 // If edge is positive ignore all falling edges
950 if (edge>0 && v0>0)
951 continue;
952
953 // If edge is negative ignore all falling edges
954 if (edge<0 && v0<0)
955 continue;
956
957 // Check if there is a zero crossing
958 if ((v0<0 && v1<0) || (v0>0 && v1>0))
959 continue;
960
961 // Calculate the position p of the zero-crossing
962 // within the interval [rel, rel+1] relative to rel
963 // by linear interpolation.
964 const double p = v0==v1 ? 0.5 : v0/(v0-v1);
965
966 // If this was at least the second zero-crossing detected
967 if (i_prev>=0)
968 {
969 // Calculate the distance l between the
970 // current and the last zero-crossing
971 const double l = i+p - (i_prev+p_prev);
972
973 // By summation, the average length of each
974 // cell is calculated. For the first and last
975 // fraction of a cell, the fraction is applied
976 // as a weight.
977 const double w0 = 1-p_prev;
978 fStat[pos+(spos+i_prev)%1024].first += w0*l;
979 fStat[pos+(spos+i_prev)%1024].second += w0;
980
981 for (size_t k=i_prev+1; k<i; k++)
982 {
983 fStat[pos+(spos+k)%1024].first += l;
984 fStat[pos+(spos+k)%1024].second += 1;
985 }
986
987 const double w1 = p;
988 fStat[pos+(spos+i)%1024].first += w1*l;
989 fStat[pos+(spos+i)%1024].second += w1;
990 }
991
992 // Remember this zero-crossing position
993 p_prev = p;
994 i_prev = i;
995 }
996 }
997 fNumEntries++;
998 }
999
1000 void FillEmptyBins()
1001 {
1002 for (int ch=0; ch<160; ch++)
1003 {
1004 const auto beg = fStat.begin() + ch*1024;
1005 const auto end = beg + 1024;
1006
1007 double avg = 0;
1008 uint32_t num = 0;
1009 for (auto it=beg; it!=end; it++)
1010 {
1011 if (it->second<fNumEntries-0.5)
1012 continue;
1013
1014 avg += it->first / it->second;
1015 num++;
1016 }
1017 avg /= num;
1018
1019 for (auto it=beg; it!=end; it++)
1020 {
1021 if (it->second>=fNumEntries-0.5)
1022 continue;
1023
1024 // {
1025 // result[i+1].first = *is2;
1026 // result[i+1].second = *iw2;
1027 // }
1028 // else
1029 // {
1030 it->first = avg*fNumEntries;
1031 it->second = fNumEntries;
1032 // }
1033 }
1034 }
1035 }
1036
1037 DrsCalibrateTime GetComplete() const
1038 {
1039 DrsCalibrateTime rc(*this);
1040 rc.FillEmptyBins();
1041 return rc;
1042 }
1043
1044 void CalcResult()
1045 {
1046 for (int ch=0; ch<160; ch++)
1047 {
1048 const auto beg = fStat.begin() + ch*1024;
1049 const auto end = beg + 1024;
1050
1051 // First calculate the average length s of a single
1052 // zero-crossing interval in the whole range [0;1023]
1053 // (which is identical to the/ wavelength of the
1054 // calibration signal)
1055 double s = 0;
1056 double w = 0;
1057 for (auto it=beg; it!=end; it++)
1058 {
1059 s += it->first;
1060 w += it->second;
1061 }
1062 s /= w;
1063
1064 // Dividing the average length s of the zero-crossing
1065 // interval in the range [0;1023] by the average length
1066 // in the interval [0;n] yields the relative size of
1067 // the interval in the range [0;n].
1068 //
1069 // Example:
1070 // Average [0;1023]: 10.00 (global interval size in samples)
1071 // Average [0;512]: 8.00 (local interval size in samples)
1072 //
1073 // Globally, on average one interval is sampled by 10 samples.
1074 // In the sub-range [0;512] one interval is sampled on average
1075 // by 8 samples.
1076 // That means that the interval contains 64 periods, while
1077 // in the ideal case (each sample has the same length), it
1078 // should contain 51.2 periods.
1079 // So, the sampling position 512 corresponds to a time 640,
1080 // while in the ideal case with equally spaces samples,
1081 // it would correspond to a time 512.
1082 //
1083 // The offset (defined as 'ideal - real') is then calculated
1084 // as 512*(1-10/8) = -128, so that the time is calculated as
1085 // 'sampling position minus offset'
1086 //
1087 double sumw = 0;
1088 double sumv = 0;
1089 int n = 0;
1090
1091 // Sums about many values are numerically less stable than
1092 // just sums over less. So we do the exercise from both sides.
1093 // First from the left
1094 for (auto it=beg; it!=end-512; it++, n++)
1095 {
1096 const double valv = it->first;
1097 const double valw = it->second;
1098
1099 it->first = sumv>0 ? n*(1-s*sumw/sumv) : 0;
1100
1101 sumv += valv;
1102 sumw += valw;
1103 }
1104
1105 sumw = 0;
1106 sumv = 0;
1107 n = 1;
1108
1109 // Second from the right
1110 for (auto it=end-1; it!=beg-1+512; it--, n++)
1111 {
1112 const double valv = it->first;
1113 const double valw = it->second;
1114
1115 sumv += valv;
1116 sumw += valw;
1117
1118 it->first = sumv>0 ? n*(s*sumw/sumv-1) : 0;
1119 }
1120
1121 // A crosscheck has shown, that the values from the left
1122 // and right perfectly agree over the whole range. This means
1123 // the a calculation from just one side would be enough, but
1124 // doing it from both sides might still make the numerics
1125 // a bit more stable.
1126 }
1127 }
1128
1129 DrsCalibrateTime GetResult() const
1130 {
1131 DrsCalibrateTime rc(*this);
1132 rc.CalcResult();
1133 return rc;
1134 }
1135
1136 double Offset(uint32_t ch, double pos) const
1137 {
1138 const auto p = fStat.begin() + ch*1024;
1139
1140 const uint32_t f = floor(pos);
1141
1142 const double v0 = p[f].first;
1143 const double v1 = p[(f+1)%1024].first;
1144
1145 return v0 + fmod(pos, 1)*(v1-v0);
1146 }
1147
1148 double Calib(uint32_t ch, double pos) const
1149 {
1150 return pos-Offset(ch, pos);
1151 }
1152};
1153
1154struct DrsCalibration
1155{
1156 std::vector<int32_t> fOffset;
1157 std::vector<int64_t> fGain;
1158 std::vector<int64_t> fTrgOff;
1159
1160 uint64_t fNumOffset;
1161 uint64_t fNumGain;
1162 uint64_t fNumTrgOff;
1163
1164 uint32_t fStep;
1165 uint16_t fRoi; // Region of interest for trgoff
1166 uint16_t fNumTm; // Number of time marker channels in trgoff
1167
1168 std::string fDateObs;
1169 std::string fDateRunBeg[3];
1170 std::string fDateRunEnd[3];
1171 std::string fDateEnd;
1172
1173// uint16_t fDAC[8];
1174
1175 DrsCalibration() :
1176 fOffset(1440*1024, 0),
1177 fGain(1440*1024, 4096),
1178 fTrgOff (1600*1024, 0),
1179 fNumOffset(1),
1180 fNumGain(2000),
1181 fNumTrgOff(1),
1182 fStep(0),
1183 fDateObs("1970-01-01T00:00:00"),
1184 fDateEnd("1970-01-01T00:00:00")
1185 {
1186 for (int i=0; i<3; i++)
1187 {
1188 fDateRunBeg[i] = "1970-01-01T00:00:00";
1189 fDateRunEnd[i] = "1970-01-01T00:00:00";
1190 }
1191 }
1192
1193 DrsCalibration(const DrsCalibration &cpy) :
1194 fOffset(cpy.fOffset),
1195 fGain(cpy.fGain),
1196 fTrgOff(cpy.fTrgOff),
1197 fNumOffset(cpy.fNumOffset),
1198 fNumGain(cpy.fNumGain),
1199 fNumTrgOff(cpy.fNumTrgOff),
1200 fStep(cpy.fStep),
1201 fRoi(cpy.fRoi),
1202 fNumTm(cpy.fNumTm),
1203 fDateObs(cpy.fDateObs),
1204 fDateRunBeg(cpy.fDateRunBeg),
1205 fDateRunEnd(cpy.fDateRunEnd),
1206 fDateEnd(cpy.fDateEnd)
1207 {
1208 }
1209
1210 void Clear()
1211 {
1212 // Default gain:
1213 // 0.575*[45590]*2.5V / 2^16 = 0.99999 V
1214 fOffset.assign(1440*1024, 0);
1215 fGain.assign (1440*1024, 4096);
1216 fTrgOff.assign(1600*1024, 0);
1217
1218 fNumOffset = 1;
1219 fNumGain = 2000;
1220 fNumTrgOff = 1;
1221
1222 fStep = 0;
1223
1224 fDateObs = "1970-01-01T00:00:00";
1225 fDateEnd = "1970-01-01T00:00:00";
1226
1227 for (int i=0; i<3; i++)
1228 {
1229 fDateRunBeg[i] = "1970-01-01T00:00:00";
1230 fDateRunEnd[i] = "1970-01-01T00:00:00";
1231 }
1232 }
1233
1234 std::string ReadFitsImp(const std::string &str, std::vector<float> &vec)
1235 {
1236#ifndef __MARS__
1237 std::fits file(str);
1238#else
1239 fits file(str);
1240#endif
1241 if (!file)
1242 {
1243 std::ostringstream msg;
1244 msg << "Could not open file '" << str << "': " << strerror(errno);
1245 return msg.str();
1246 }
1247
1248 if (file.GetStr("TELESCOP")!="FACT")
1249 {
1250 std::ostringstream msg;
1251 msg << "Reading '" << str << "' failed: Not a valid FACT file (TELESCOP not FACT in header)";
1252 return msg.str();
1253 }
1254
1255 if (!file.HasKey("STEP"))
1256 {
1257 std::ostringstream msg;
1258 msg << "Reading '" << str << "' failed: Is not a DRS calib file (STEP not found in header)";
1259 return msg.str();
1260 }
1261
1262 if (file.GetNumRows()!=1)
1263 {
1264 std::ostringstream msg;
1265 msg << "Reading '" << str << "' failed: Number of rows in table is not 1.";
1266 return msg.str();
1267 }
1268
1269 fStep = file.GetUInt("STEP");
1270 fNumOffset = file.GetUInt("NBOFFSET");
1271 fNumGain = file.GetUInt("NBGAIN");
1272 fNumTrgOff = file.GetUInt("NBTRGOFF");
1273 fRoi = file.GetUInt("NROI");
1274 fNumTm = file.HasKey("NTM") ? file.GetUInt("NTM") : 0;
1275
1276 if (file.HasKey("DATE-OBS"))
1277 fDateObs = file.GetStr("DATE-OBS");
1278 if (file.HasKey("DATE-END"))
1279 fDateEnd = file.GetStr("DATE-END");
1280
1281 if (file.HasKey("RUN0-BEG"))
1282 fDateRunBeg[0]= file.GetStr("RUN0-BEG");
1283 if (file.HasKey("RUN1-BEG"))
1284 fDateRunBeg[1]= file.GetStr("RUN1-BEG");
1285 if (file.HasKey("RUN2-BEG"))
1286 fDateRunBeg[2]= file.GetStr("RUN2-BEG");
1287 if (file.HasKey("RUN0-END"))
1288 fDateRunEnd[0]= file.GetStr("RUN0-END");
1289 if (file.HasKey("RUN1-END"))
1290 fDateRunEnd[1]= file.GetStr("RUN1-END");
1291 if (file.HasKey("RUN2-END"))
1292 fDateRunEnd[2]= file.GetStr("RUN2-END");
1293/*
1294 fDAC[0] = file.GetUInt("DAC_A");
1295 fDAC[1] = file.GetUInt("DAC_B");
1296 fDAC[4] = file.GetUInt("DAC_C");
1297*/
1298 vec.resize(1440*1024*4 + (1440+fNumTm)*fRoi*2 + 4);
1299
1300 float *base = vec.data();
1301
1302 reinterpret_cast<uint32_t*>(base)[0] = fRoi;
1303
1304 file.SetPtrAddress("RunNumberBaseline", base+1, 1);
1305 file.SetPtrAddress("RunNumberGain", base+2, 1);
1306 file.SetPtrAddress("RunNumberTriggerOffset", base+3, 1);
1307 file.SetPtrAddress("BaselineMean", base+4+0*1024*1440, 1024*1440);
1308 file.SetPtrAddress("BaselineRms", base+4+1*1024*1440, 1024*1440);
1309 file.SetPtrAddress("GainMean", base+4+2*1024*1440, 1024*1440);
1310 file.SetPtrAddress("GainRms", base+4+3*1024*1440, 1024*1440);
1311 file.SetPtrAddress("TriggerOffsetMean", base+4+4*1024*1440, fRoi*1440);
1312 file.SetPtrAddress("TriggerOffsetRms", base+4+4*1024*1440+fRoi*1440, fRoi*1440);
1313 if (fNumTm>0)
1314 {
1315 file.SetPtrAddress("TriggerOffsetTMMean", base+4+4*1024*1440+ 2*fRoi*1440, fRoi*fNumTm);
1316 file.SetPtrAddress("TriggerOffsetTMRms", base+4+4*1024*1440+ 2*fRoi*1440+ fRoi*fNumTm, fRoi*fNumTm);
1317 }
1318
1319 if (!file.GetNextRow())
1320 {
1321 std::ostringstream msg;
1322 msg << "Reading data from " << str << " failed.";
1323 return msg.str();
1324 }
1325/*
1326 fDAC[2] = fDAC[1];
1327 fDAC[4] = fDAC[1];
1328
1329 fDAC[5] = fDAC[4];
1330 fDAC[6] = fDAC[4];
1331 fDAC[7] = fDAC[4];
1332*/
1333 fOffset.resize(1024*1440);
1334 fGain.resize(1024*1440);
1335
1336 fTrgOff.resize(fRoi*(1440+fNumTm));
1337
1338 // Convert back to ADC counts: 256/125 = 4096/2000
1339 // Convert back to sum (mean * num_entries)
1340 for (int i=0; i<1024*1440; i++)
1341 {
1342 fOffset[i] = fNumOffset *256*base[i+1024*1440*0+4]/125;
1343 fGain[i] = fNumOffset*fNumGain*256*base[i+1024*1440*2+4]/125;
1344 }
1345
1346 for (int i=0; i<fRoi*1440; i++)
1347 fTrgOff[i] = fNumOffset*fNumTrgOff*256*base[i+1024*1440*4+4]/125;
1348
1349 for (int i=0; i<fRoi*fNumTm; i++)
1350 fTrgOff[i+1440*fRoi] = fNumOffset*fNumTrgOff*256*base[i+1024*1440*4+2*fRoi*1440+4]/125;
1351
1352
1353 // DAC: 0..2.5V == 0..65535
1354 // V-mV: 1000
1355 //fNumGain *= 2500*50000;
1356 //for (int i=0; i<1024*1440; i++)
1357 // fGain[i] *= 65536;
1358 if (fStep==0)
1359 {
1360 for (int i=0; i<1024*1440; i++)
1361 fGain[i] = fNumOffset*4096;
1362 }
1363 else
1364 {
1365 fNumGain *= 1953125;
1366 for (int i=0; i<1024*1440; i++)
1367 fGain[i] *= 1024;
1368 }
1369
1370 // Now mark the stored DRS data as "officially valid"
1371 // However, this is not thread safe. It only ensures that
1372 // this data is not used before it is completely and correctly
1373 // read.
1374 fStep++;
1375
1376 return std::string();
1377 }
1378
1379 std::string WriteFitsImp(const std::string &filename, const std::vector<float> &vec, uint32_t night=0) const
1380 {
1381 const size_t n = 1440*1024*4 + 1440*fRoi*2 + fNumTm*fRoi*2 + 3;
1382
1383#ifndef __MARS__
1384 std::ofits file(filename.c_str());
1385#else
1386 ofits file(filename.c_str());
1387#endif
1388 if (!file)
1389 {
1390 std::ostringstream msg;
1391 msg << "Could not open file '" << filename << "': " << strerror(errno);
1392 return msg.str();
1393 }
1394
1395 file.AddColumnInt("RunNumberBaseline");
1396 file.AddColumnInt("RunNumberGain");
1397 file.AddColumnInt("RunNumberTriggerOffset");
1398
1399 file.AddColumnFloat(1024*1440, "BaselineMean", "mV");
1400 file.AddColumnFloat(1024*1440, "BaselineRms", "mV");
1401 file.AddColumnFloat(1024*1440, "GainMean", "mV");
1402 file.AddColumnFloat(1024*1440, "GainRms", "mV");
1403 file.AddColumnFloat(fRoi*1440, "TriggerOffsetMean", "mV");
1404 file.AddColumnFloat(fRoi*1440, "TriggerOffsetRms", "mV");
1405 file.AddColumnFloat(fRoi*fNumTm, "TriggerOffsetTMMean", "mV");
1406 file.AddColumnFloat(fRoi*fNumTm, "TriggerOffsetTMRms", "mV");
1407
1408 file.SetDefaultKeys();
1409 if (night>0)
1410 file.SetInt("NIGHT", night, "Night as int");
1411
1412 file.SetStr("DATE-OBS", fDateObs, "First event of whole DRS calibration");
1413 file.SetStr("DATE-END", fDateEnd, "Last event of whole DRS calibration");
1414 file.SetStr("RUN0-BEG", fDateRunBeg[0], "First event of run 0");
1415 file.SetStr("RUN1-BEG", fDateRunBeg[1], "First event of run 1");
1416 file.SetStr("RUN2-BEG", fDateRunBeg[2], "First event of run 2");
1417 file.SetStr("RUN0-END", fDateRunEnd[0], "Last event of run 0");
1418 file.SetStr("RUN1-END", fDateRunEnd[1], "Last event of run 1");
1419 file.SetStr("RUN2-END", fDateRunEnd[2], "Last event of run 2");
1420
1421 file.SetInt("STEP", fStep, "");
1422
1423 file.SetInt("ADCRANGE", 2000, "Dynamic range of the ADC in mV");
1424 file.SetInt("DACRANGE", 2500, "Dynamic range of the DAC in mV");
1425 file.SetInt("ADC", 12, "Resolution of ADC in bits");
1426 file.SetInt("DAC", 16, "Resolution of DAC in bits");
1427 file.SetInt("NPIX", 1440, "Number of channels in the camera");
1428 file.SetInt("NTM", fNumTm, "Number of time marker channels");
1429 file.SetInt("NROI", fRoi, "Region of interest");
1430
1431 file.SetInt("NBOFFSET", fNumOffset, "Num of entries for offset calibration");
1432 file.SetInt("NBGAIN", fNumGain/1953125, "Num of entries for gain calibration");
1433 file.SetInt("NBTRGOFF", fNumTrgOff, "Num of entries for trigger offset calibration");
1434
1435 // file.WriteKeyNT("DAC_A", fData.fDAC[0], "Level of DAC 0 in DAC counts") ||
1436 // file.WriteKeyNT("DAC_B", fData.fDAC[1], "Leval of DAC 1-3 in DAC counts") ||
1437 // file.WriteKeyNT("DAC_C", fData.fDAC[4], "Leval of DAC 4-7 in DAC counts") ||
1438
1439 file.WriteTableHeader("DrsCalibration");
1440
1441 if (!file.WriteRow(vec.data()+1, n*sizeof(float)))
1442 {
1443 std::ostringstream msg;
1444 msg << "Writing data to " << filename << " failed.";
1445 return msg.str();
1446 }
1447
1448 return std::string();
1449 }
1450
1451
1452 std::string ReadFitsImp(const std::string &str)
1453 {
1454 std::vector<float> vec;
1455 return ReadFitsImp(str, vec);
1456 }
1457
1458 bool IsValid() { return fStep>2; }
1459
1460 bool Apply(float *vec, const int16_t *val, const int16_t *start, uint32_t roi)
1461 {
1462 if (roi!=fRoi)
1463 {
1464 for (size_t ch=0; ch<1440; ch++)
1465 {
1466 const size_t pos = ch*roi;
1467 const size_t drs = ch*1024;
1468
1469 DrsCalibrate::ApplyCh(vec+pos, val+pos, start[ch], roi,
1470 fOffset.data()+drs, fNumOffset,
1471 fGain.data() +drs, fNumGain);
1472 }
1473
1474 return false;
1475 }
1476
1477 for (size_t ch=0; ch<1440; ch++)
1478 {
1479 const size_t pos = ch*fRoi;
1480 const size_t drs = ch*1024;
1481
1482 DrsCalibrate::ApplyCh(vec+pos, val+pos, start[ch], roi,
1483 fOffset.data()+drs, fNumOffset,
1484 fGain.data() +drs, fNumGain,
1485 fTrgOff.data()+pos, fNumTrgOff);
1486 }
1487
1488 for (size_t ch=0; ch<fNumTm; ch++)
1489 {
1490 const size_t pos = (ch+1440)*fRoi;
1491 const size_t drs = (ch*9+8)*1024;
1492
1493 DrsCalibrate::ApplyCh(vec+pos, val+pos, start[ch], roi,
1494 fOffset.data()+drs, fNumOffset,
1495 fGain.data() +drs, fNumGain,
1496 fTrgOff.data()+pos, fNumTrgOff);
1497 }
1498
1499 return true;
1500 }
1501};
1502
1503#endif
Note: See TracBrowser for help on using the repository browser.