source: fact/tools/rootmacros/fpeak_cdf.C@ 12363

Last change on this file since 12363 was 12357, checked in by neise, 13 years ago
starting point for gain fit
  • Property svn:executable set to *
File size: 17.9 KB
Line 
1#include <TROOT.h>
2#include <TCanvas.h>
3#include <TProfile.h>
4#include <TTimer.h>
5#include <TH1F.h>
6#include <TH2F.h>
7#include <Getline.h>
8#include <TLine.h>
9#include <TBox.h>
10#include <TMath.h>
11#include <TFile.h>
12#include <TStyle.h>
13
14#include <stdio.h>
15#include <stdint.h>
16#include <cstdio>
17#include <deque>
18
19#define NPIX 1440
20#define NCELL 1024
21#define FAD_MAX_SAMPLES 1024
22
23#define HAVE_ZLIB
24#include "fits.h"
25#include "FOpenCalibFile.c"
26
27#include "discriminator.h"
28#include "discriminator.C"
29#include "zerosearch.h"
30#include "zerosearch.C"
31#include "factfir.C"
32
33
34vector<int16_t> AllPixelDataVector;
35vector<int16_t> StartCellVector;
36
37unsigned int CurrentEventID;
38
39bool breakout=false;
40
41size_t ROIxNOP;
42UInt_t NumberOfPixels;
43UInt_t RegionOfInterest;
44int NEvents;
45
46size_t drs_n;
47vector<float> drs_basemean;
48vector<float> drs_gainmean;
49vector<float> drs_triggeroffsetmean;
50
51int FOpenDataFile( fits & );
52
53
54vector<float> Ameas(FAD_MAX_SAMPLES); // copy of the data (measured amplitude
55vector<float> N1mean(FAD_MAX_SAMPLES); // mean of the +1 -1 ch neighbors
56vector<float> Vcorr(FAD_MAX_SAMPLES); // corrected Values
57vector<float> Vdiff(FAD_MAX_SAMPLES); // numerical derivative
58
59vector<float> Vslide(FAD_MAX_SAMPLES); // sliding average result
60vector<float> Vcfd(FAD_MAX_SAMPLES); // CDF result
61vector<float> Vcfd2(FAD_MAX_SAMPLES); // CDF result + 2nd sliding average
62
63
64float getValue( int, int );
65void computeN1mean( int );
66void removeSpikes( int );
67
68// histograms
69const int NumberOfDebugHistoTypes = 7;
70const unsigned int
71 Ameas_ = 0,
72 N1mean_ = 1,
73 Vcorr_ = 2,
74 Vtest_ = 3,
75 Vslide_ = 4,
76 Vcfd_ = 5,
77 Vcfd2_ = 6;
78
79TH1F* h;
80TH1F *debugHistos;
81TH2F* hStartCell; // id of the DRS physical pipeline cell where readout starts
82 // x = pixel id, y = DRS cell id
83
84TH1F *hBaseline[ NPIX ]; // histograms for baseline extraction
85TH1F *hMeanBsl, *hpltMeanBsl;
86TH1F *hRmsBsl, *hpltRmsBsl;
87TH2F * hAmplSpek_cfd;
88TH2F * hAmplSpek_discri;
89TH2F * hTemp_Array[1441];
90TObjArray hList;
91TObjArray hListBaseline, hListTemplates;
92
93void BookHistos( );
94void SaveHistograms( char * );
95
96int searchSinglesPeaks = 0;
97
98int fpeak(
99 char *datafilename = "../../20111011_055.fits.gz",
100 const char *drsfilename = "../../20111011_054.drs.fits.gz",
101 int nevents = -1,
102 int firstevent = 0,
103 int PixelID = -1,
104 bool spikeDebug = false,
105 int verbosityLevel = 1 // different verbosity levels can be implemented here
106 )
107{
108
109// Create (pointer to) Canvases, which are used in every run,
110// also in 'non-debug' runs
111 TCanvas * cSpektrum;
112 TCanvas * cStartCell;
113 TCanvas *cTemplate;
114 cSpektrum = new TCanvas("cSpektrum","Amplitude Spektra of different discriminators",10,10,400,400);
115 cSpektrum->Divide(1,2);
116 cStartCell = new TCanvas("cStartCell ", "The Startcells of this run", 10,410,400,400);
117 cTemplate = new TCanvas("cTemplate","Template of current Pixel",1,1,1600,1000);
118
119 // Canvases only need if spike Debug, but I want to deklare
120 // the pointers anyway ...
121 TCanvas *cRawAndSpikeRemoval = NULL;
122 TCanvas *cFiltered = NULL;
123
124
125 if (spikeDebug){
126 cRawAndSpikeRemoval = new TCanvas("cRawAndSpikeRemoval","DRS Waveform",410,10,400,400);
127 cRawAndSpikeRemoval->Divide(1, 2);
128 cFiltered = new TCanvas("cFiltered","filtered DRS Waveforms",410,410,400,400);
129 cFiltered->Divide(1, 2);
130 }
131
132gStyle->SetPalette(1,0);
133gROOT->SetStyle("Plain");
134// read FACT raw data
135// * remove spikes
136// * calculate baseline
137// * find peaks (CFD and normal discriminator)
138// * compute pulse height and pulse integral spektrum of the peaks
139
140// sliding window filter settings
141// int k_slide = 16;
142// vector<double> a_slide(k_slide, 1);
143// double b_slide = k_slide;
144
145// CFD filter settings
146 int k_cfd = 10;
147 vector<double> a_cfd(k_cfd, 0);
148 double b_cfd = 1.;
149 a_cfd[0]=-0.75;
150 a_cfd[k_cfd-1]=1.;
151
152// 2nd slinding window filter
153// int ks2 = 16;
154// vector<double> as2(ks2, 1);
155// double bs2 = ks2;
156
157// Open the data file
158 fits *datafile = new fits( datafilename );
159 if (!datafile) {
160 printf( "Could not open the file: %s\n", datafilename );
161 return 1;
162 }
163
164// access data
165 NEvents = FOpenDataFile( *datafile );
166 printf("number of events in file: %d\n", NEvents);
167 if ( nevents == -1 || nevents > NEvents ) nevents = NEvents; // -1 means all!
168
169//Get the DRS calibration
170 FOpenCalibFile( drsfilename,
171 drs_basemean,
172 drs_gainmean,
173 drs_triggeroffsetmean,
174 drs_n);
175
176//Check the sizes of the data columns
177 if (drs_n != 1474560) {
178 cerr << "error: DRS calib file has wrong ...erm...size ... drs_n is: "
179 << drs_n << endl;
180 cerr << " Aborting." << endl;
181 return 1;
182 }
183
184 if(ROIxNOP != 1474560)
185 {
186 cout << "warning: data_n should better be 1440x1024=1474560, but is "
187 << ROIxNOP << endl;
188 cout << "this script is not guaranteed to run under these "
189 <<" circumstances....any way ... it is never guaranteed." << endl;
190 }
191// Book the histograms
192
193 BookHistos( );
194
195 float calibratedVoltage;
196
197 for ( int ev = firstevent; ev < firstevent + nevents; ev++) {
198 // Get an Event --> consists of 1440 Pixel ...erm....data
199 datafile->GetRow( ev );
200
201 for ( int pix = 0; pix < 1440; pix++ ){
202
203 hStartCell->Fill( pix, StartCellVector[pix] );
204
205 // this is a stupid hack ... there is more code at the
206 // end of this loop to complete this hack ...
207 // beginning with if (PixelID != -1) as well.
208 if (PixelID != -1) {
209 pix = PixelID;
210 if (verbosityLevel > 0){
211 cout << "Processing Event number: " << CurrentEventID << "\t"
212 << "Pixel number: "<< pix << endl;
213 }
214 }
215
216 if (verbosityLevel > 0){
217 if (pix % 20 ==0){
218 cout << "Processing Event number: " << CurrentEventID << "\t"
219 << "Pixel number: "<< pix << endl;
220 }
221 }
222
223 // compute the DRs calibrated values and put them into Ameas[]
224 for ( unsigned int sl = 0; sl < RegionOfInterest; sl++){
225 calibratedVoltage = getValue( sl, pix);
226 if (verbosityLevel > 10){
227 printf("calibratedVoltage = %f\n", calibratedVoltage);
228 }
229 Ameas[ sl ] = calibratedVoltage;
230
231 // in case we want to plot this ... we need to put it into a
232 // Histgramm
233 if (spikeDebug){
234 debugHistos[Ameas_].SetBinContent(sl, calibratedVoltage);
235 }
236 }
237 // operates on Ameas[] and writes to N1mean[]
238 computeN1mean( RegionOfInterest );
239
240 // operates on Ameas[] and N1mean[], then writes to Vcorr[]
241 removeSpikes( RegionOfInterest );
242
243 // filter Vcorr with sliding average using FIR filter function
244 //factfir(b_slide , a_slide, k_slide, Vcorr, Vslide);
245 sliding_avg(Vcorr, Vslide, 8);
246 // filter Vslide with CFD using FIR filter function
247 factfir(b_cfd , a_cfd, k_cfd, Vslide, Vcfd);
248 // filter Vcfd with sliding average using FIR filter function
249 //factfir(bs2 , as2, ks2, Vcfd, Vcfd2);
250 sliding_avg(Vcfd, Vcfd2, 8);
251
252
253 // peaks in Ameas[] are found by searching for zero crossings
254 // in Vcfd2
255 // first Argument 1 means ... *rising* edge
256 // second Argument 1 means ... search with stepsize 1 ... 8 is okay as well
257 vector<Region> * zXings = zerosearch( Vcfd2 , 1 , 8);
258 // zXings means "zero cross ings"
259 //ShiftRegionBy(*zXings, -ks2/2);
260 EnlargeRegion(*zXings, 10, 10);
261 findAbsMaxInRegions(*zXings, Vslide);
262 removeMaximaBelow( *zXings, 11.0, 0);
263 removeMaximaAbove( *zXings, 14.0, 0);
264
265 if (zXings->size() != 0 ){
266 for (unsigned int i=0; i<zXings->size(); i++){
267 if (verbosityLevel > 1){
268 cout << zXings->at(i).maxPos << ":\t"
269 << zXings->at(i).maxVal <<endl;
270 }
271 hAmplSpek_cfd->Fill(pix, zXings->at(i).maxVal);
272
273 for (int j=-100; j<150; j++){
274 if (zXings->at(i).maxPos + j >= 0 && zXings->at(i).maxPos + j <= 1023)
275 hTemp_Array[pix]->Fill(j+101, Vslide[zXings->at(i).maxPos + j]);
276 }
277 }
278 }
279
280 if ( spikeDebug ){
281 for ( unsigned int sl = 0; sl < RegionOfInterest; sl++){
282 debugHistos[Vslide_].SetBinContent( sl, Vslide[sl] );
283 debugHistos[Vcfd_].SetBinContent( sl, Vcfd[sl] );
284 debugHistos[Vcfd2_].SetBinContent( sl, Vcfd2[sl] );
285 }
286
287 cRawAndSpikeRemoval->cd( 1);
288 gPad->SetGrid();
289 debugHistos[Ameas_].Draw();
290
291 cRawAndSpikeRemoval->cd( 2);
292 gPad->SetGrid();
293 debugHistos[Vcorr_].Draw();
294
295 cFiltered->cd(1);
296 gPad->SetGrid();
297 debugHistos[Vslide_].Draw();
298
299 TBox *OneBox;
300 vector<TBox*> MyBoxes;
301 for (unsigned int i=0; i<zXings->size(); i++){
302 OneBox = new TBox(
303 zXings->at(i).maxPos -10 ,
304 zXings->at(i).maxVal -0.5,
305 zXings->at(i).maxPos +10 ,
306 zXings->at(i).maxVal +0.5);
307 OneBox->SetLineColor(kBlue);
308 OneBox->SetLineWidth(1);
309 OneBox->SetFillStyle(0);
310 OneBox->SetFillColor(kRed);
311 MyBoxes.push_back(OneBox);
312 OneBox->Draw();
313 }
314
315 cFiltered->cd(2);
316 gPad->SetGrid();
317 debugHistos[Vcfd2_].Draw();
318 TLine *zeroline = new TLine(0, 0, 1024, 0);
319 zeroline->SetLineColor(kBlue);
320 zeroline->Draw();
321
322 cRawAndSpikeRemoval->Update();
323 cFiltered->Update();
324
325 cTemplate->cd();
326 hTemp_Array[pix]->Draw("COLZ");
327 cTemplate->Modified();
328 cTemplate->Update();
329
330 //Process gui events asynchronously during input
331 TTimer timer("gSystem->ProcessEvents();", 50, kFALSE);
332 timer.TurnOn();
333 TString input = Getline("Type 'q' to exit, <return> to go on: ");
334 timer.TurnOff();
335 if (input=="q\n") {
336 breakout=true;
337 }
338
339 //TODO!!!!!!!!!
340 // do some Garbage collection ...
341 // all the Objects on the heap should be deleted here.
342
343 }// end of if(spikeDebug)
344
345 delete zXings;
346
347 // this is the 2nd part of the ugly hack in the beginning.
348 // this makes sure, that the loop ends
349 if (PixelID != -1){
350 pix = 1440;
351 }
352
353
354 if (breakout)
355 break;
356
357 } // end of loop over pixels
358
359if (ev % 10 == 0){
360 cSpektrum->cd(1);
361 hAmplSpek_cfd->Draw("COLZ");
362 cSpektrum->cd(2);
363 hAmplSpek_discri->Draw("COLZ");
364 cSpektrum->Modified();
365 cSpektrum->Update();
366
367 // updating seems not to work ..
368 // debug cout
369 cStartCell->cd();
370 hStartCell->Draw();
371 cStartCell->Modified();
372 cStartCell->Update();
373
374 cTemplate->cd();
375 hTemp_Array[PixelID]->GetYaxis()->SetRangeUser(-10,40);
376 hTemp_Array[PixelID]->Draw("COLZ");
377 cTemplate->Modified();
378 cTemplate->Update();
379}
380
381
382 if (breakout)
383 break;
384 } // end of loop over pixels
385
386
387 SaveHistograms( datafilename );
388
389 return( 0 );
390}
391
392void removeSpikes(int Samples){
393
394 const float fract = 0.8;
395 float x, xp, xpp, x3p;
396
397 // assume that there are no spikes
398 for ( int i = 0; i < Samples; i++) Vcorr[i] = Ameas[i];
399
400// find the spike and replace it by mean value of neighbors
401 for ( int i = 0; i < Samples; i++) {
402
403 // printf("Vcorr[%d] = %f, Ameas[%d] = %f\n", i, Vcorr[ i ], i, Ameas[ i ] );
404
405 x = Ameas[i] - N1mean[i];
406
407 if ( x < -5. ){ // a spike candidate
408 // check consistency with a single channel spike
409 xp = Ameas[i+1] - N1mean[i+1];
410 xpp = Ameas[i+2] - N1mean[i+2];
411 x3p = Ameas[i+3] - N1mean[i+3];
412
413 // printf("candidates x[%d] = %f; xp = %f; xpp = %f, x3p = %f\n", i, x, xp, xpp, x3p);
414
415 if ( Ameas[i+2] - ( Ameas[i] + Ameas[i+3] )/2. > 10. ){
416 // printf("double spike candidate\n");
417 Vcorr[i+1] = ( Ameas[i] + Ameas[i+3] )/2.;
418 Vcorr[i+2] = ( Ameas[i] + Ameas[i+3] )/2.;
419 // printf("Vcorr[%d] = %f %f %f %f\n", i, Vcorr[i], Vcorr[i+1], Vcorr[i+2], Vcorr[ i+3 ]);
420 // printf("Ameas[%d] = %f %f %f %f\n", i, Ameas[ i ], Ameas[ i+1 ], Ameas[ i+2 ], Ameas[i+3]);
421 i = i + 3;
422 }
423 else{
424
425 if ( ( xp > -2.*x*fract ) && ( xpp < -10. ) ){
426 Vcorr[i+1] = N1mean[i+1];
427 // printf("Vcorr[%d] = %f %f %f\n", i, Vcorr[i], Vcorr[i+1], Vcorr[i+2]);
428 // N1mean[i+1] = (Ameas[i] - Ameas[i+2] / 2.);
429 N1mean[i+2] = (Ameas[i+1] - Ameas[i+3] / 2.);
430 i = i + 2;//do not care about the next sample it was the spike
431 }
432 // treatment for the end of the pipeline must be added !!!
433 }
434 }
435 else{
436 // do nothing
437 }
438 } // end of spike search and correction
439 for ( int i = 0; i < Samples; i++ ) debugHistos[ Vcorr_ ].SetBinContent( i, Vcorr[i] );
440}
441
442void computeN1mean( int Samples ){
443// compute the mean of the left and right neighbors of a channel
444
445 for( int i = 2; i < Samples - 2; i++){
446/* if (i == 0){ // use right sample es mean
447 N1mean[i] = Ameas[i+1];
448 }
449 else if ( i == Samples-1 ){ //use left sample as mean
450 N1mean[i] = Ameas[i-1];
451 }
452 else{
453 N1mean[i] = ( Ameas[i-1] + Ameas[i+1] ) / 2.;
454 }
455*/
456 N1mean[i] = ( Ameas[i-1] + Ameas[i+1] ) / 2.;
457 }
458} // end of computeN1mean computation
459
460float getValue( int slice, int pixel ){
461 const float dconv = 2000/4096.0;
462
463 float vraw, vcal;
464
465 unsigned int pixel_pt;
466 unsigned int slice_pt;
467 unsigned int cal_pt;
468 unsigned int drs_cal_offset;
469
470 // printf("pixel = %d, slice = %d\n", slice, pixel);
471
472 pixel_pt = pixel * RegionOfInterest;
473 slice_pt = pixel_pt + slice;
474 drs_cal_offset = ( slice + StartCellVector[ pixel ] )%RegionOfInterest;
475 cal_pt = pixel_pt + drs_cal_offset;
476
477 vraw = AllPixelDataVector[ slice_pt ] * dconv;
478 vcal = ( vraw - drs_basemean[ cal_pt ] - drs_triggeroffsetmean[ slice_pt ] ) / drs_gainmean[ cal_pt ]*1907.35;
479
480 return( vcal );
481}
482
483// booking and parameter settings for all histos
484void BookHistos( ){
485 // histograms for baseline extraction
486 char hName[500];
487 char hTitle[500];
488
489 TH1F *h;
490
491 for( int i = 0; i < NPIX; i++ ) {
492 sprintf(&hTitle[0],"all events all slices of pixel %d", i);
493 sprintf(&hName[0],"base%d", i);
494
495 h = new TH1F( hName, hTitle, 400, -99.5 ,100.5 );
496
497 h->GetXaxis()->SetTitle( "Sample value (mV)" );
498 h->GetYaxis()->SetTitle( "Entries / 0.5 mV" );
499 hListBaseline.Add( h );
500 hBaseline[i] = h;
501 }
502
503 hMeanBsl = new TH1F("histo_mean","Value of maximal probability",400,-99.5,100.5);
504 hMeanBsl->GetXaxis()->SetTitle( "max value (mV)" );
505 hMeanBsl->GetYaxis()->SetTitle( "Entries / 0.5 mV" );
506 hList.Add( hMeanBsl );
507
508 hpltMeanBsl = new TH1F("hplt_mean","Value of maximal probability",1440,-0.5,1439.5);
509 hpltMeanBsl->GetXaxis()->SetTitle( "pixel" );
510 hpltMeanBsl->GetYaxis()->SetTitle( "max value in mV" );
511 hList.Add( hpltMeanBsl );
512
513 hRmsBsl = new TH1F("histo_rms","RMS in mV",2000,-99.5,100.5);
514 hRmsBsl->GetXaxis()->SetTitle( "RMS (mV)" );
515 hRmsBsl->GetYaxis()->SetTitle( "Entries / 0.5 mV" );
516 hList.Add( hRmsBsl );
517
518 hpltRmsBsl = new TH1F("hplt_rms","Value of maximal probability",1440,-0.5,1439.5);
519 hpltRmsBsl->GetXaxis()->SetTitle( "pixel" );
520 hpltRmsBsl->GetYaxis()->SetTitle( "RMS in mV" );
521 hList.Add( hpltRmsBsl );
522
523 hAmplSpek_cfd = new TH2F("hAmplSpek_cfd","amplitude spektrum - CFD",1440,-0.5,1439.5, 256, -27.5, 100.5);
524 hAmplSpek_cfd->GetXaxis()->SetTitle( "pixel" );
525 hAmplSpek_cfd->GetYaxis()->SetTitle( "amplitude in mV" );
526 hList.Add( hAmplSpek_cfd );
527
528 hAmplSpek_discri = new TH2F("hAmplSpek_discri","amplitude spektrum - std discriminator",1440,-0.5,1439.5, 256, -27.5, 100.5);
529 hAmplSpek_discri->GetXaxis()->SetTitle( "pixel" );
530 hAmplSpek_discri->GetXaxis()->SetTitle( "amplitude in mV" );
531 hList.Add( hAmplSpek_discri );
532
533 hStartCell = new TH2F("StartCell", "StartCell", 1440, 0., 1440., 1024, 0., 1024);
534 hStartCell->GetXaxis()->SetTitle( "pixel" );
535 hStartCell->GetXaxis()->SetTitle( "slice" );
536 hList.Add( hStartCell );
537
538 debugHistos = new TH1F[ NumberOfDebugHistoTypes ];
539 for ( int type = 0; type < NumberOfDebugHistoTypes; type++){
540 debugHistos[ type ].SetBins(1024, 0, 1024);
541 debugHistos[ type ].SetLineColor(1);
542 debugHistos[ type ].SetLineWidth(2);
543
544 // set X axis paras
545 debugHistos[ type ].GetXaxis()->SetLabelSize(0.1);
546 debugHistos[ type ].GetXaxis()->SetTitleSize(0.1);
547 debugHistos[ type ].GetXaxis()->SetTitleOffset(1.2);
548 debugHistos[ type ].GetXaxis()->SetTitle(Form("Time slice (%.1f ns/slice)", 1./2.));
549
550 // set Y axis paras
551 debugHistos[ type ].GetYaxis()->SetLabelSize(0.1);
552 debugHistos[ type ].GetYaxis()->SetTitleSize(0.1);
553 debugHistos[ type ].GetYaxis()->SetTitleOffset(0.3);
554 debugHistos[ type ].GetYaxis()->SetTitle("Amplitude (a.u.)");
555 }
556
557 TH2F *temp;
558 for ( int type = 0; type < 1441; type++){
559 sprintf(&hTitle[0],"pulse template of pixel %d", type );
560 sprintf(&hName[0],"template_%d", type );
561
562 temp = new TH2F( hName, hTitle, 256, 0, 255, 256, -10.5, 117.5);
563
564 temp->GetXaxis()->SetTitle( "Time slice (%.1f ns/slice)" );
565 temp->GetYaxis()->SetTitle( "Amplitude (about mV)" );
566 hListTemplates.Add( temp );
567 hTemp_Array[ type ] = temp;
568 }
569
570
571}
572
573void SaveHistograms( char * loc_fname ){
574
575 TString fName; // name of the histogram file
576
577 // create the filename for the histogram file
578 fName = loc_fname; // use the name of the tree file
579 // TODO ... next statement doesn't work for ".fits.gz"
580 fName.Remove(fName.Length() - 5); // remove the extension .fits
581 fName += "_discri.root"; // add the new extension
582
583 // create the histogram file (replace if already existing)
584 TFile tf( fName, "RECREATE");
585
586 hList.Write(); // write the major histograms into the top level directory
587 tf.mkdir("BaselineHisto");
588 tf.cd("BaselineHisto"); // go to new subdirectory
589 hListBaseline.Write(); // write histos into subdirectory
590 tf.cd("..");
591 tf.mkdir("PulseTempplates");
592 tf.cd("PulseTempplates");
593 hListTemplates.Write(); // write histos into subdirectory
594
595 tf.Close(); // close the file
596} // end of SaveHistograms( char * loc_fname )
597
598int FOpenDataFile(fits &datafile){
599
600// cout << "-------------------- Data Header -------------------" << endl;
601// datafile.PrintKeys();
602// cout << "------------------- Data Columns -------------------" << endl;
603// datafile.PrintColumns();
604
605 //Get the size of the data column
606 RegionOfInterest = datafile.GetUInt("NROI");
607 NumberOfPixels = datafile.GetUInt("NPIX");
608 //Size of column "Data" = #Pixel x ROI
609 ROIxNOP = datafile.GetN("Data");
610
611 //Set the sizes of the data vectors
612 AllPixelDataVector.resize(ROIxNOP,0);
613 StartCellVector.resize(NumberOfPixels,0);
614
615 //Link the data to variables
616 datafile.SetRefAddress("EventNum", CurrentEventID);
617 datafile.SetVecAddress("Data", AllPixelDataVector);
618 datafile.SetVecAddress("StartCellData", StartCellVector);
619 datafile.GetRow(0);
620
621 return datafile.GetNumRows() ;
622}
Note: See TracBrowser for help on using the repository browser.