source: trunk/MagicSoft/Mars/mpedestal/MPedCalcFromLoGain.cc@ 4682

Last change on this file since 4682 was 4682, checked in by tbretz, 20 years ago
*** empty log message ***
File size: 20.1 KB
Line 
1/* ======================================================================== *\
2!
3! *
4! * This file is part of MARS, the MAGIC Analysis and Reconstruction
5! * Software. It is distributed to you in the hope that it can be a useful
6! * and timesaving tool in analysing Data of imaging Cerenkov telescopes.
7! * It is distributed WITHOUT ANY WARRANTY.
8! *
9! * Permission to use, copy, modify and distribute this software and its
10! * documentation for any purpose is hereby granted without fee,
11! * provided that the above copyright notice appear in all copies and
12! * that both that copyright notice and this permission notice appear
13! * in supporting documentation. It is provided "as is" without express
14! * or implied warranty.
15! *
16!
17!
18! Author(s): Josep Flix 04/2001 <mailto:jflix@ifae.es>
19! Author(s): Thomas Bretz 05/2001 <mailto:tbretz@astro.uni-wuerzburg.de>
20! Author(s): Sebastian Commichau 12/2003
21! Author(s): Javier Rico 01/2004 <mailto:jrico@ifae.es>
22! Author(s): Markus Gaug 01/2004 <mailto:markus@ifae.es>
23! Author(s): Florian Goebel 06/2004 <mailto:fgoebel@mppmu.mpg.de>
24!
25! Copyright: MAGIC Software Development, 2000-2004
26!
27!
28\* ======================================================================== */
29
30/////////////////////////////////////////////////////////////////////////////
31//
32// MPedCalcLoGain
33//
34//
35// This task is devide form MPedCalcPedRun, described below. However, It
36// calculates the pedstals using the low gain slices, whenever the difference
37// between the highest and the lowest slice in the high gain
38// slices is below a given threshold (SetMaxHiGainVar). In this case the receiver
39// boards do not switch to lo gain and the so called lo gain slices are actually
40// high gain slices.
41//
42// MPedCalcLoGain also fills the ABoffset in MPedestalPix which allows to correct
43// the 150 MHz clock noise.
44//
45// This task takes a pedestal run file and fills MPedestalCam during
46// the Process() with the pedestal and rms computed in an event basis.
47// In the PostProcess() MPedestalCam is finally filled with the pedestal
48// mean and rms computed in a run basis.
49// More than one run (file) can be merged
50//
51// MPedCalcPedRun applies the following formula (1):
52//
53// Pedestal per slice = sum(x_i) / n / slices
54// PedRMS per slice = Sqrt( ( sum(x_i^2) - sum(x_i)^2/n ) / n-1 / slices )
55//
56// where x_i is the sum of "slices" FADC slices and sum means the sum over all
57// events. "n" is the number of events, "slices" is the number of summed FADC samples.
58//
59// Note that the slice-to-slice fluctuations are not Gaussian, but Poissonian, thus
60// asymmetric and they are correlated.
61//
62// It is important to know that the Pedestal per slice and PedRMS per slice depend
63// on the number of used FADC slices, as seen in the following plots:
64//
65//Begin_Html
66/*
67<img src="images/PedestalStudyInner.gif">
68*/
69//End_Html
70//
71//Begin_Html
72/*
73<img src="images/PedestalStudyOuter.gif">
74*/
75//
76// The plots show the inner and outer pixels, respectivly and have the following meaning:
77//
78// 1) The calculated mean pedestal per slice (from MPedCalcFromLoGain)
79// 2) The fitted mean pedestal per slice (from MHPedestalCam)
80// 3) The calculated pedestal RMS per slice (from MPedCalcFromLoGain)
81// 4) The fitted sigma of the pedestal distribution per slice
82// (from MHPedestalCam)
83// 5) The relative difference between calculation and histogram fit
84// for the mean
85// 6) The relative difference between calculation and histogram fit
86// for the sigma or RMS, respectively.
87//
88// The calculated means do not change significantly except for the case of 2 slices,
89// however the RMS changes from 5.7 per slice in the case of 2 extracted slices
90// to 8.3 per slice in the case of 26 extracted slices. This change is very significant.
91//
92// The plots have been produced on run 20123. You can reproduce them using
93// the macro pedestalstudies.C
94//
95// Usage of this class:
96// ====================
97//
98// Call: SetRange(higainfirst, higainlast, logainfirst, logainlast)
99// to modify the ranges in which the window is allowed to move.
100// Defaults are:
101//
102// fHiGainFirst = fgHiGainFirst = 0
103// fHiGainLast = fgHiGainLast = 29
104// fLoGainFirst = fgLoGainFirst = 0
105// fLoGainLast = fgLoGainLast = 14
106//
107// Call: SetWindowSize(windowhigain, windowlogain)
108// to modify the sliding window widths. Windows have to be an even number.
109// In case of odd numbers, the window will be modified.
110//
111// Defaults are:
112//
113// fHiGainWindowSize = fgHiGainWindowSize = 14
114// fLoGainWindowSize = fgLoGainWindowSize = 0
115//
116// Variables:
117// fgHiGainFirst; First FADC slice Hi-Gain (currently set to: 3)
118// fgHiGainLast: Last FADC slice Hi-Gain (currently set to: 14)
119// fgLoGainFirst: First FADC slice Lo-Gain (currently set to: 3)
120// fgLoGainLast: Last FADC slice Lo-Gain (currently set to: 14)
121// fgHiGainWindowSize: The extraction window Hi-Gain
122// fgLoGainWindowSize: The extraction window Lo-Gain
123// fgMaxHiGainVar: The maximum difference between the highest and lowest slice
124// in the high gain window allowed in order to use low gain
125//
126// Input Containers:
127// MRawEvtData
128// MRawRunHeader
129// MGeomCam
130//
131// Output Containers:
132// MPedestalCam
133//
134// See also: MPedestalCam, MPedestalPix, MHPedestalCam, MExtractor
135//
136/////////////////////////////////////////////////////////////////////////////
137#include "MPedCalcFromLoGain.h"
138#include "MExtractor.h"
139
140#include "MParList.h"
141
142#include "MLog.h"
143#include "MLogManip.h"
144
145#include "MRawRunHeader.h"
146#include "MRawEvtPixelIter.h"
147#include "MRawEvtData.h"
148
149#include "MPedestalPix.h"
150#include "MPedestalCam.h"
151
152#include "MGeomPix.h"
153#include "MGeomCam.h"
154
155ClassImp(MPedCalcFromLoGain);
156
157using namespace std;
158
159const Byte_t MPedCalcFromLoGain::fgHiGainFirst = 0;
160const Byte_t MPedCalcFromLoGain::fgHiGainLast = 11;
161const Byte_t MPedCalcFromLoGain::fgLoGainFirst = 1;
162const Byte_t MPedCalcFromLoGain::fgLoGainLast = 14;
163const Byte_t MPedCalcFromLoGain::fgHiGainWindowSize = 12;
164const Byte_t MPedCalcFromLoGain::fgLoGainWindowSize = 14;
165const Byte_t MPedCalcFromLoGain::fgMaxHiGainVar = 40;
166
167// --------------------------------------------------------------------------
168//
169// Default constructor:
170//
171// Sets:
172// - all pointers to NULL
173// - fWindowSizeHiGain to fgHiGainWindowSize
174// - fWindowSizeLoGain to fgLoGainWindowSize
175//
176// Calls:
177// - AddToBranchList("fHiGainPixId");
178// - AddToBranchList("fHiGainFadcSamples");
179// - SetRange(fgHiGainFirst, fgHiGainLast, fgLoGainFirst, fgLoGainLast)
180// - Clear()
181//
182MPedCalcFromLoGain::MPedCalcFromLoGain(const char *name, const char *title)
183 : fWindowSizeHiGain(fgHiGainWindowSize),
184 fWindowSizeLoGain(fgLoGainWindowSize),
185 fGeom(NULL), fPedContainerName("MPedestalCam")
186{
187 fName = name ? name : "MPedCalcFromLoGain";
188 fTitle = title ? title : "Task to calculate pedestals from pedestal runs raw data";
189
190 AddToBranchList("fHiGainPixId");
191 AddToBranchList("fLoGainPixId");
192 AddToBranchList("fHiGainFadcSamples");
193 AddToBranchList("fLoGainFadcSamples");
194
195 SetRange();
196 SetMaxHiGainVar();
197 SetPedestalUpdate(kTRUE);
198
199 Clear();
200}
201
202// --------------------------------------------------------------------------
203//
204// Sets:
205// - fNumSamplesTot to 0
206// - fRawEvt to NULL
207// - fRunHeader to NULL
208// - fPedestals to NULL
209//
210void MPedCalcFromLoGain::Clear(const Option_t *o)
211{
212 fRawEvt = NULL;
213 fRunHeader = NULL;
214 fPedestals = NULL;
215
216 // If the size is yet set, set the size
217 if (fSumx.GetSize()>0)
218 {
219 // Reset contents of arrays.
220 fSumx.Reset();
221 fSumx2.Reset();
222 fSumAB0.Reset();
223 fSumAB1.Reset();
224 fNumEventsUsed.Reset();
225 fTotalCounter.Reset();
226 }
227}
228
229// --------------------------------------------------------------------------
230//
231// SetRange:
232//
233// Calls:
234// - MExtractor::SetRange(hifirst,hilast,lofirst,lolast);
235// - SetWindowSize(fWindowSizeHiGain,fWindowSizeLoGain);
236//
237void MPedCalcFromLoGain::SetRange(Byte_t hifirst, Byte_t hilast, Byte_t lofirst, Byte_t lolast)
238{
239 MExtractor::SetRange(hifirst, hilast, lofirst, lolast);
240
241 //
242 // Redo the checks if the window is still inside the ranges
243 //
244 SetWindowSize(fWindowSizeHiGain,fWindowSizeLoGain);
245}
246
247// --------------------------------------------------------------------------
248//
249void MPedCalcFromLoGain::SetMaxHiGainVar(Byte_t maxvar)
250{
251 fMaxHiGainVar = maxvar;
252}
253
254// --------------------------------------------------------------------------
255//
256// Checks:
257// - if a window is odd, subtract one
258// - if a window is bigger than the one defined by the ranges, set it to the available range
259// - if a window is smaller than 2, set it to 2
260//
261// Sets:
262// - fNumHiGainSamples to: (Float_t)fWindowSizeHiGain
263// - fNumLoGainSamples to: (Float_t)fWindowSizeLoGain
264// - fSqrtHiGainSamples to: TMath::Sqrt(fNumHiGainSamples)
265// - fSqrtLoGainSamples to: TMath::Sqrt(fNumLoGainSamples)
266//
267void MPedCalcFromLoGain::SetWindowSize(Byte_t windowh, Byte_t windowl)
268{
269 fWindowSizeHiGain = windowh & ~1;
270 fWindowSizeLoGain = windowl & ~1;
271
272 if (fWindowSizeHiGain != windowh)
273 {
274 *fLog << warn;
275 *fLog << GetDescriptor() << ": HiGain window has to be even, set to: ";
276 *fLog << int(fWindowSizeHiGain) << " samples " << endl;
277 }
278
279 if (fWindowSizeLoGain != windowl)
280 {
281 *fLog << warn;
282 *fLog << GetDescriptor() << ": Lo Gain window has to be even, set to: ";
283 *fLog << int(fWindowSizeLoGain) << " samples " << endl;
284 }
285
286 const Byte_t availhirange = (fHiGainLast-fHiGainFirst+1) & ~1;
287 const Byte_t availlorange = (fLoGainLast-fLoGainFirst+1) & ~1;
288
289 if (fWindowSizeHiGain > availhirange)
290 {
291 *fLog << warn;
292 *fLog << GetDescriptor() << ": HiGain window " << (int)fWindowSizeHiGain;
293 *fLog << " out of range [" << (int)fHiGainFirst;
294 *fLog << "," << (int)fHiGainLast << "]" << endl;
295 *fLog << "Will set window size to " << (int)availhirange << endl;
296 fWindowSizeHiGain = availhirange;
297 }
298
299 if (fWindowSizeLoGain > availlorange)
300 {
301 *fLog << warn;
302 *fLog << GetDescriptor() << ": LoGain window " << (int)fWindowSizeLoGain;
303 *fLog << " out of range [" << (int)fLoGainFirst;
304 *fLog << "," << (int)fLoGainLast << "]" << endl;
305 *fLog << "Will set window size to " << (int)availlorange << endl;
306 fWindowSizeLoGain = availlorange;
307 }
308 /*
309 fNumHiGainSamples = (Float_t)fWindowSizeHiGain;
310 fNumLoGainSamples = (Float_t)fWindowSizeLoGain;
311
312 fSqrtHiGainSamples = TMath::Sqrt(fNumHiGainSamples);
313 fSqrtLoGainSamples = TMath::Sqrt(fNumLoGainSamples);
314 */
315}
316
317// --------------------------------------------------------------------------
318//
319// Look for the following input containers:
320//
321// - MRawEvtData
322// - MRawRunHeader
323// - MGeomCam
324//
325// The following output containers are also searched and created if
326// they were not found:
327//
328// - MPedestalCam
329//
330Int_t MPedCalcFromLoGain::PreProcess(MParList *pList)
331{
332 Clear();
333
334 fRawEvt = (MRawEvtData*)pList->FindObject("MRawEvtData");
335 if (!fRawEvt)
336 {
337 *fLog << err << "MRawEvtData not found... aborting." << endl;
338 return kFALSE;
339 }
340
341 fRunHeader = (MRawRunHeader*)pList->FindObject(AddSerialNumber("MRawRunHeader"));
342 if (!fRunHeader)
343 {
344 *fLog << err << AddSerialNumber("MRawRunHeader") << " not found... aborting." << endl;
345 return kFALSE;
346 }
347
348 fGeom = (MGeomCam*)pList->FindObject("MGeomCam");
349 if (!fGeom)
350 {
351 *fLog << err << "MGeomCam not found... aborting." << endl;
352 return kFALSE;
353 }
354
355 fPedestals = (MPedestalCam*)pList->FindCreateObj("MPedestalCam", AddSerialNumber(fPedContainerName));
356 if (!fPedestals)
357 return kFALSE;
358
359 if (fNumEventsDump<=0 && fPedestalUpdate)
360 {
361 *fLog << warn << "Pedestal Update switched on and Number of Events to dump <= 0... fNumEventsDump=1000" << endl;
362 fNumEventsDump=1000;
363 }
364
365 return kTRUE;
366}
367
368// --------------------------------------------------------------------------
369//
370// The ReInit searches for:
371// - MRawRunHeader::GetNumSamplesHiGain()
372// - MRawRunHeader::GetNumSamplesLoGain()
373//
374// In case that the variables fHiGainLast and fLoGainLast are smaller than
375// the even part of the number of samples obtained from the run header, a
376// warning is given an the range is set back accordingly. A call to:
377// - SetRange(fHiGainFirst, fHiGainLast-diff, fLoGainFirst, fLoGainLast) or
378// - SetRange(fHiGainFirst, fHiGainLast, fLoGainFirst, fLoGainLast-diff)
379// is performed in that case. The variable diff means here the difference
380// between the requested range (fHiGainLast) and the available one. Note that
381// the functions SetRange() are mostly overloaded and perform more checks,
382// modifying the ranges again, if necessary.
383//
384Bool_t MPedCalcFromLoGain::ReInit(MParList *pList)
385{
386 if (fRunHeader->GetNumSamplesHiGain()<1)
387 {
388 *fLog << err << "ERROR - Number of available HiGainSamples<1... abort." << endl;
389 return kFALSE;
390 }
391 if (fRunHeader->GetNumSamplesLoGain()<1)
392 {
393 *fLog << err << "ERROR - Number of available LoGainSamples<1... abort." << endl;
394 return kFALSE;
395 }
396
397 fHiGainFirst = TMath::Max(fHiGainFirst, 0);
398 fHiGainLast = TMath::Min(fHiGainLast, fRunHeader->GetNumSamplesHiGain()-1);
399
400 fLoGainFirst = TMath::Max(fLoGainFirst, 0);
401 fLoGainLast = TMath::Min(fLoGainLast, fRunHeader->GetNumSamplesLoGain()-1);
402
403 const Double_t wh = fWindowSizeHiGain;
404 const Double_t wl = fWindowSizeLoGain;
405 const Double_t fh = fHiGainFirst;
406 const Double_t fl = fLoGainFirst;;
407
408 fWindowSizeHiGain = TMath::Min(fWindowSizeHiGain, fHiGainLast-fHiGainFirst+1);
409 fWindowSizeLoGain = TMath::Min(fWindowSizeLoGain, fLoGainLast-fLoGainFirst+1);
410
411 SetRange(fHiGainFirst, fHiGainLast, fLoGainFirst, fLoGainLast);
412
413 if (wh!=fWindowSizeHiGain || fh!=fHiGainFirst || wl!=fWindowSizeLoGain || fl!=fLoGainFirst)
414 {
415 *fLog << inf << endl;
416 *fLog << "Taking " << Form("%2d", (int)fWindowSizeHiGain) << " slices of Hi-Gain starting at slice " << (int)fHiGainFirst << endl;
417 *fLog << "Taking " << Form("%2d", (int)fWindowSizeLoGain) << " slices of Lo-Gain starting at slice " << (int)fLoGainFirst << endl;
418 }
419
420 if (fWindowSizeHiGain==0)
421 {
422 *fLog << err << "ERROR - HiGain windows size == 0... abort." << endl;
423 return kFALSE;
424 }
425 if (fWindowSizeLoGain==0)
426 {
427 *fLog << err << "ERROR - HiGain windows size == 0... abort." << endl;
428 return kFALSE;
429 }
430
431 // If the size is not yet set, set the size
432 if (fSumx.GetSize()==0)
433 {
434 const Int_t npixels = fPedestals->GetSize();
435
436 fSumx. Set(npixels);
437 fSumx2.Set(npixels);
438 fSumAB0.Set(npixels);
439 fSumAB1.Set(npixels);
440 fNumEventsUsed.Set(npixels);
441 fTotalCounter.Set(npixels);
442
443 // Reset contents of arrays.
444 fSumx.Reset();
445 fSumx2.Reset();
446 fSumAB0.Reset();
447 fSumAB1.Reset();
448 fNumEventsUsed.Reset();
449 fTotalCounter.Reset();
450 }
451
452 return kTRUE;
453}
454
455void MPedCalcFromLoGain::Calc(ULong_t n, UInt_t idx)
456{
457 const ULong_t nsamplestot = n*fWindowSizeLoGain;
458
459 const Float_t sum = fSumx.At(idx);
460 const Float_t sum2 = fSumx2.At(idx);
461 const Float_t ped = sum/nsamplestot;
462
463 // 1. Calculate the Variance of the sums:
464 Float_t var = (sum2-sum*sum/n)/(n-1.);
465
466 // 2. Scale the variance to the number of slices:
467 var /= (Float_t)(fWindowSizeLoGain);
468
469 // 3. Calculate the RMS from the Variance:
470 const Float_t rms = var<0 ? 0 : TMath::Sqrt(var);
471
472 // 4. Calculate the amplitude of the 150MHz "AB" noise
473 const Float_t abOffs = (fSumAB0[idx] - fSumAB1[idx]) / nsamplestot;
474
475 (*fPedestals)[idx].Set(ped, rms, abOffs, n);
476
477 fTotalCounter[idx]++;
478}
479
480// --------------------------------------------------------------------------
481//
482// Fill the MPedestalCam container with the signal mean and rms for the event.
483// Store the measured signal in arrays fSumx and fSumx2 so that we can
484// calculate the overall mean and rms in the PostProcess()
485//
486Int_t MPedCalcFromLoGain::Process()
487{
488 MRawEvtPixelIter pixel(fRawEvt);
489
490 while (pixel.Next())
491 {
492 const UInt_t idx = pixel.GetPixelId();
493
494 Byte_t *ptr = pixel.GetHiGainSamples() + fHiGainFirst;
495 Byte_t *end = ptr + fWindowSizeHiGain;
496
497 UInt_t sum = 0;
498 UInt_t sqr = 0;
499
500 UInt_t max = 0;
501 UInt_t min = 255;
502
503 // Find the maximum and minimum signal per slice in the high gain window
504 do {
505 if (*ptr > max)
506 max = *ptr;
507 if (*ptr < min)
508 min = *ptr;
509 } while (++ptr != end);
510
511 // If the maximum in the high gain window is smaller than
512 if (max-min>=fMaxHiGainVar || max>=255)
513 continue;
514
515 ptr = pixel.GetLoGainSamples() + fLoGainFirst;
516 end = ptr + fWindowSizeLoGain;
517
518 Byte_t *firstSlice = ptr;
519
520 do {
521 sum += *ptr;
522 sqr += *ptr * *ptr;
523 } while (++ptr != end);
524
525 const Float_t msum = (Float_t)sum;
526 const Float_t sqrsum = msum*msum;
527
528 fSumx[idx] += msum;
529 fSumx2[idx] += sqrsum;
530 fNumEventsUsed[idx]++;
531
532 // Calculate the amplitude of the 150MHz "AB" noise
533
534 if (pixel.IsABFlagValid())
535 {
536 const Int_t abFlag = (fRunHeader->GetNumSamplesHiGain()
537 + fLoGainFirst + pixel.HasABFlag()) & 0x1;
538 for (Int_t islice=0; islice<fWindowSizeLoGain; islice+=2)
539 {
540 const Int_t sliceAB0 = islice + abFlag;
541 const Int_t sliceAB1 = islice - abFlag + 1;
542 fSumAB0[idx] += firstSlice[sliceAB0];
543 fSumAB1[idx] += firstSlice[sliceAB1];
544 }
545 }
546
547 if (!fPedestalUpdate || fNumEventsUsed[idx]<fNumEventsDump)
548 continue;
549
550 Calc(fNumEventsDump, idx);
551
552 fNumEventsUsed[idx]=0;
553 fSumx[idx]=0;
554 fSumx2[idx]=0;
555 fSumAB0[idx]=0;
556 fSumAB1[idx]=0;
557 }
558
559 if (fPedestalUpdate)
560 {
561 //fPedestals->ReCalc(*fGeom);
562 fPedestals->SetReadyToSave();
563 }
564
565 return kTRUE;
566}
567
568// --------------------------------------------------------------------------
569//
570// Compute signal mean and rms in the whole run and store it in MPedestalCam
571//
572Int_t MPedCalcFromLoGain::PostProcess()
573{
574 // Compute pedestals and rms from the whole run
575 if (fPedestalUpdate || GetNumExecutions()<1)
576 return kTRUE;
577
578 *fLog << flush << inf << "Calculating pedestals..." << flush;
579
580 Double_t sum = 0;
581
582 const Int_t npix = fGeom->GetNumPixels();
583 for (Int_t idx=0; idx<npix; idx++)
584 {
585 const ULong_t n = fNumEventsUsed[idx];
586 if (n>1)
587 Calc(n, idx);
588 sum += n;
589 }
590
591 *fLog << flush << inf << "Calculating means..." << flush;
592
593 fPedestals->SetTotalEntries((UInt_t)(sum/npix*(fWindowSizeLoGain+fWindowSizeHiGain)));
594 fPedestals->ReCalc(*fGeom);
595 fPedestals->SetReadyToSave();
596 return kTRUE;
597}
598
599Int_t MPedCalcFromLoGain::ReadEnv(const TEnv &env, TString prefix, Bool_t print)
600{
601 if (MExtractor::ReadEnv(env, prefix, print)==kERROR)
602 return kERROR;
603
604 Bool_t rc=kFALSE;
605
606 Byte_t hw = fWindowSizeHiGain;
607 Byte_t lw = fWindowSizeLoGain;
608 if (IsEnvDefined(env, prefix, "WindowSizeHiGain", print))
609 {
610 hw = GetEnvValue(env, prefix, "WindowSizeHiGain", hw);
611 rc = kTRUE;
612 }
613 if (IsEnvDefined(env, prefix, "WindowSizeLoGain", print))
614 {
615 lw = GetEnvValue(env, prefix, "WindowSizeLoGain", lw);
616 rc = kTRUE;
617 }
618
619 if (rc)
620 SetWindowSize(hw, lw);
621
622 if (IsEnvDefined(env, prefix, "NumEventsDump", print))
623 {
624 SetNumEventsDump(GetEnvValue(env, prefix, "NumEventsDump", fNumEventsDump));
625 rc = kTRUE;
626 }
627
628 if (IsEnvDefined(env, prefix, "MaxHiGainVar", print))
629 {
630 SetMaxHiGainVar(GetEnvValue(env, prefix, "MaxHiGainVar", fMaxHiGainVar));
631 rc = kTRUE;
632 }
633
634 if (IsEnvDefined(env, prefix, "PedestalUpdate", print))
635 {
636 SetPedestalUpdate(GetEnvValue(env, prefix, "PedestalUpdate", fPedestalUpdate));
637 rc = kTRUE;
638 }
639
640 return rc;
641}
Note: See TracBrowser for help on using the repository browser.