]> git.uio.no Git - u/mrichter/AliRoot.git/blob - FMD/AliFMDSurveyToAlignObjs.cxx
Fixed up some code for making alignment objects from survey
[u/mrichter/AliRoot.git] / FMD / AliFMDSurveyToAlignObjs.cxx
1 //
2 // Class to take survey data and 
3 // transform that to alignment objects. 
4 // 
5 // FMD
6 //
7 #include "AliFMDSurveyToAlignObjs.h"
8 #include "AliLog.h"
9 #include "AliSurveyPoint.h"
10 #include <TGraph2DErrors.h>
11 #include <TF2.h>
12 #include <TVector3.h>
13 #include <iostream>
14 #include <iomanip>
15 #include <TMath.h>
16 #include <TRotation.h>
17 #include <TGeoMatrix.h>
18 #include <TGeoManager.h>
19 #include <TGeoPhysicalNode.h>
20 #include "AliFMDGeometry.h"
21
22 //____________________________________________________________________
23 Double_t
24 AliFMDSurveyToAlignObjs::GetUnitFactor() const
25 {
26   // Returns the conversion factor from the measured values to
27   // centimeters. 
28   if (!fSurveyObj) return 0;
29   TString units(fSurveyObj->GetUnits());
30   if      (units.CompareTo("mm", TString::kIgnoreCase) == 0) return .1;
31   else if (units.CompareTo("cm", TString::kIgnoreCase) == 0) return 1.;
32   else if (units.CompareTo("m",  TString::kIgnoreCase) == 0) return 100.;
33   return 1;
34 }
35
36 //____________________________________________________________________
37 Bool_t
38 AliFMDSurveyToAlignObjs::GetPoint(const char* name, 
39                                   TVector3&   point, 
40                                   TVector3&   error) const
41 {
42   // Get named point.   On return, point will contain the point
43   // coordinates in centimeters, and error will contain the
44   // meassurement errors in centimeters too.  If no point is found,
45   // returns false, otherwise true. 
46   if (!fSurveyPoints) return kFALSE;
47   
48   Double_t unit = GetUnitFactor();
49   if (unit == 0) return kFALSE;
50   
51   TObject* obj  = fSurveyPoints->FindObject(name);
52   if (!obj) return kFALSE;
53   
54   AliSurveyPoint* p = static_cast<AliSurveyPoint*>(obj);
55   point.SetXYZ(unit * p->GetX(), 
56                unit * p->GetY(),
57                unit * p->GetZ());
58   error.SetXYZ(unit * p->GetPrecisionX(),
59                unit * p->GetPrecisionY(),
60                unit * p->GetPrecisionZ());
61   return kTRUE;
62 }
63
64 //____________________________________________________________________
65 Bool_t 
66 AliFMDSurveyToAlignObjs::CalculatePlane(const     TVector3& a, 
67                                         const     TVector3& b,
68                                         const     TVector3& c, 
69                                         Double_t  depth,
70                                         Double_t* trans,
71                                         Double_t* rot) const
72 {
73   // 
74   // Calculate the plane translation and rotation from 3 survey points
75   // 
76   // Parameters:
77   //    a     1st Survey point 
78   //    b     2nd Survey point
79   //    c     3rd Survey point
80   //    trans Translation vector
81   //    rot   Rotation matrix (direction cosines)
82   // 
83   // Return:
84   //    
85   //
86
87   // Vector a->b, b->c, and normal to plane defined by these two
88   // vectors. 
89   TVector3 ab(b-a), bc(c-a);
90   
91   // Normal vector to the plane of the fiducial marks obtained
92   // as cross product of the two vectors on the plane d0^d1
93   TVector3 nn(ab.Cross(bc));
94   if (nn.Mag() < 1e-8) { 
95     Info("CalculatePlane", "Normal vector is null vector");
96     return kFALSE;
97   }
98   
99   // We express the plane in Hessian normal form.
100   //
101   //   n x = -p,
102   //
103   // where n is the normalised normal vector given by 
104   // 
105   //   n_x = a / l,   n_y = b / l,   n_z = c / l,  p = d / l
106   //
107   // with l = sqrt(a^2+b^2+c^2) and a, b, c, and d are from the
108   // normal plane equation 
109   //
110   //  ax + by + cz + d = 0
111   // 
112   // Normalize
113   TVector3 n(nn.Unit());
114   // Double_t p = - (n * a);
115   
116   // The center of the square with the fiducial marks as the
117   // corners.  The mid-point of one diagonal - md.  Used to get the
118   // center of the surveyd box. 
119   // TVector3 md(a + c);
120   // md *= 1/2.;
121   TVector3 md(c + b);
122   md *= 1./2;
123
124   // The center of the box. 
125   TVector3 orig(md - depth * n);
126   trans[0] = orig[0];
127   trans[1] = orig[1];
128   trans[2] = orig[2];
129   
130   // Normalize the spanning vectors 
131   TVector3 uab(ab.Unit());
132   TVector3 ubc(bc.Unit());
133   
134   for (size_t i = 0; i < 3; i++) { 
135     // rot[i * 3 + 0] = ubc[i];
136     // rot[i * 3 + 1] = uab[i];
137     rot[i * 3 + 0] = uab[i];
138     rot[i * 3 + 1] = ubc[i];
139     rot[i * 3 + 2] = n[i];
140   }
141   return kTRUE;
142 }
143
144 //____________________________________________________________________
145 Bool_t 
146 AliFMDSurveyToAlignObjs::FitPlane(const TObjArray& points, 
147                                   const TObjArray& errors,
148                                   Double_t         /* depth */,
149                                   Double_t*        trans,
150                                   Double_t*        rot) const
151 {
152   // 
153   // Calculate the plane rotation and translation by doing a fit of
154   // the plane equation to the surveyed points.  At least 4 points
155   // must be passed in the @a points array with corresponding errors
156   // in the array @a errors.  The arrays are assumed to contain
157   // TVector3 objects.
158   // 
159   // Parameters:
160   //    points Array surveyed positions
161   //    errors Array of errors corresponding to @a points
162   //    depth  Survey targets depth (perpendicular to the plane)
163   //    trans  On return, translation of the plane
164   //    rot    On return, rotation (direction cosines) of the plane
165   // 
166   // Return:
167   //    @c true on success, @c false otherwise
168   //
169
170   Int_t nPoints = points.GetEntries();
171   if (nPoints < 4) { 
172     AliError(Form("Cannot fit a plane equation to less than 4 survey points, "
173                   "got only %d", nPoints));
174     return kFALSE;
175   }
176   
177   TGraph2DErrors g;
178   // Loop and fill graph 
179   for (int i = 0; i < nPoints; i++) {
180     TVector3* p = static_cast<TVector3*>(points.At(i));
181     TVector3* e = static_cast<TVector3*>(errors.At(i));
182   
183     if (!p || !e) continue;
184     
185     g.SetPoint(i, p->X(), p->Y(), p->Z());
186     g.SetPointError(i, e->X(), e->Y(), e->Z());
187   }
188
189   // Check that we have enough points
190   if (g.GetN() < 4) { 
191     AliError(Form("Only got %d survey points - no good for plane fit",
192                   g.GetN()));
193     return kFALSE;
194   }
195
196   // Next, declare fitting function and fit to graph. 
197   // Fit to the plane equation: 
198   // 
199   //   ax + by + cz + d = 0
200   //
201   // or 
202   // 
203   //   z = - ax/c - by/c - d/c
204   //
205   TF2 f("plane", "-[0]*x-[1]*y-[2]", 
206         g.GetXmin(), g.GetXmax(), g.GetYmin(), g.GetYmax());
207   g.Fit(&f, "Q");
208   
209   // Now, extract the normal and offset
210   TVector3 nv(f.GetParameter(0), f.GetParameter(1), 1);
211   TVector3 n(nv.Unit());
212   Double_t p = -f.GetParameter(2);
213   
214   // Create two vectors spanning the plane 
215   TVector3 a(1, 0, f.Eval(1, 0)-p);
216   TVector3 b(0, -1, f.Eval(0, -1)-p);
217   TVector3 ua(a.Unit());
218   TVector3 ub(b.Unit());
219   // Double_t angAb = ua.Angle(ub);
220   // PrintVector("ua: ", ua);
221   // PrintVector("ub: ", ub);
222   // std::cout << "Angle: " << angAb * 180 / TMath::Pi() << std::endl;
223     
224   for (size_t i = 0; i < 3; i++) { 
225     rot[i * 3 + 0] = ua[i];
226     rot[i * 3 + 1] = ub[i];
227     rot[i * 3 + 2] = n[i];
228   }
229     
230   // The intersection of the plane is given by (0, 0, -d/c)
231   trans[0] = 0;
232   trans[1] = 0;
233   trans[2] = p;
234   
235   return kTRUE;
236 }
237
238
239 //____________________________________________________________________
240 Bool_t
241 AliFMDSurveyToAlignObjs::MakeDelta(const char*  path, 
242                                    const Double_t*    rot, 
243                                    const Double_t*    trans, 
244                                    TGeoHMatrix& delta) const
245 {
246   // 
247   // Create a delta transform from a global rotation matrix and
248   // translation. 
249   // 
250   // Parameters:
251   //    path   Path of element to transform.
252   //    rot    Rotation matrix (direction cosines)
253   //    trans  Translation 
254   //    delta  On return, the delta transform
255   // 
256   // Return:
257   //    Newly 
258   //
259   if (!gGeoManager)           return kFALSE;
260   if (!gGeoManager->cd(path)) return kFALSE;
261   
262   
263   TGeoMatrix*  global = gGeoManager->GetCurrentMatrix();
264 #if 0
265   PrintRotation(Form("%s rot:", global->GetName()),global->GetRotationMatrix());
266   PrintVector(Form("%s trans:", global->GetName()),global->GetTranslation());
267 #endif
268
269   return MakeDelta(global, rot, trans, delta);
270 }
271
272 //____________________________________________________________________
273 Bool_t
274 AliFMDSurveyToAlignObjs::MakeDelta(const TGeoMatrix*  global,
275                                    const Double_t*    rot, 
276                                    const Double_t*    trans, 
277                                    TGeoHMatrix& delta) const
278 {
279   // 
280   // Create a delta transform from a global rotation matrix and
281   // translation. 
282   // 
283   // Parameters:
284   //    global Global matrix of element to transform.
285   //    rot    Rotation matrix (direction cosines)
286   //    trans  Translation 
287   //    delta  On return, the delta transform
288   // 
289   // Return:
290   //    Newly 
291   //
292   TGeoHMatrix* geoM = new TGeoHMatrix;
293   geoM->SetTranslation(trans);
294   geoM->SetRotation(rot);
295
296   delta = global->Inverse();
297   delta.MultiplyLeft(geoM);
298   
299   return true;
300 }
301
302 //____________________________________________________________________
303 Bool_t
304 AliFMDSurveyToAlignObjs::GetFMD1Plane(Double_t* rot, Double_t* trans) const
305 {
306   // 
307   // Get the FMD1 plane from the survey points
308   // 
309   // Parameters:
310   //    rot    Rotation matrix (direction cosines)
311   //    trans  Translation
312   // 
313   // Return:
314   //    @c true on success, @c false otherwise.
315   //
316
317   // The possile survey points 
318   TVector3  icb, ict, ocb, oct, dummy;
319   Int_t     missing = 0;
320   if (!GetPoint("V0L_ICB", icb, dummy)) missing++;
321   if (!GetPoint("V0L_ICT", ict, dummy)) missing++;
322   if (!GetPoint("V0L_OCB", ocb, dummy)) missing++;
323   if (!GetPoint("V0L_OCT", oct, dummy)) missing++;
324
325   // Check that we have enough points
326   if (missing > 1) { 
327     AliWarning(Form("Only got %d survey points - no good for FMD1 plane",
328                     4-missing));
329     return kFALSE;
330   }
331
332 #if 0
333   const char* lidN = "FMD1_lid_mat0";
334   TGeoMatrix* lidM = static_cast<TGeoMatrix*>(gGeoManager->GetListOfMatrices()
335                                               ->FindObject(lidN));
336   if (!lidM) { 
337     AliError(Form("Couldn't find FMD1 lid transformation %s", lidN));
338     return kFALSE;
339   }
340
341   const Double_t* lidT = lidM->GetTranslation();
342   Double_t        lidZ = lidT[2];
343   Double_t        off  = lidZ-3.3;
344 #else
345   Double_t        off  = 0;
346 #endif
347
348   // if (!CalculatePlane(ocb, icb, ict, off, trans, rot)) return kFALSE;
349   if (!CalculatePlane(ocb, icb, oct, off, trans, rot)) return kFALSE;
350   PrintRotation("FMD1 rotation:",  rot);
351   PrintVector("FMD1 translation:", trans);
352
353   return kTRUE;
354 }
355
356 //____________________________________________________________________
357 Bool_t
358 AliFMDSurveyToAlignObjs::DoFMD1()
359 {
360   // 
361   // Do the FMD1 analysis.  We have 4 survey targets on V0-A on the
362   // C-side.  These are 
363   //
364   //  - V0A_ICT  In-side, C-side, top.
365   //  - V0A_ICB  In-side, C-side, bottom.  
366   //  - V0A_OCT  Out-side, C-side, top.  
367   //  - V0A_OCB  Out-side, C-side, bottom.
368   // 
369   // These 4 survey targets sit 3.3mm over the V0-A C-side surface, or
370   // 3.3mm over the back surface of FMD1.  
371   //
372   // Since these are really sitting on a plane, we can use the method
373   // proposed by the CORE offline. 
374   // 
375   // Return:
376   //    @c true on success, @c false otherwise.
377   //
378
379   // Do the FMD1 stuff
380   Double_t rot[9], trans[3];
381   if (!GetFMD1Plane(rot, trans)) return kFALSE;
382   // const char* path = "/ALIC_1/F1MT_1/FMD1_lid_0";
383
384 #if 0  
385   // TGeoHMatrix delta;
386   Double_t gRot[9], gTrans[3];
387   TVector3 ocb(-127, -220, 324.67);
388   TVector3 oct(-127, +220, 324.67);
389   TVector3 icb(+127, -220, 324.67);
390   TVector3 ict(+127, +220, 324.67);
391   if (!CalculatePlane(ocb, icb, oct, 0, gTrans, gRot)) { 
392     Warning("DoFMD1", "Failed to make reference plane");
393     return kFALSE;
394   }
395   PrintRotation("FMD1 ref rotation:",  gRot);
396   PrintVector("FMD1 ref translation:", gTrans);
397   TGeoRotation ggRot; ggRot.SetMatrix(gRot);
398   TGeoCombiTrans global(gTrans[0], gTrans[1], gTrans[2], &ggRot);
399 #endif
400
401   TGeoTranslation global(0,0,324.670);
402   if (!MakeDelta(&global, rot, trans, fFMD1Delta)) 
403     return kFALSE;
404   
405   // PrintRotation("FMD1 delta rotation:",  fFMD1Delta.GetRotationMatrix());
406   // PrintVector("FMD1 delta translation:", fFMD1Delta.GetTranslation());
407
408   return kTRUE;
409 }
410
411 //____________________________________________________________________
412 Bool_t
413 AliFMDSurveyToAlignObjs::GetFMD2Plane(Double_t* rot, Double_t* trans) const
414 {
415   // 
416   // Get the surveyed plane corresponding to the backside of FMD2.
417   // The plane is done as a best fit of the plane equation to at least
418   // 4 of the available survey points.
419   // 
420   // Parameters:
421   //    rot    Rotation matrix (direction cosines)
422   //    trans  Translation vector.
423   // 
424   // Return:
425   //    @c true on success, @c false otherwise
426   //
427
428   // The possible survey points 
429   const char*    names[] = { "FMD2_ITOP",  "FMD2_OTOP", 
430                              "FMD2_IBOTM", "FMD2_OBOTM", 
431                              "FMD2_IBOT",  "FMD2_OBOT", 
432                              0 };
433   const char**   name    = names;
434
435   TObjArray points;
436   TObjArray errors;
437   
438   // Loop and fill graph 
439   while (*name) {
440     TVector3 p, e;
441     if (!GetPoint(*name++, p, e)) continue;
442     
443     points.Add(new TVector3(p));
444     errors.Add(new TVector3(e));
445   }
446   if (points.GetEntries() < 4) { 
447     AliWarning(Form("Only got %d survey points - no good for FMD2 plane",
448                     points.GetEntries()));
449     return kFALSE;
450   }
451
452   return FitPlane(points, errors, 0, trans, rot);
453 }
454
455 #define M(I,J) rot[(J-1) * 3 + (I-1)]
456 //____________________________________________________________________
457 Bool_t
458 AliFMDSurveyToAlignObjs::DoFMD2()
459 {
460   // 
461   // Do the FMD2 calculations.  We have 6 survey points of which only
462   // 5 are normally surveyed.  These are all sittings 
463   //
464   //  - FMD2_ITOP   - In-side, top
465   //  - FMD2_IBOTM  - In-side, middle bottom
466   //  - FMD2_IBOT   - In-side, bottom
467   //  - FMD2_OTOP   - Out-side, top
468   //  - FMD2_OBOTM  - Out-side, middle bottom
469   //  - FMD2_OBOT   - Out-side, bottom
470   //
471   // The nominal coordinates of these retro-fitted survey stickers
472   // isn't known.  Also, these stickers are put on a thin (0.3mm
473   // thick) carbon cover which flexes quite easily.  This means, that
474   // to rotations and xy-translation obtained from the survey data
475   // cannot be used, and left is only the z-translation.
476   //
477   // Further more, since FMD2 to is attached to the ITS SPD thermal
478   // screen, it is questionable if the FMD2 survey will ever be used. 
479   // 
480   // Return:
481   //    @c true on success, @c false otherwise.
482   //
483
484   // Do the FMD2 stuff
485   Double_t rot[9], trans[3];
486   if (!GetFMD2Plane(rot, trans)) return kFALSE;
487   PrintRotation("FMD2 rotation:",  rot);
488   PrintVector("FMD2 translation:", trans);
489
490 #if 0
491   for (int i = 0; i < 3; i++) { 
492     for (int j = 0; j < 3; j++) { 
493       rot[i*3+j] = (i == j ? 1 : 0);
494     }
495   }
496 #endif
497   trans[0] = trans[1] = 0;
498   trans[2] += 0.015;
499   // PrintRotation("FMD2 rotation:",  rot);
500   // PrintVector("FMD2 translation:", trans);
501   
502   // TGeoHMatrix delta;
503   if (!MakeDelta("/ALIC_1/F2MT_2/FMD2_support_0/FMD2_back_cover_2", 
504                  rot, trans, fFMD2Delta)) return kFALSE;
505   
506   // PrintRotation("FMD2 delta rotation:",  fFMD2Delta.GetRotationMatrix());
507   // PrintVector("FMD2 delta translation:", fFMD2Delta.GetTranslation());
508
509   return kTRUE;
510 }
511
512 //____________________________________________________________________
513 void
514 AliFMDSurveyToAlignObjs::Run()
515 {
516   // 
517   // Run the task.
518   // 
519   //  
520
521   AliFMDGeometry* geom = AliFMDGeometry::Instance();
522   geom->Init();
523   geom->InitTransformations();
524   
525   DoFMD1();
526   DoFMD2();
527 }
528
529 //____________________________________________________________________
530 void
531 AliFMDSurveyToAlignObjs::Run(const char** files)
532 {
533   // 
534   // Run the task.
535   // 
536   //  
537
538   AliFMDGeometry* geom = AliFMDGeometry::Instance();
539   geom->Init();
540   geom->InitTransformations();
541
542   const char** file = files; 
543   while (*file) { 
544     if ((*file)[0] == '\0') { 
545       Warning("Run", "no file specified");
546       file++;
547       continue;
548     }
549     if (!LoadSurveyFromLocalFile(*file)) { 
550       Warning("Run", "Failed to load %s", *file);
551       file++;
552       continue;
553     }
554     TString sDet(fSurveyObj->GetDetector());
555     Int_t   d    = Int_t(sDet[sDet.Length()-1] - '0');
556     Info("Run", "Making alignment for %s (%d)", sDet.Data(), d);
557     Bool_t ret = true;
558     switch (d) { 
559     case 1: ret = DoFMD1(); break;
560     case 2: ret = DoFMD2(); break;
561     default: 
562       Warning("Run", "Do not know how to deal with %s", sDet.Data());
563       break;
564     }
565     if (!ret) { 
566       Warning("Run", "Calculation for %s failed", sDet.Data());
567     }
568     file++;
569   }
570   CreateAlignObjs();
571   FillDefaultAlignObjs();
572 }
573
574 //____________________________________________________________________
575 AliAlignObjParams*
576 AliFMDSurveyToAlignObjs::CreateDefaultAlignObj(const TString& path, 
577                                                Int_t id)
578 {
579   Int_t nAlign = fAlignObjArray->GetEntries();
580   AliAlignObjParams* obj = 
581     new ((*fAlignObjArray)[nAlign]) AliAlignObjParams(path.Data(),
582                                                       id,0,0,0,0,0,0,kTRUE);
583   if (!obj) {
584     AliError(Form("Failed to create alignment object for %s", path.Data()));
585     return 0;
586   }
587   if (!obj->SetLocalPars(0, 0, 0, 0, 0, 0)) {
588     AliError(Form("Failed to set local transforms on %s", path.Data()));
589     return obj;
590   }
591   return obj;
592 }
593
594 //____________________________________________________________________
595 AliAlignObjParams*
596 AliFMDSurveyToAlignObjs::FindAlignObj(const TString& path) const 
597 {
598   AliAlignObjParams* p = 0;
599   for (int i = 0; i < fAlignObjArray->GetEntries(); i++) { 
600     p = static_cast<AliAlignObjParams*>(fAlignObjArray->At(i));
601     if (path.EqualTo(p->GetSymName())) return p;
602   }
603   return 0;
604 }
605
606 //____________________________________________________________________
607 Bool_t 
608 AliFMDSurveyToAlignObjs::FillDefaultAlignObjs()
609 {
610   for (int d = 1; d <= 3; d++) { 
611     const char sides[] = { 'T', 'B', 0 };
612     const char* side   = sides;
613     while (*side) { 
614       TString path = TString::Format("FMD/FMD%d_%c", d, *side);
615       AliAlignObjParams* p = FindAlignObj(path);
616       if (!p) 
617         p = CreateDefaultAlignObj(path, 0);
618       else 
619         Info("FillDefaultAlignObjs", "Alignment object %s exists", path.Data());
620       const char halves[] = { 'I', d == 1 ? '\0' : 'O', 0 };
621       const char*  half = halves;
622       while (*half) { 
623         int nsec  = *half == 'I' ? 10 : 20;
624         int start = *side == 'T' ? 0      : nsec/2;
625         int end   = *side == 'T' ? nsec/2 : nsec;
626         for (int s=start; s < end; s++) {
627           path = TString::Format("FMD/FMD%d_%c/FMD%c_%02d", 
628                                  d, *side, *half, s);
629           CreateDefaultAlignObj(path, 0);
630         }
631         half++;
632       }
633       side++;
634     }
635     
636   }
637   return true;
638 }
639
640 //____________________________________________________________________
641 Bool_t 
642 AliFMDSurveyToAlignObjs::CreateAlignObjs()
643 {
644   // 
645   // 
646   // Method to create the alignment objects
647   // 
648   // Return:
649   //    @c true on success, @c false otherwise
650   //  
651   TClonesArray& array = *fAlignObjArray;
652   Int_t         n     = array.GetEntriesFast();
653
654   if (!fFMD1Delta.IsIdentity()) { 
655     new (array[n++]) AliAlignObjParams("FMD/FMD1_T", 0, fFMD1Delta, kTRUE);
656     new (array[n++]) AliAlignObjParams("FMD/FMD1_B", 0, fFMD1Delta, kTRUE);
657   }
658   if (!fFMD2Delta.IsIdentity()) { 
659     new (array[n++]) AliAlignObjParams("FMD/FMD2_T", 0, fFMD2Delta, kTRUE);
660     new (array[n++]) AliAlignObjParams("FMD/FMD2_B", 0, fFMD2Delta, kTRUE);
661   }
662   // array.Print();
663   
664   return kTRUE;
665 }
666
667 //____________________________________________________________________
668 void 
669 AliFMDSurveyToAlignObjs::PrintVector(const char* text, const TVector3& v)
670 {
671   // 
672   // Service member function to print a vector
673   // 
674   // Parameters:
675   //    text Prefix text
676   //    v    Vector
677   //
678   Double_t va[] = { v.X(), v.Y(), v.Z() };
679   PrintVector(text, va);
680 }
681 //____________________________________________________________________
682 void 
683 AliFMDSurveyToAlignObjs::PrintVector(const char* text, const Double_t* v)
684 {
685   // 
686   // Service member function to print a vector
687   // 
688   // Parameters:
689   //    text Prefix text
690   //    v    Vector (array of 3 doubles)
691   //
692   std::cout << text 
693             << std::setw(15) << v[0] 
694             << std::setw(15) << v[1]
695             << std::setw(15) << v[2]
696             << std::endl;
697 }
698
699
700 //____________________________________________________________________
701 void 
702 AliFMDSurveyToAlignObjs::PrintRotation(const char* text, const Double_t* rot)
703 {
704   // 
705   // Service member function to print a rotation matrix
706   // 
707   // Parameters:
708   //    text Prefix text
709   //    v    Matrix (array of 9 doubles)
710   //
711
712   std::cout << text << std::endl;
713   for (size_t i = 0; i < 3; i++) { 
714     for (size_t j = 0; j < 3; j++) 
715       std::cout << std::setw(15) << rot[i * 3 + j];
716     std::cout << std::endl;
717   }
718 }
719
720 //____________________________________________________________________
721 //
722 // EOF
723 //