1 | // ESAF : Euso Simulation and Analysis Framework |
---|
2 | // $Id: PixelMapBuilder.cc 2927 2011-06-16 18:02:41Z mabl $ |
---|
3 | // A.Thea created Apr, 15 2004 |
---|
4 | |
---|
5 | #include "PixelMapBuilder.hh" |
---|
6 | #include "EusoDetector.hh" |
---|
7 | #include "DetectorTransportManager.hh" |
---|
8 | #include "EusoElectronics.hh" |
---|
9 | //#include "OAPxBunch.hh" |
---|
10 | #include "Photon.hh" |
---|
11 | #include "ParentPhoton.hh" |
---|
12 | #include "DetectorGeometry.hh" |
---|
13 | #include <TTree.h> |
---|
14 | #include <TChain.h> |
---|
15 | #include "TTimeStamp.h" |
---|
16 | #include "TString.h" |
---|
17 | #include <TCanvas.h> |
---|
18 | #include <TF1.h> |
---|
19 | #include <TText.h> |
---|
20 | #include <TEllipse.h> |
---|
21 | |
---|
22 | #include <TPolyMarker.h> |
---|
23 | #include <TSystem.h> |
---|
24 | #include <TGClient.h> |
---|
25 | #include <TGProgressBar.h> |
---|
26 | #include <TMutex.h> |
---|
27 | #include "EGViewer.hh" |
---|
28 | #include <TMath.h> |
---|
29 | |
---|
30 | using namespace TMath; |
---|
31 | |
---|
32 | //______________________________________________________________________________ |
---|
33 | // |
---|
34 | // PixelMapBuilder |
---|
35 | // |
---|
36 | // Works in 2 modes |
---|
37 | // |
---|
38 | // Create the photon's file for the anaysis |
---|
39 | // |
---|
40 | // fNphotons |
---|
41 | // fDetector |
---|
42 | // fTransporter |
---|
43 | // fElectronics |
---|
44 | // fHitsRootFile |
---|
45 | // fHitsTree |
---|
46 | // fFoV |
---|
47 | // fHit |
---|
48 | // fHeader |
---|
49 | // |
---|
50 | // |
---|
51 | |
---|
52 | ClassImp(PixelMapBuilder) |
---|
53 | |
---|
54 | //______________________________________________________________________________ |
---|
55 | PixelMapBuilder::PixelMapBuilder() : EsafConfigurable(), fHitsRootFile(0), fHitsTree(0), fHitsChain(0), |
---|
56 | fFitHistsFile(0), fFitHistsTree(0), fFitUID(0), fH1FitTheta(0), |
---|
57 | fH1FitPhi(0), fH2FitThetaPhi(0), fH2ThetaPhi(0), fDisplay(0) { |
---|
58 | // |
---|
59 | // ctor |
---|
60 | |
---|
61 | fMaxBufSize = 5000000/(sizeof(Float_t)*2); |
---|
62 | fBufferSize = 250 /*Mb*/ *1024*1024; |
---|
63 | fNsigma = 1.5; |
---|
64 | |
---|
65 | fHitsThreshold = 0; |
---|
66 | fNbins = 100; |
---|
67 | |
---|
68 | fBufIndex = kBufEmpty; |
---|
69 | |
---|
70 | // fFullFoV = Pi()/5.; |
---|
71 | // fFoV = Pi()/5.; |
---|
72 | fFullFoV = Conf()->GetNum("DiffusePhotonsOnPupil.fThetaFOVMax")*TMath::DegToRad(); |
---|
73 | fFoV = fFullFoV; |
---|
74 | // fBufferSize = 1; |
---|
75 | } |
---|
76 | |
---|
77 | //______________________________________________________________________________ |
---|
78 | PixelMapBuilder::~PixelMapBuilder() { |
---|
79 | // |
---|
80 | // Destructor |
---|
81 | // |
---|
82 | } |
---|
83 | |
---|
84 | |
---|
85 | //______________________________________________________________________________ |
---|
86 | Bool_t PixelMapBuilder::AddToChain( const char* name ) { |
---|
87 | |
---|
88 | if ( fHitsChain ) fHitsChain->Add(name,0); |
---|
89 | return kTRUE; |
---|
90 | } |
---|
91 | |
---|
92 | //______________________________________________________________________________ |
---|
93 | Bool_t PixelMapBuilder::OpenChain( const char* name ) { |
---|
94 | |
---|
95 | fHitsChain = new TChain("oapxtree"); |
---|
96 | |
---|
97 | AddToChain(name); |
---|
98 | fHitsChain->GetEntries(); |
---|
99 | |
---|
100 | fHitsChain->SetBranchAddress("Hit", &fHit); |
---|
101 | fHitsChain->GetEntry(); |
---|
102 | fHeader = (Header*)fHitsChain->GetTree()->GetUserInfo()->FindObject("PixelMapBuilder::Header")->Clone(); |
---|
103 | |
---|
104 | fHitsTree = fHitsChain; |
---|
105 | return kTRUE; |
---|
106 | } |
---|
107 | |
---|
108 | //______________________________________________________________________________ |
---|
109 | Bool_t PixelMapBuilder::OpenChain( const char** name, Int_t n ) { |
---|
110 | |
---|
111 | fHitsChain = new TChain("oapxtree"); |
---|
112 | |
---|
113 | for(Int_t i(0); i<n; i++) { |
---|
114 | AddToChain(name[i]); |
---|
115 | } |
---|
116 | fHitsChain->GetEntries(); |
---|
117 | |
---|
118 | fHitsChain->SetBranchAddress("Hit", &fHit); |
---|
119 | fHitsChain->GetEntry(); |
---|
120 | fHeader = (Header*)fHitsChain->GetTree()->GetUserInfo()->FindObject("PixelMapBuilder::Header")->Clone(); |
---|
121 | |
---|
122 | fHitsTree = fHitsChain; |
---|
123 | return kTRUE; |
---|
124 | } |
---|
125 | |
---|
126 | //______________________________________________________________________________ |
---|
127 | Bool_t PixelMapBuilder::OpenRoot( const char* name, Bool_t write ) { |
---|
128 | // |
---|
129 | // Open rootfile with the correct name |
---|
130 | // |
---|
131 | CloseRoot(); |
---|
132 | |
---|
133 | TString fname(name); |
---|
134 | TString ext = ".root"; |
---|
135 | |
---|
136 | if ( write) { |
---|
137 | if ( fname.EndsWith(ext)) fname.Resize(fname.Length()-ext.Length()); |
---|
138 | Msg(EsafMsg::Info) << "Opening root file " << fname << ext << MsgDispatch; |
---|
139 | fHitsRootFileName = fname+ext; |
---|
140 | fHitsRootFile = new TFile(fHitsRootFileName,"CREATE"); |
---|
141 | |
---|
142 | if (fHitsRootFile->IsZombie()) { |
---|
143 | Msg(EsafMsg::Warning) << MsgDispatch; |
---|
144 | Msg(EsafMsg::Warning) << "Error opening file " << fname << MsgDispatch; |
---|
145 | Msg(EsafMsg::Warning) << "Probably it already exists or there is no " << MsgDispatch; |
---|
146 | Msg(EsafMsg::Warning) << "write access permission to this directory" << MsgDispatch; |
---|
147 | Msg(EsafMsg::Warning) << MsgDispatch; |
---|
148 | |
---|
149 | // try adding date and time to the name |
---|
150 | TTimeStamp time; |
---|
151 | TString now = time.AsString("s"); |
---|
152 | now[now.Last(' ')]=':'; |
---|
153 | TString newFileName = Form("%s-", fname.Data())+now+ext; |
---|
154 | fHitsRootFile = new TFile(newFileName,"CREATE"); |
---|
155 | if (fHitsRootFile->IsZombie()) { |
---|
156 | MsgForm(EsafMsg::Warning,"Cannot open root file"); |
---|
157 | return kFALSE; |
---|
158 | } |
---|
159 | } |
---|
160 | Msg(EsafMsg::Info) << "Root file " << fHitsRootFile->GetName() << " successfully opened\n" << MsgDispatch; |
---|
161 | |
---|
162 | fHitsTree = new TTree("oapxtree","Angle Pixel Map photons' database"); |
---|
163 | fHitsTree->Branch("Hit",&fHit,"fUID/I:fTheta/F:fPhi/F:fLambda/F"); |
---|
164 | fHeader = new Header; |
---|
165 | fHitsTree->GetUserInfo()->Add(fHeader); |
---|
166 | |
---|
167 | return kTRUE; |
---|
168 | } else { |
---|
169 | if ( fname.EndsWith(ext)) fname.Resize(fname.Length()-ext.Length()); |
---|
170 | fHitsRootFile = new TFile((fname+ext)); |
---|
171 | |
---|
172 | if ( fHitsRootFile->IsZombie() ) return kFALSE; |
---|
173 | |
---|
174 | fHitsTree = (TTree*)fHitsRootFile->Get("oapxtree"); |
---|
175 | fHitsTree->SetBranchAddress("Hit", &fHit); |
---|
176 | |
---|
177 | fHeader = (Header*)fHitsTree->GetUserInfo()->FindObject("PixelMapBuilder::Header"); |
---|
178 | if ( !fHeader ) { |
---|
179 | Msg(EsafMsg::Warning) << "No header in the rootfile" << MsgDispatch; |
---|
180 | return kFALSE; |
---|
181 | } |
---|
182 | |
---|
183 | return kTRUE; |
---|
184 | } |
---|
185 | } |
---|
186 | |
---|
187 | //______________________________________________________________________________ |
---|
188 | Bool_t PixelMapBuilder::CloseRoot() { |
---|
189 | // |
---|
190 | // close root file |
---|
191 | // |
---|
192 | |
---|
193 | if ( fHitsRootFile && fHitsChain ) { |
---|
194 | Msg(EsafMsg::Warning) << "Both fHitsRootFile and fHitsChain are present. Something's wronmg. Anorting" << MsgDispatch; |
---|
195 | return kFALSE; |
---|
196 | } |
---|
197 | |
---|
198 | if ( fHitsRootFile ) { |
---|
199 | if ( fHitsRootFile->IsWritable() ) { |
---|
200 | fHitsRootFile->Write(); |
---|
201 | |
---|
202 | SafeDelete(fHitsTree); |
---|
203 | fHeader = 0; |
---|
204 | |
---|
205 | Msg(EsafMsg::Info) << "Root file saved" << MsgDispatch; |
---|
206 | } else { |
---|
207 | SafeDelete(fHitsTree); |
---|
208 | fHeader = 0; |
---|
209 | } |
---|
210 | fHitsRootFile->Close(); |
---|
211 | |
---|
212 | Msg(EsafMsg::Info) << "Root file closed" << MsgDispatch; |
---|
213 | SafeDelete( fHitsRootFile ) |
---|
214 | } else if (fHitsChain) { |
---|
215 | // chain, reading by default |
---|
216 | SafeDelete(fHitsChain); |
---|
217 | SafeDelete(fHeader); |
---|
218 | fHitsTree = 0; |
---|
219 | } |
---|
220 | |
---|
221 | return kTRUE; |
---|
222 | } |
---|
223 | |
---|
224 | |
---|
225 | //______________________________________________________________________________ |
---|
226 | void PixelMapBuilder::TrackPhotons() { |
---|
227 | // |
---|
228 | // build the map |
---|
229 | // |
---|
230 | |
---|
231 | // disable electronics simulation, just find the photon's pad |
---|
232 | fElectronics->EnableSimulation(kFALSE); |
---|
233 | fHit.fUID = 0; |
---|
234 | fHit.fTheta = 0; |
---|
235 | fHit.fPhi = 0; |
---|
236 | fHit.fLambda = 0; |
---|
237 | fPupil.SetThetaFOVMax(fFoV); |
---|
238 | fHeader->fFoV = fFoV; |
---|
239 | fHeader->fFullFoV = fDetector->GetGeometry()->GetFoV(); |
---|
240 | |
---|
241 | Msg(EsafMsg::Info) << "Photons to track: " << fNphotons << MsgDispatch; |
---|
242 | Msg(EsafMsg::Info) << "PixelMapBuilder: tracing..." << MsgDispatch; |
---|
243 | Long_t hits(0); |
---|
244 | EVector initdir, initpos; |
---|
245 | EVector calcdir,dirdiff; |
---|
246 | for(Long_t i(0); i<fNphotons; i++) { |
---|
247 | Photon *p = fPupil.GetPhoton(); |
---|
248 | initdir=p->dir; |
---|
249 | initpos=p->pos; |
---|
250 | fTransporter->Transport(p); |
---|
251 | calcdir=(p->pos-initpos).Unit(); |
---|
252 | dirdiff=calcdir-initdir; |
---|
253 | // double dirdiff_0=(p->dir-initdir).Mag(); |
---|
254 | // if ( p->hitIfs && dirdiff_0<1.e-9 ){ |
---|
255 | // double theta=initdir.Theta(); |
---|
256 | // cout<<"strang photon hits ifs"<<endl; |
---|
257 | // cout<<"initpos="<<initpos<<" rho="<<initpos.Perp()<<endl; |
---|
258 | // cout<<"initdir="<<initdir<<" theta="<<theta<<endl; |
---|
259 | // cout<<"fspos="<<p->posOnIfs<<" rho="<<p->posOnIfs.Perp()<<endl; |
---|
260 | // cout<<"latest pos="<<p->pos<<" rho="<<p->pos.Perp()<<endl; |
---|
261 | // cout<<"latest dir="<<p->dir<<" theta="<<p->dir.Theta()<<endl; |
---|
262 | // } |
---|
263 | |
---|
264 | fHeader->fNtracked++; |
---|
265 | |
---|
266 | if ( p->pixelUid > 0 ) { // FIXME: should be > 0 |
---|
267 | TVector3 dir = p->parent->Dir(); |
---|
268 | hits++; |
---|
269 | fHeader->fNhits++; |
---|
270 | |
---|
271 | fHit.fUID = p->pixelUid; |
---|
272 | fHit.fTheta = dir.Theta(); |
---|
273 | fHit.fPhi = dir.Phi(); |
---|
274 | fHit.fLambda = p->wl; |
---|
275 | |
---|
276 | fHitsTree->Fill(); |
---|
277 | if ( i%1000000 == 0 ) fHitsTree->AutoSave(); |
---|
278 | |
---|
279 | if ( fHitsTree->GetCurrentFile() != fHitsRootFile ) { |
---|
280 | fHitsRootFile = fHitsTree->GetCurrentFile(); |
---|
281 | fHeader->fNtracked = 0; |
---|
282 | fHeader->fNhits = 0; |
---|
283 | } |
---|
284 | |
---|
285 | } |
---|
286 | |
---|
287 | if ( (i+1)%1000 == 0) { |
---|
288 | Msg(EsafMsg::Info) << '\r' << MsgFlush; |
---|
289 | Msg(EsafMsg::Info) << Form("%ld photons traced, %ld hits on the pixels", |
---|
290 | (i+1), hits) << MsgFlush; |
---|
291 | } |
---|
292 | } |
---|
293 | Msg(EsafMsg::Info) << MsgDispatch; |
---|
294 | Msg(EsafMsg::Info) << "Completed" << MsgDispatch; |
---|
295 | |
---|
296 | MsgForm(EsafMsg::Info, "%d photons traced.",fNphotons); |
---|
297 | MsgForm(EsafMsg::Info, "%d photons hit the pixels.",hits); |
---|
298 | // FIXME: should be false if it was before TrackPhotons() |
---|
299 | fElectronics->EnableSimulation(kTRUE); |
---|
300 | } |
---|
301 | |
---|
302 | |
---|
303 | //______________________________________________________________________________ |
---|
304 | Bool_t PixelMapBuilder::MakePhotonsFile( const char* name, Long_t n) { |
---|
305 | |
---|
306 | |
---|
307 | fNphotons = n; |
---|
308 | |
---|
309 | |
---|
310 | Msg(EsafMsg::Info) << "Building Detector" << MsgDispatch; |
---|
311 | |
---|
312 | fDetector = new EusoDetector(); |
---|
313 | fTransporter = fDetector->GetDetectorTransportManager(); |
---|
314 | //fElectronics = fDetector->GetEusoElectronics(); |
---|
315 | fElectronics = gEusoElectronics; |
---|
316 | |
---|
317 | Msg(EsafMsg::Debug) << "TransportManager Modules" << MsgDispatch; |
---|
318 | Msg(EsafMsg::Debug) << "Optics: " << fTransporter->GetOptics()->IsA()->GetName() << MsgDispatch; |
---|
319 | Msg(EsafMsg::Debug) << "FocalPlane: " << fTransporter->GetFocalPlane()->IsA()->GetName() << MsgDispatch; |
---|
320 | Msg(EsafMsg::Info) << "Building complete" << MsgDispatch; |
---|
321 | |
---|
322 | if ( !OpenRoot(name, kTRUE) ) { |
---|
323 | Msg(EsafMsg::Warning) << "Unable to open the root file. Aborting" << MsgDispatch; |
---|
324 | return kFALSE; |
---|
325 | } |
---|
326 | |
---|
327 | // setup the tree |
---|
328 | |
---|
329 | fHeader->fNpixels = fElectronics->NumOfCh(); |
---|
330 | |
---|
331 | // loop here |
---|
332 | TrackPhotons(); |
---|
333 | |
---|
334 | CloseRoot(); |
---|
335 | SafeDelete(fHeader); |
---|
336 | |
---|
337 | return kTRUE; |
---|
338 | } |
---|
339 | |
---|
340 | //______________________________________________________________________________ |
---|
341 | Bool_t PixelMapBuilder::MakeMap( const char* hits, const char* mapfile) { |
---|
342 | |
---|
343 | if ( !OpenRoot(hits) ) return kFALSE; |
---|
344 | InitFit(); |
---|
345 | |
---|
346 | // Int_t first = 1; |
---|
347 | // Int_t last = fHeader->fNpixels; |
---|
348 | ComputeMap( 1, 0, "WEQ0" ); |
---|
349 | ClearFit(); |
---|
350 | |
---|
351 | CloseRoot(); |
---|
352 | |
---|
353 | WriteMap(mapfile); |
---|
354 | |
---|
355 | return kTRUE; |
---|
356 | } |
---|
357 | |
---|
358 | //______________________________________________________________________________ |
---|
359 | void PixelMapBuilder::ComputeMap(Int_t first, Int_t last, const char* opt) { |
---|
360 | // |
---|
361 | // Fits the histograms |
---|
362 | // |
---|
363 | TString options(opt); |
---|
364 | |
---|
365 | if ( !fMapTheta.GetSize() ) { |
---|
366 | Msg(EsafMsg::Warning) << "Map not defined. Aborting" << MsgDispatch; |
---|
367 | return; |
---|
368 | } |
---|
369 | if ( !first ) first = 1; |
---|
370 | if ( !last ) last = fHeader->fNpixels; |
---|
371 | |
---|
372 | Msg(EsafMsg::Info) << "Processing map, pixels " << first << |
---|
373 | " - " << last << MsgDispatch; |
---|
374 | TF1 *g; |
---|
375 | Double_t parTh[3], parPhi[3]; |
---|
376 | |
---|
377 | // |
---|
378 | // Tentative display |
---|
379 | // |
---|
380 | Bool_t display = gClient && fDisplay; |
---|
381 | TCanvas *cPoly = NULL, *cHist = NULL; |
---|
382 | TPolyMarker* m = NULL; |
---|
383 | TGProgressBar* p = NULL; |
---|
384 | TH2F* h = NULL; |
---|
385 | Stat_t entries(0); |
---|
386 | |
---|
387 | if ( display ) { |
---|
388 | // |
---|
389 | // Polymarker display |
---|
390 | // |
---|
391 | cPoly = fDisplay->FindAddTab("PixelMapDisplayPoly","Map projection"); |
---|
392 | cPoly->cd(); |
---|
393 | cPoly->DrawFrame(-Sin(fHeader->fFoV),-Sin(fHeader->fFoV), |
---|
394 | Sin(fHeader->fFoV), Sin(fHeader->fFoV)); |
---|
395 | m = new TPolyMarker(fHeader->fNpixels); |
---|
396 | // m->SetMarkerStyle(7); |
---|
397 | m->SetBit(kCanDelete); |
---|
398 | m->Draw(); |
---|
399 | |
---|
400 | // |
---|
401 | // Histogram display |
---|
402 | // |
---|
403 | cHist = fDisplay->FindAddTab("PixelMapDisplayHist","Histogram"); |
---|
404 | cHist->cd(); |
---|
405 | h = new TH2F("PixelMap","PixelMap", |
---|
406 | 100, -Sin(fHeader->fFoV),Sin(fHeader->fFoV), |
---|
407 | 100, -Sin(fHeader->fFoV),Sin(fHeader->fFoV)); |
---|
408 | h->SetDirectory(0); |
---|
409 | h->SetBit(kCanDelete); |
---|
410 | h->SetXTitle("Sin(#theta) #times Cos(#phi)"); |
---|
411 | h->SetYTitle("Sin(#theta) #times Sin(#phi)"); |
---|
412 | h->Draw("col"); |
---|
413 | |
---|
414 | p = fDisplay->GetProgressBar(); |
---|
415 | p->SetRange(1,fHeader->fNpixels); |
---|
416 | fDisplay->ShowProgressBarPosition(kTRUE,kFALSE,"%.0f pixels"); |
---|
417 | |
---|
418 | // no fit display if the display is active. |
---|
419 | if ( !options.Contains("0")) options.Append("0"); |
---|
420 | } |
---|
421 | |
---|
422 | |
---|
423 | for(Int_t k(first); k<=last; k++){ |
---|
424 | // |
---|
425 | // fit mut be called before UidToIndex because the buffer |
---|
426 | // may be not be filled yet |
---|
427 | // |
---|
428 | Bool_t fit = Fit(k, options); |
---|
429 | //cout << "fit " << fit << endl; |
---|
430 | Int_t j=UidToIndex(k); |
---|
431 | //cout << "index " << j << endl; |
---|
432 | |
---|
433 | // |
---|
434 | // fit pixel data |
---|
435 | // |
---|
436 | if (!fit) { |
---|
437 | fMapTheta[k-1] = -1; // -2*TwoPi() |
---|
438 | fMapThetaRMS[k-1] = -1; // 0 |
---|
439 | fMapPhi[k-1] = -1; // -2*TwoPi() |
---|
440 | fMapPhiRMS[k-1] = -1; // 0 |
---|
441 | } else { |
---|
442 | // retrieve results from histos |
---|
443 | g = fH1FitTheta->GetFunction("gaus"); |
---|
444 | |
---|
445 | if ( g ) g->GetParameters(parTh); |
---|
446 | // if g doesn't exists put dummy values |
---|
447 | else parTh[0] = parTh[1] = parTh[2] = -1; |
---|
448 | |
---|
449 | g = fH1FitPhi->GetFunction("gaus"); |
---|
450 | if ( g ) g->GetParameters(parPhi); |
---|
451 | else parPhi[0] = parPhi[1] = parPhi[2] = -1; |
---|
452 | |
---|
453 | // fill the containers |
---|
454 | fMapTheta[k-1] = parTh[1]; |
---|
455 | fMapThetaRMS[k-1] = parTh[2]; |
---|
456 | fMapPhi[k-1] = parPhi[1]; |
---|
457 | fMapPhiRMS[k-1] = parPhi[2]; |
---|
458 | } |
---|
459 | if ( fFitHistsTree ) fFitHistsTree->Fill(); |
---|
460 | |
---|
461 | if ( display && fit ) { |
---|
462 | |
---|
463 | Float_t x = Sin(fMapTheta[k])*Cos(fMapPhi[k]); |
---|
464 | Float_t y = Sin(fMapTheta[k])*Sin(fMapPhi[k]); |
---|
465 | m->SetPoint(j,x,y); |
---|
466 | h->Fill(x,y); |
---|
467 | |
---|
468 | } |
---|
469 | |
---|
470 | if ( !(k%100) || k == last ) { |
---|
471 | cout << '\r' << Form("Pixel %d of %d completed (%d%%) ", |
---|
472 | k, fHeader->fNpixels, k*100/fHeader->fNpixels) << flush; |
---|
473 | |
---|
474 | // |
---|
475 | // update the display |
---|
476 | // |
---|
477 | if (display && ( h->GetEntries() != entries || k == last ) ) { |
---|
478 | cPoly->Modified(); |
---|
479 | cPoly->Update(); |
---|
480 | cHist->Modified(); |
---|
481 | cHist->Update(); |
---|
482 | p->SetPosition((Float_t)k); |
---|
483 | if (!TThread::Self()) |
---|
484 | gSystem->ProcessEvents(); |
---|
485 | |
---|
486 | entries = h->GetEntries(); |
---|
487 | } |
---|
488 | } |
---|
489 | |
---|
490 | } |
---|
491 | cout << endl; |
---|
492 | |
---|
493 | |
---|
494 | |
---|
495 | } |
---|
496 | |
---|
497 | //______________________________________________________________________________ |
---|
498 | void PixelMapBuilder::OptimizeBuffer() { |
---|
499 | // |
---|
500 | // Tries to evaluate the expectes average number of hits per pixel and the |
---|
501 | // length of the buffers to match fBufferSize. Then reserves the |
---|
502 | // corresponding amount of memory in the bugger |
---|
503 | // |
---|
504 | |
---|
505 | if (!fHitsTree) return; |
---|
506 | |
---|
507 | Long64_t entries = fHitsTree->GetEntries(); |
---|
508 | |
---|
509 | // |
---|
510 | // expected average number of hits per pixel |
---|
511 | // |
---|
512 | Int_t average = Nint( entries*(1-Cos(fFullFoV))/(fHeader->fNpixels*(1-Cos(fHeader->fFoV))) ); |
---|
513 | average=average?average:1; |
---|
514 | |
---|
515 | Msg(EsafMsg::Info) << "Expected average number of entries per pixel: " << average << MsgDispatch; |
---|
516 | |
---|
517 | // |
---|
518 | // fBufferSize = sizeof(float)*2*average*length |
---|
519 | // |
---|
520 | double bufsize=sizeof(float)*2*average; |
---|
521 | Int_t length = fBufferSize/int(bufsize); |
---|
522 | length = Min(length,fHeader->fNpixels); |
---|
523 | Msg(EsafMsg::Info) << "Buffer lenght set to " << length << MsgDispatch; |
---|
524 | Msg(EsafMsg::Info) << "Expected buffer size in memory " << |
---|
525 | (sizeof(float)*2*average*length)/(1024*1024) << " Mb"<< MsgDispatch; |
---|
526 | |
---|
527 | // prepare the buffers |
---|
528 | fThetaFOVBuffer.resize(length); |
---|
529 | fPhiFOVBuffer.resize(length); |
---|
530 | |
---|
531 | for(size_t k(0); k<fThetaFOVBuffer.size(); k++) { |
---|
532 | fThetaFOVBuffer[k].reserve(average); |
---|
533 | fPhiFOVBuffer[k].reserve(average); |
---|
534 | } |
---|
535 | |
---|
536 | } |
---|
537 | |
---|
538 | //______________________________________________________________________________ |
---|
539 | Bool_t PixelMapBuilder::InitFit() { |
---|
540 | |
---|
541 | if ( !fHitsTree ) return kFALSE; |
---|
542 | |
---|
543 | // clear the content of the buffers. |
---|
544 | ClearPixelsBuffers(); |
---|
545 | // resize the map arrays |
---|
546 | fMapTheta.Set(fHeader->fNpixels); |
---|
547 | fMapThetaRMS.Set(fHeader->fNpixels); |
---|
548 | fMapPhi.Set(fHeader->fNpixels); |
---|
549 | fMapPhiRMS.Set(fHeader->fNpixels); |
---|
550 | |
---|
551 | fMapIndex.Set(fHeader->fNpixels+1); |
---|
552 | fMapIndex[0] = 0; |
---|
553 | |
---|
554 | OptimizeBuffer(); |
---|
555 | |
---|
556 | // note: upper limits in theta is a little bit more than Pi/6 |
---|
557 | Bool_t status = TH1::AddDirectoryStatus(); |
---|
558 | TH1::AddDirectory(kFALSE); |
---|
559 | |
---|
560 | fH1FitTheta = new TH1F("oapx_thfit","Temp fit histogram for Theta",fNbins, 0, 1); |
---|
561 | fH1FitTheta->SetXTitle("theta"); |
---|
562 | fH1FitTheta->SetYTitle("hits"); |
---|
563 | fH1FitPhi = new TH1F("oapx_phifit","Temp fit histogram for Phi", fNbins, 0, 1); |
---|
564 | fH1FitPhi->SetXTitle("phi"); |
---|
565 | fH1FitPhi->SetYTitle("hits"); |
---|
566 | fH2FitThetaPhi = new TH2F("oapx_thphifit","Temp fit histogram for Theta and Phi", |
---|
567 | fNbins, 0, 1, fNbins, 0, 1); |
---|
568 | fH2FitThetaPhi->SetXTitle("theta"); |
---|
569 | fH2FitThetaPhi->SetYTitle("phi"); |
---|
570 | |
---|
571 | Float_t r = Sin(fHeader->fFoV*1.1); |
---|
572 | fH2ThetaPhi = new TH2F("oapx_tp","tp distribution projected on a plane", |
---|
573 | fNbins, -r, r, fNbins, -r, r); |
---|
574 | fH2FitThetaPhi->SetXTitle("Sin(#theta) #times Cos(#phi)"); |
---|
575 | fH2FitThetaPhi->SetYTitle("Sin(#theta) #times Sin(#phi)"); |
---|
576 | |
---|
577 | TH1::AddDirectory(status); |
---|
578 | |
---|
579 | if ( !fFitHistsFileName.IsNull() ) { |
---|
580 | TString ext = ".root"; |
---|
581 | TString fname = fFitHistsFileName; |
---|
582 | |
---|
583 | if ( fname.EndsWith(ext)) fname.Resize(fname.Length()-ext.Length()); |
---|
584 | Msg(EsafMsg::Info) << "Saving histograms in " << fname << ext << MsgDispatch; |
---|
585 | fFitHistsFileName = fname+ext; |
---|
586 | fFitHistsFile = new TFile(fFitHistsFileName,"RECREATE"); |
---|
587 | |
---|
588 | fFitHistsTree = new TTree("pxmap_hist","Pixel Map histograms"); |
---|
589 | fFitHistsTree->Branch("fTheta",&fH1FitTheta, 100000, 0); |
---|
590 | fFitHistsTree->Branch("fPhi",&fH1FitPhi, 100000, 0); |
---|
591 | fFitHistsTree->Branch("fThetaPhi",&fH2FitThetaPhi, 100000, 0); |
---|
592 | fFitHistsTree->Branch("fThetaPhiWholeFoV",&fH2ThetaPhi, 100000, 0); |
---|
593 | fFitHistsTree->Branch("fUID",&fFitUID, "fUID/I"); |
---|
594 | |
---|
595 | } |
---|
596 | |
---|
597 | fFitUID = 0; |
---|
598 | |
---|
599 | return kTRUE; |
---|
600 | } |
---|
601 | |
---|
602 | //______________________________________________________________________________ |
---|
603 | Bool_t PixelMapBuilder::ClearFit() { |
---|
604 | // |
---|
605 | // Clears the histograms usend in the fit procedure |
---|
606 | // |
---|
607 | |
---|
608 | if ( fFitHistsTree ) { |
---|
609 | fFitHistsFile = fFitHistsTree->GetCurrentFile(); |
---|
610 | fFitHistsFile->cd(); |
---|
611 | fFitHistsTree->Write(); |
---|
612 | SafeDelete(fFitHistsTree); |
---|
613 | SafeDelete(fFitHistsFile); |
---|
614 | } |
---|
615 | |
---|
616 | fMapIndex.Set(0); |
---|
617 | // fHeader = 0; |
---|
618 | |
---|
619 | SafeDelete(fH1FitTheta); |
---|
620 | SafeDelete(fH1FitPhi); |
---|
621 | SafeDelete(fH2FitThetaPhi); |
---|
622 | SafeDelete(fH2ThetaPhi); |
---|
623 | |
---|
624 | ClearPixelsBuffers(); |
---|
625 | |
---|
626 | return kTRUE; |
---|
627 | } |
---|
628 | |
---|
629 | //______________________________________________________________________________ |
---|
630 | Bool_t PixelMapBuilder::FillPixelsBuffer(Int_t startuid ) { |
---|
631 | // |
---|
632 | // Fills the buffer starting from |
---|
633 | // |
---|
634 | |
---|
635 | // |
---|
636 | // Check boundaries |
---|
637 | // |
---|
638 | if ( startuid > fHeader->fNpixels ){ |
---|
639 | Msg(EsafMsg::Warning) << "First out of bounds" << endl; |
---|
640 | return kFALSE; |
---|
641 | } |
---|
642 | |
---|
643 | |
---|
644 | Int_t length = BufferEntries(); |
---|
645 | Int_t lastuid = (startuid+length-1) <= fHeader->fNpixels ? |
---|
646 | (startuid+length-1) : (fHeader->fNpixels-1); |
---|
647 | |
---|
648 | Long64_t nhits = fHitsTree->GetEntriesFast(); |
---|
649 | // |
---|
650 | // clear the buffer and set the buffer index to this channel |
---|
651 | // |
---|
652 | ClearPixelsBuffers(); |
---|
653 | fBufIndex = startuid-1; |
---|
654 | |
---|
655 | Bool_t display = gClient && fDisplay; |
---|
656 | TGProgressBar* p = NULL; |
---|
657 | Bool_t show = false, percent = false; |
---|
658 | TString format; |
---|
659 | Float_t min = 0, max = 0, pos = 0; |
---|
660 | if ( display ) { |
---|
661 | p = fDisplay->GetProgressBar(); |
---|
662 | format = p->GetFormat(); |
---|
663 | min = p->GetMin(); |
---|
664 | max = p->GetMax(); |
---|
665 | show = p->GetShowPos(); |
---|
666 | percent = p->UsePercent(); |
---|
667 | pos = p->GetPosition(); |
---|
668 | |
---|
669 | p->SetRange(0,100); |
---|
670 | fDisplay->ShowProgressBarPosition(kTRUE,kFALSE,"Buffering... (%.1f%%)"); |
---|
671 | } |
---|
672 | |
---|
673 | for( Long64_t i(0); i<nhits; i++) { |
---|
674 | fHitsTree->GetEntry(i); |
---|
675 | Int_t uid = fHit.fUID; |
---|
676 | |
---|
677 | if ( uid >= startuid && uid <= lastuid ) { |
---|
678 | fThetaFOVBuffer[uid-fBufIndex-1].push_back(fHit.fTheta); |
---|
679 | fPhiFOVBuffer[uid-fBufIndex-1].push_back(fHit.fPhi); |
---|
680 | } |
---|
681 | |
---|
682 | // |
---|
683 | // if the map index has not been filled, fMapIndex->fArray[0] == 0 |
---|
684 | // fill it |
---|
685 | // |
---|
686 | if ( !fMapIndex[0] ) |
---|
687 | fMapIndex[uid]++; |
---|
688 | |
---|
689 | if ( !((i+1)%100000) || i == nhits-1) { |
---|
690 | Float_t x = ((Double_t)(i+1)*100)/(Double_t)nhits; |
---|
691 | // EsafMsg useless here |
---|
692 | cout << '\r' << i+1 << " out of " << nhits << " hits processed (" |
---|
693 | << Nint(Floor(x)) << "%)" << flush; |
---|
694 | |
---|
695 | if (display) { |
---|
696 | p->SetPosition(x); |
---|
697 | gClient->ProcessEventsFor(p); |
---|
698 | } |
---|
699 | } |
---|
700 | } |
---|
701 | |
---|
702 | fMapIndex[0] = 1; |
---|
703 | |
---|
704 | cout << endl; |
---|
705 | Msg(EsafMsg::Info) << "Buffer filled" << MsgDispatch; |
---|
706 | |
---|
707 | if ( display ) { |
---|
708 | p->SetRange(min,max); |
---|
709 | p->SetPosition(pos); |
---|
710 | fDisplay->ShowProgressBarPosition(show,percent,format); |
---|
711 | gClient->ProcessEventsFor(p); |
---|
712 | } |
---|
713 | |
---|
714 | return kTRUE; |
---|
715 | |
---|
716 | } |
---|
717 | |
---|
718 | //______________________________________________________________________________ |
---|
719 | Bool_t PixelMapBuilder::CheckPixelInBuffer(Int_t uid) { |
---|
720 | // load id if it is out of current buffer |
---|
721 | |
---|
722 | // check uid to be valid |
---|
723 | if ( uid < 1 || uid > fHeader->fNpixels) { |
---|
724 | MsgForm(EsafMsg::Warning,"PixelMapBuilder: uid out of range"); |
---|
725 | return kFALSE; |
---|
726 | } |
---|
727 | |
---|
728 | // transform in array's index |
---|
729 | Int_t idx = uid-1; |
---|
730 | |
---|
731 | Int_t length = BufferEntries(); |
---|
732 | // check if idx if in the current buffer |
---|
733 | if ( fBufIndex == -1 || idx < fBufIndex || idx >= fBufIndex+length){ |
---|
734 | Msg(EsafMsg::Info) << "Channel out of buffer. Switching to the right buffer" << MsgDispatch; |
---|
735 | Msg(EsafMsg::Info) << "Buffer " << idx/length << MsgDispatch; |
---|
736 | // load the right buffer |
---|
737 | return FillPixelsBuffer((idx/length)*length+1); |
---|
738 | } |
---|
739 | |
---|
740 | return kTRUE; |
---|
741 | } |
---|
742 | |
---|
743 | //______________________________________________________________________________ |
---|
744 | Bool_t PixelMapBuilder::ClearPixelsBuffers() { |
---|
745 | // |
---|
746 | // clears buffers |
---|
747 | // |
---|
748 | |
---|
749 | for(size_t k(0); k<fThetaFOVBuffer.size(); k++) { |
---|
750 | fThetaFOVBuffer[k].resize(0); |
---|
751 | fPhiFOVBuffer[k].resize(0); |
---|
752 | } |
---|
753 | fBufIndex = kBufEmpty; |
---|
754 | |
---|
755 | return kTRUE; |
---|
756 | } |
---|
757 | |
---|
758 | //______________________________________________________________________________ |
---|
759 | Bool_t PixelMapBuilder::Fit(Int_t uid, Option_t *option) { |
---|
760 | |
---|
761 | TString opt = option; |
---|
762 | // check on the index if there are enough it for fitting |
---|
763 | if ( fMapIndex.GetSize() && // index allocated |
---|
764 | fMapIndex[0] && // index filled |
---|
765 | fMapIndex[uid] < fHitsThreshold ) { // px under thres |
---|
766 | if ( !opt.Contains("Q") ) |
---|
767 | MsgForm(EsafMsg::Debug,"Pixel %d under threshold. Skipping", uid); |
---|
768 | return kFALSE; |
---|
769 | } |
---|
770 | |
---|
771 | if ( !CheckPixelInBuffer(uid) ) { |
---|
772 | MsgForm(EsafMsg::Debug,"Pixel %d not in buffer. Skipping", uid); |
---|
773 | return kFALSE; |
---|
774 | } |
---|
775 | |
---|
776 | // clear histogramis |
---|
777 | fH2ThetaPhi->Reset(); |
---|
778 | fH1FitTheta->Reset(); |
---|
779 | fH1FitPhi->Reset(); |
---|
780 | fH2FitThetaPhi->Reset(); |
---|
781 | // translate uid in buffer index and check number of hits |
---|
782 | Int_t idx = UidToIndex(uid); |
---|
783 | |
---|
784 | if ( !opt.Contains("Q") ) |
---|
785 | MsgForm(EsafMsg::Info,"Fit: %d entries for channel %d (idx = %d).", |
---|
786 | fThetaFOVBuffer[idx].size(), uid, idx); |
---|
787 | |
---|
788 | if ( (Int_t)fThetaFOVBuffer[idx].size() < fHitsThreshold ) { |
---|
789 | if ( !opt.Contains("Q") ) |
---|
790 | MsgForm(EsafMsg::Debug,"Pixel %d under threshold. Skipping", uid); |
---|
791 | return kFALSE; |
---|
792 | } |
---|
793 | |
---|
794 | |
---|
795 | // transform (th,phi)->(x,y) |
---|
796 | Float_t x,y; |
---|
797 | |
---|
798 | for(size_t j(0); j<fThetaFOVBuffer[idx].size(); j++) { |
---|
799 | x = Sin(fThetaFOVBuffer[idx][j]) |
---|
800 | *Cos(fPhiFOVBuffer[idx][j]); |
---|
801 | y = Sin(fThetaFOVBuffer[idx][j]) |
---|
802 | *Sin(fPhiFOVBuffer[idx][j]); |
---|
803 | |
---|
804 | fH2ThetaPhi->Fill(x,y); |
---|
805 | } |
---|
806 | |
---|
807 | // look for the most populated bin |
---|
808 | Int_t lx, ly, lz; |
---|
809 | fH2ThetaPhi->GetMaximumBin(lx, ly, lz); |
---|
810 | |
---|
811 | Float_t xCenter = fH2ThetaPhi->GetXaxis()->GetBinCenter(lx); |
---|
812 | Float_t yCenter = fH2ThetaPhi->GetYaxis()->GetBinCenter(ly); |
---|
813 | |
---|
814 | // calculate errors |
---|
815 | Float_t dr = fH2ThetaPhi->GetXaxis()->GetBinWidth(1); |
---|
816 | Float_t r = Sqrt(xCenter*xCenter+yCenter*yCenter); |
---|
817 | |
---|
818 | // xmean,xxmean,ymean,yymean->th, therr, ph, phierr |
---|
819 | Float_t thCenter = ASin(r); |
---|
820 | Float_t phiCenter = ATan2(yCenter, xCenter); |
---|
821 | Float_t thErr = dr/Cos(thCenter); |
---|
822 | Float_t phiErr = dr/r; |
---|
823 | |
---|
824 | |
---|
825 | // reset fit histos |
---|
826 | |
---|
827 | // fill and fit theta |
---|
828 | fH1FitTheta->GetXaxis()->SetLimits(thCenter-fNsigma*thErr, thCenter+fNsigma*thErr); |
---|
829 | fH1FitTheta->GetXaxis()->SetRange(); |
---|
830 | // fill and fit phi |
---|
831 | fH1FitPhi->GetXaxis()->SetLimits(phiCenter-fNsigma*phiErr, phiCenter+fNsigma*phiErr); |
---|
832 | fH1FitPhi->GetXaxis()->SetRange(); |
---|
833 | |
---|
834 | // fill and fit theta |
---|
835 | fH2FitThetaPhi->GetXaxis()->SetLimits(thCenter-fNsigma*thErr, thCenter+fNsigma*thErr); |
---|
836 | fH2FitThetaPhi->GetYaxis()->SetLimits(phiCenter-fNsigma*phiErr, phiCenter+fNsigma*phiErr); |
---|
837 | fH2FitThetaPhi->GetXaxis()->SetRange(); |
---|
838 | fH2FitThetaPhi->GetYaxis()->SetRange(); |
---|
839 | |
---|
840 | // save quadrant where phi is |
---|
841 | Short_t quad = (Short_t)(phiCenter/TMath::PiOver2()); |
---|
842 | |
---|
843 | // fill |
---|
844 | for(size_t j(0); j<fThetaFOVBuffer[idx].size(); j++) { |
---|
845 | Float_t phi = fPhiFOVBuffer[idx][j]; |
---|
846 | Float_t theta = fThetaFOVBuffer[idx][j]; |
---|
847 | switch ( quad ) { |
---|
848 | case 1: |
---|
849 | if ( fPhiFOVBuffer[idx][j] > 3*TMath::PiOver2() ) |
---|
850 | phi -= TwoPi(); |
---|
851 | break; |
---|
852 | case 4: |
---|
853 | if ( fPhiFOVBuffer[idx][j] < TMath::PiOver2() ) |
---|
854 | phi += TwoPi(); |
---|
855 | break; |
---|
856 | default: |
---|
857 | break; |
---|
858 | } |
---|
859 | |
---|
860 | fH1FitTheta->Fill(theta); |
---|
861 | fH1FitPhi->Fill(phi); |
---|
862 | fH2FitThetaPhi->Fill(theta, phi); |
---|
863 | } |
---|
864 | |
---|
865 | //FIXME: there can be a ghost; add a TH2F histo and use |
---|
866 | // fH1FitTheta and fH1FitPhi as projections |
---|
867 | // (TH2::GetProjections()) |
---|
868 | // this is a low priority fix |
---|
869 | |
---|
870 | // fit |
---|
871 | fH1FitTheta->Fit("gaus",opt); |
---|
872 | fH1FitPhi->Fit("gaus",opt); |
---|
873 | |
---|
874 | fFitUID = uid; |
---|
875 | return kTRUE; |
---|
876 | |
---|
877 | } |
---|
878 | //______________________________________________________________________________ |
---|
879 | TCanvas *PixelMapBuilder::DrawHistPad() const { |
---|
880 | // Draws one of the buffer's histos |
---|
881 | |
---|
882 | TCanvas *c = new TCanvas("PixelMapFit","Pixel Dist fit"); |
---|
883 | |
---|
884 | TText txt; |
---|
885 | txt.SetTextFont(72); |
---|
886 | txt.SetTextAlign(13); |
---|
887 | txt.SetTextSize(5.5e-2); |
---|
888 | txt.DrawTextNDC(0.01, 0.99,Form("Pixel %d",fFitUID)); |
---|
889 | |
---|
890 | TPad* pth = new TPad("PixelMapFit_Theta","PixelMapFit #Theta", 0., 0.475, 0.5, 0.95); |
---|
891 | TPad* pphi = new TPad("PixelMapFit_Phi","PixelMapFit #Phi", 0, 0, 0.5, 0.475); |
---|
892 | TPad* p2D = new TPad("PixelMapFit_2D","PixelMapFit #Theta-#Phi", 0.5, 0.475, 1., 0.95); |
---|
893 | TPad* p2DFoV = new TPad("PixelMapFit_2D_FoV","PixelMapFit 2D Whole FoV", 0.5, 0., 1., 0.475); |
---|
894 | |
---|
895 | pth->Draw(); |
---|
896 | pphi->Draw(); |
---|
897 | p2D->Draw(); |
---|
898 | p2DFoV->Draw(); |
---|
899 | |
---|
900 | pth->cd(); |
---|
901 | fH1FitTheta->Draw(); |
---|
902 | |
---|
903 | pphi->cd(); |
---|
904 | fH1FitPhi->Draw(); |
---|
905 | |
---|
906 | p2D->cd(); |
---|
907 | fH2FitThetaPhi->Draw("colz"); |
---|
908 | |
---|
909 | p2DFoV->cd(); |
---|
910 | fH2ThetaPhi->Draw("colz"); |
---|
911 | |
---|
912 | TEllipse *e = new TEllipse(0,0,Sin( fHeader->fFoV )); |
---|
913 | e->SetBit(kCanDelete); |
---|
914 | e->Draw(); |
---|
915 | |
---|
916 | return c; |
---|
917 | } |
---|
918 | |
---|
919 | //______________________________________________________________________________ |
---|
920 | void PixelMapBuilder::DumpHit() const { |
---|
921 | // |
---|
922 | // |
---|
923 | // |
---|
924 | Msg(EsafMsg::Info) << "fHit.fUID : " << fHit.fUID << MsgDispatch; |
---|
925 | Msg(EsafMsg::Info) << "fHit.fTheta : " << fHit.fTheta << MsgDispatch; |
---|
926 | Msg(EsafMsg::Info) << "fHit.fPhi : " << fHit.fPhi << MsgDispatch; |
---|
927 | Msg(EsafMsg::Info) << "fHit.fLambda : " << fHit.fLambda << MsgDispatch; |
---|
928 | } |
---|
929 | |
---|
930 | //______________________________________________________________________________ |
---|
931 | void PixelMapBuilder::WriteMap(const char* name) { |
---|
932 | // |
---|
933 | // Write the map to file in the ESAF format |
---|
934 | // |
---|
935 | |
---|
936 | |
---|
937 | ofstream fout; |
---|
938 | if ( name ) { |
---|
939 | fout.open(name); |
---|
940 | if (!fout.is_open()) { |
---|
941 | Msg(EsafMsg::Warning) << "Unable to open " << name << MsgDispatch; |
---|
942 | return; |
---|
943 | } |
---|
944 | Msg(EsafMsg::Info) << "Saving map in " << name << MsgDispatch; |
---|
945 | } |
---|
946 | |
---|
947 | fout << "# " << endl; |
---|
948 | fout << "# $Id: PixelMapBuilder.cc 2927 2011-06-16 18:02:41Z mabl $" << endl; |
---|
949 | fout << "# PixelMapBuilder" << endl; |
---|
950 | fout << "" << endl; |
---|
951 | fout << "###############################################################################" << endl; |
---|
952 | fout << "# ESAF: Euso Simulation and Analysis Framework #" << endl; |
---|
953 | fout << "# #" << endl; |
---|
954 | fout << "# Package: Optics #" << endl; |
---|
955 | fout << "# Coordinator: Alessandro.Thea #" << endl; |
---|
956 | fout << "# #" << endl; |
---|
957 | fout << "###############################################################################" << endl; |
---|
958 | fout << "#" << endl; |
---|
959 | fout << "# uid theta thetaRMS phi phiRMS" << endl; |
---|
960 | |
---|
961 | |
---|
962 | for ( Int_t k(0); k<MapSize(); k++) |
---|
963 | fout << Form("%d %f %f %f %f", |
---|
964 | k+1, fMapTheta[k], fMapThetaRMS[k], fMapPhi[k], fMapPhiRMS[k]) << endl; |
---|
965 | |
---|
966 | fout.close(); |
---|
967 | } |
---|