LArSoft  v10_04_05
Liquid Argon Software toolkit - https://larsoft.org/
APAGeometryAlg.cxx
Go to the documentation of this file.
1 //
3 // \fileAPAGeometryAlg.cxx
4 //
5 // tylerdalion@gmail.com
6 //
7 // Geometry interface to smooth over the awkwardness of fitting an
8 // APA into LArSoft. It is geared towards making reconstruction simpler
9 //
11 
12 //Framework includes:
14 
23 
24 #include <algorithm>
25 #include <cmath>
26 #include <cstdlib>
27 #include <iostream>
28 
29 namespace apa {
30 
31  //----------------------------------------------------------
33  : fWireReadoutGeom{&art::ServiceHandle<geo::WireReadout const>()->Get()}
34  {
35  // find the number of channels per APA
36  uint32_t channel = 0;
37 
39  for (channel = 0; channel < fWireReadoutGeom->Nchannels(); ++channel) {
40  if (fWireReadoutGeom->ChannelToWire(channel)[0].TPC > 1) {
41  fChannelsPerAPA = channel;
42  break;
43  }
44  }
45 
46  // Step through channel c and find the view boundaries, until
47  // outside of first APA - these help optimize ChannelToAPAView
48  // (very dependent on the conventions implimented in the channel map)
49  fFirstU = 0;
50  uint32_t c = 1;
52  geo::WireID lastwid;
53  while (wid.TPC < 2) {
54  if (fWireReadoutGeom->View(c) == geo::kV && fWireReadoutGeom->View(c - 1) == geo::kU) {
55  fLastU = c - 1;
56  fFirstV = c;
57  }
58 
59  if (fWireReadoutGeom->View(c) == geo::kZ && fWireReadoutGeom->View(c - 1) == geo::kV) {
60  fLastV = c - 1;
61  fFirstZ0 = c;
62  }
63 
64  if (wid.TPC == lastwid.TPC + 1) {
65  fLastZ0 = c - 1;
66  fFirstZ1 = c;
67  }
68 
69  lastwid = wid;
70  c++;
71  if (c >= fWireReadoutGeom->Nchannels()) break;
72  wid = fWireReadoutGeom->ChannelToWire(c)[0]; // for the while condition
73  }
74 
75  fLastZ1 = c - 1;
76 
77  if (fLastZ1 + 1 != fChannelsPerAPA)
78  throw cet::exception("APAGeometryAlg") << "Channel boundaries are inconsistent.\n";
79 
80  // some other things that will be needed
81  fAPAsPerCryo = fGeom->NTPC() / 2;
82  constexpr geo::TPCID tpcid{0, 0};
83  fChannelRange[0] =
84  (fLastU - fFirstU + 1) * fWireReadoutGeom->Plane({tpcid, geo::kU}).WirePitch();
85  fChannelRange[1] =
86  (fLastV - fFirstV + 1) * fWireReadoutGeom->Plane({tpcid, geo::kV}).WirePitch();
87  }
88 
89  //----------------------------------------------------------
90  void APAGeometryAlg::ChannelToAPA(uint32_t chan, unsigned int& apa, unsigned int& cryo) const
91  {
92  cryo = chan / (fAPAsPerCryo * fChannelsPerAPA);
93 
94  // Number apa uniquely across cryostats so that
95  // apa to recob::Object maps are easy to work with.
96  // If we decide to reset APA number per cryo, uncomment:
97  //chan -= cryo*fAPAsPerCryo*fChannelsPerAPA;
98  apa = chan / fChannelsPerAPA;
99  }
100 
101  //----------------------------------------------------------
102  unsigned int APAGeometryAlg::ChannelToAPA(uint32_t chan) const
103  {
104  return chan / fChannelsPerAPA;
105  }
106 
107  //----------------------------------------------------------
108  unsigned int APAGeometryAlg::ChannelsInView(geo::View_t geoview) const
109  {
110  switch (geoview) {
111  default: return 0;
112  case geo::kU: return ChannelsInAPAView(kU);
113  case geo::kV: return ChannelsInAPAView(kV);
114  case geo::kZ:
116  return ChannelsInAPAView(kZ0);
117  else
118  throw cet::exception("ChannelsInView")
119  << "Both Z sides should have the same amount of channels\n";
120  }
121  }
122 
123  //----------------------------------------------------------
124  unsigned int APAGeometryAlg::ChannelsInAPAView(APAView_t apaview) const
125  {
126  switch (apaview) {
127  default: return 0;
128  case kU: return fFirstV - fFirstU;
129  case kV: return fFirstZ0 - fFirstV;
130  case kZ0: return fFirstZ1 - fFirstZ0;
131  case kZ1: return fLastZ1 - fFirstZ1 + 1;
132  }
133  }
134 
135  //----------------------------------------------------------
137  unsigned int apa,
138  unsigned int cryo) const
139  {
140  switch (geoview) {
141  default: return 0 + (uint32_t)(apa + cryo * fAPAsPerCryo) * fChannelsPerAPA;
142  case geo::kU: return fFirstU + (uint32_t)(apa + cryo * fAPAsPerCryo) * fChannelsPerAPA;
143  case geo::kV: return fFirstV + (uint32_t)(apa + cryo * fAPAsPerCryo) * fChannelsPerAPA;
144  case geo::kZ:
145  //TODO: would need tpc number for the rest of this
146  return fFirstZ0 + (uint32_t)(apa + cryo * fAPAsPerCryo) * fChannelsPerAPA;
147  }
148  }
149 
150  //----------------------------------------------------------
151  uint32_t APAGeometryAlg::FirstChannelInView(uint32_t chan) const
152  {
153  geo::View_t geoview = fWireReadoutGeom->View(chan);
154  unsigned int apa, cryo;
155  ChannelToAPA(chan, apa, cryo);
156  return FirstChannelInView(geoview, chan);
157  }
158 
159  //----------------------------------------------------------
160  uint32_t APAGeometryAlg::FirstChannelInView(geo::View_t geoview, uint32_t chan) const
161  {
162  unsigned int apa, cryo;
163  ChannelToAPA(chan, apa, cryo);
164  return FirstChannelInView(geoview, apa, cryo);
165  }
166 
167  //----------------------------------------------------------
168  APAView_t APAGeometryAlg::APAView(uint32_t chan) const
169  {
170  // it seems trivial to do this for U and V, but this gives a side to
171  // geo::kZ, unlike Geometry::View(c), as is often needed in disambiguation
172 
173  geo::View_t view = fWireReadoutGeom->View(chan);
174  switch (view) {
175  default: break;
176  case geo::kU: return kU;
177  case geo::kV: return kV;
178  case geo::kZ:
179  unsigned int modchan = chan % fChannelsPerAPA;
180  // Channel mapping number in the order of U, V, Z0, then Z1
181  if (modchan > fLastV && modchan < fFirstZ1) return kZ0;
182  if (modchan > fLastZ0) return kZ1;
183  }
184 
185  return kUnknown;
186  }
187 
188  //----------------------------------------------------------
189  std::vector<geo::WireID> APAGeometryAlg::ChanSegsPerSide(uint32_t chan, unsigned int side) const
190  {
191  std::vector<geo::WireID> wids = fWireReadoutGeom->ChannelToWire(chan);
192  return ChanSegsPerSide(wids, side);
193  }
194 
195  //----------------------------------------------------------
196  std::vector<geo::WireID> APAGeometryAlg::ChanSegsPerSide(std::vector<geo::WireID> wids,
197  unsigned int side) const
198  {
199  // Given a vector of wireIDs and an APA side, return
200  // the wireIDs the the tpc side where tpc%2 = side
201 
202  std::vector<geo::WireID> thisSide;
203 
204  for (size_t i = 0; i < wids.size(); i++)
205  if (wids[i].TPC % 2 == side) thisSide.push_back(wids[i]);
206 
207  return thisSide;
208  }
209 
210  //----------------------------------------------------------
212  uint32_t chan,
213  geo::PlaneID const& planeID) const
214  {
215  std::vector<geo::WireID> cWids = fWireReadoutGeom->ChannelToWire(chan);
216 
217  if (cWids[0].Cryostat != planeID.Cryostat)
218  throw cet::exception("APAGeometryAlg")
219  << "Channel " << chan << "not in cryostat " << planeID.Cryostat << "\n";
220 
221  if (std::floor(cWids[0].TPC / 2) != std::floor(planeID.TPC / 2))
222  throw cet::exception("APAGeometryAlg")
223  << "Channel " << chan << "not in APA " << std::floor(planeID.TPC / 2) << "\n";
224 
225  // special case for vertical wires
226  if (fWireReadoutGeom->View(chan) == geo::kZ) {
227  return fWireReadoutGeom->ChannelToWire(chan)[0];
228  }
229 
230  unsigned int xyzWire = fWireReadoutGeom->Plane(planeID).NearestWireID(worldLoc).Wire;
231 
232  // The desired wire ID will be the only channel segment within half the channel range.
233  geo::WireID wid;
234  for (size_t i = 0; i < cWids.size(); i++) {
235  if (cWids[i].TPC != planeID.TPC) continue;
236  if (std::abs((int)cWids[i].Wire - (int)xyzWire) < fChannelRange[planeID.Plane] / 2)
237  wid = cWids[i];
238  }
239 
240  return wid;
241  }
242 
243  //----------------------------------------------------------
245  geo::Point_t const& xyzEnd,
246  uint32_t chan,
247  std::vector<geo::WireID>& widsCrossed,
248  bool ExtendLine = true) const
249  {
250  // This assumes a smooth wire numbering, and that the line seg is contained in a tpc.
251  // Meant for use with the approximate line calculated by matching cluster endpoints in
252  // disambiguation.
253 
254  // Find tpc, use midpoint in case start/end is on a boundary
255  geo::Point_t const xyzMid = geo::vect::middlePoint({xyzStart, xyzEnd});
256  auto const& tpcid = fGeom->PositionToTPCID(xyzMid);
257 
258  // Find the nearest wire number to the line segment endpoints
259  std::vector<geo::WireID> wids = fWireReadoutGeom->ChannelToWire(chan);
260  geo::PlaneID const planeID{tpcid, wids[0].Plane};
261  auto const& plane = fWireReadoutGeom->Plane(planeID);
262  unsigned int startW = plane.NearestWireID(xyzStart).Wire;
263  unsigned int endW = plane.NearestWireID(xyzEnd).Wire;
264 
265  if (startW > endW) std::swap(startW, endW);
266 
267  // Loop through wireIDs and check for intersection, if in the right TPC
268  for (size_t w = 0; w < wids.size(); w++) {
269  if (wids[w].TPC != tpcid.TPC) continue;
270  if (wids[w].Cryostat != tpcid.Cryostat)
271  throw cet::exception("LineSegChanIntersect")
272  << "Channel and line not in the same crostat.\n";
273 
274  // If the current wire id wire number is inbetween the start/end
275  // point wires, the line segment intersects the wireID at some point.
276 
277  // TODO: for now, extend range, but that is application specific. fix asap
278  // The longer we make the range, the more conservative it is, so it is safe
279  // to extend the range a bit to get hits at the ends of the line
280  unsigned int ext = 0;
281  if (ExtendLine) ext = 10;
282 
283  if (lar::util::ValueInRange(wids[w].Wire * 1., (startW - ext) * 1., (endW + ext) * 1.))
284  widsCrossed.push_back(wids[w]);
285  }
286 
287  return not widsCrossed.empty();
288  }
289 
290  //----------------------------------------------------------
291  std::vector<double> APAGeometryAlg::ThreeChanPos(uint32_t u, uint32_t v, uint32_t z) const
292  {
293  // Say we've associated a U, V, and Z channel -- perhaps by associating hits
294  // or cluster endpoints -- these don't necessarily all intersect, but they
295  // are hopefully pretty close. Find the center of the 3 intersections.
296 
297  // get data needed along the way
298  std::vector<geo::WireIDIntersection> UVIntersects;
299  APAChannelsIntersect(u, v, UVIntersects);
300  std::vector<double> UVzToZ(UVIntersects.size());
302  unsigned int cryo = Zwid.Cryostat;
303  unsigned int tpc = Zwid.TPC;
304  std::vector<geo::WireID> Uwids = fWireReadoutGeom->ChannelToWire(u);
305  std::vector<geo::WireID> Vwids = fWireReadoutGeom->ChannelToWire(v);
306  std::vector<geo::WireID> UwidsInTPC, VwidsInTPC;
307  for (size_t i = 0; i < Uwids.size(); i++)
308  if (Uwids[i].TPC == tpc) UwidsInTPC.push_back(Uwids[i]);
309  for (size_t i = 0; i < Vwids.size(); i++)
310  if (Vwids[i].TPC == tpc) VwidsInTPC.push_back(Vwids[i]);
311  auto const Zcent = fWireReadoutGeom->Wire(Zwid).GetCenter();
312 
313  std::cout << "Zcent = " << Zcent.Z() << ", UVintersects zpos = ";
314  for (size_t uv = 0; uv < UVIntersects.size(); uv++) {
315  std::cout << UVIntersects[uv].z << ", ";
316  }
317  std::cout << "\n";
318 
321 
322  if (UVIntersects.empty()) {
323  if (UwidsInTPC.size() > 1 || VwidsInTPC.size() > 1)
324  throw cet::exception("ThreeChanPos") << "U/V channels don't intersect, bad return.\n";
325 
326  // Now assume there are only one of each u and v wireIDs on in this TPC
327  mf::LogWarning("ThreeChanPos")
328  << "No U/V intersect, exceptional channels. See if U or V intersects Z\n";
329  std::vector<double> yzCenter(2, 0.);
330  geo::WireID Uwid = UwidsInTPC[0];
331  geo::WireID Vwid = VwidsInTPC[0];
332  auto const UZInt = fWireReadoutGeom->WireIDsIntersect(Uwid, Zwid);
333  auto const VZInt = fWireReadoutGeom->WireIDsIntersect(Vwid, Zwid);
334  if (!UZInt && !VZInt)
335  throw cet::exception("NoChanIntersect") << "No channels intersect, bad return.\n";
336  if (UZInt && !VZInt) {
337  yzCenter[0] = UZInt->y;
338  yzCenter[1] = UZInt->z;
339  }
340  if (VZInt && !UZInt) {
341  yzCenter[0] = VZInt->y;
342  yzCenter[1] = VZInt->z;
343  }
344  if (UZInt && VZInt) {
345  yzCenter[0] = (VZInt->y + UZInt->y) / 2;
346  yzCenter[1] = (VZInt->z + UZInt->z) / 2;
347  }
348  return yzCenter;
349  }
350 
353 
354  // In case the uv channels intersect twice on the same side, choose the best case.
355  // Note: this will not happen for APAs with UV angle at about 36, but will for 45
356  std::cout << "UVzToZ = ";
357  for (size_t widI = 0; widI < UVIntersects.size(); widI++) {
358  UVzToZ[widI] = std::abs(UVIntersects[widI].z - Zcent.Z());
359  std::cout << UVzToZ[widI] << ", ";
360  }
361  std::cout << "\n";
362 
363  unsigned int bestWidI = 0;
364  double minZdiff = fGeom->TPC(Zwid).Length(); // start it out at maximum z
365  for (unsigned int widI = 0; widI < UVIntersects.size(); widI++) {
366  if (UVIntersects[widI].TPC == tpc && UVzToZ[widI] < minZdiff) {
367  minZdiff = UVzToZ[widI];
368  bestWidI = widI;
369  }
370  }
371  geo::WireIDIntersection ChosenUVInt = UVIntersects[bestWidI];
372 
373  // Now having the UV intersection, get the UZ and VZ
374  geo::Point_t const UVInt{0., ChosenUVInt.y, ChosenUVInt.z};
375  geo::WireID Uwid = NearestWireIDOnChan(UVInt, u, {cryo, tpc, 0});
376  geo::WireID Vwid = NearestWireIDOnChan(UVInt, v, {cryo, tpc, 1});
377  auto const UZInt = fWireReadoutGeom->WireIDsIntersect(Uwid, Zwid);
378  auto const VZInt = fWireReadoutGeom->WireIDsIntersect(Vwid, Zwid);
379 
380  // find the center
381  std::vector<double> yzCenter(2, 0.);
382 
383  std::cout << "UZint = " << std::boolalpha << static_cast<bool>(UZInt)
384  << ", VZint = " << static_cast<bool>(VZInt) << '\n';
385  if (!UZInt || !VZInt) {
386  std::cout << "ChosenUVint.y = " << ChosenUVInt.y << "ChosenUVint.z = " << ChosenUVInt.z
387  << std::endl;
388 
389  //temporary case
390  yzCenter[0] = ChosenUVInt.y;
391  yzCenter[1] = ChosenUVInt.z;
392  }
393  else {
394  std::cout << "UZint.z = " << UZInt->z << ", VZint.z = " << VZInt->z << '\n';
395  yzCenter[0] = (ChosenUVInt.y + UZInt->y + VZInt->y) / 3;
396  yzCenter[1] = (ChosenUVInt.z + UZInt->z + VZInt->z) / 3;
397  }
398 
399  return yzCenter;
400  }
401 
402  //----------------------------------------------------------
404  uint32_t chan1,
405  uint32_t chan2,
406  std::vector<geo::WireIDIntersection>& IntersectVector) const
407  {
408  // Get the WireIDs and view for each channel, make sure views are different
409  std::vector<geo::WireID> wids1 = fWireReadoutGeom->ChannelToWire(chan1);
410  std::vector<geo::WireID> wids2 = fWireReadoutGeom->ChannelToWire(chan2);
411  geo::View_t view1 = fWireReadoutGeom->View(chan1);
412  geo::View_t view2 = fWireReadoutGeom->View(chan2);
413  if (view1 == view2) {
414  mf::LogWarning("APAChannelsIntersect")
415  << "Comparing two channels in the same view, return false";
416  return false;
417  }
418  if (wids1[0].Cryostat != wids2[0].Cryostat || ChannelToAPA(chan1) != ChannelToAPA(chan2)) {
419  throw cet::exception("APAChannelsIntersect")
420  << "Comparing two channels in in different APAs: "
421  << "channel " << chan1 << " in Cryo " << wids1[0].Cryostat << ", APA "
422  << ChannelToAPA(chan1) << ", and channel " << chan2 << " in Cryo " << wids2[0].Cryostat
423  << ", APA " << ChannelToAPA(chan2) << "\n";
424  return false;
425  }
426 
427  // Loop through wids1 and see if wids2 has any intersecting wires,
428  // given that the WireIDs are in the same TPC
429  for (unsigned int i1 = 0; i1 < wids1.size(); i1++) {
430  for (unsigned int i2 = 0; i2 < wids2.size(); i2++) {
431 
432  // make sure it is reasonable to intersect
433  if (wids1[i1].Plane == wids2[i2].Plane ||
434  wids1[i1].TPC != wids2[i2].TPC || // not reasonable for a *WireID*
435  wids1[i1].Cryostat != wids2[i2].Cryostat)
436  continue;
437 
438  // Check if they even intersect; if they do, push back
439  if (auto widIntersect = fWireReadoutGeom->WireIDsIntersect(wids1[i1], wids2[i2])) {
440  IntersectVector.push_back(*widIntersect);
441  }
442  }
443  }
444 
445  // Of all considered configurations, there are never more than
446  // 4 intersections per channel pair
447  if (IntersectVector.size() > 4) {
448  mf::LogWarning("APAChannelsIntersect")
449  << "Got " << IntersectVector.size() << " intersections for channels " << chan1 << " and "
450  << chan2 << " - never expect more than 4, so far";
451  }
452 
453  // With increasing IntersectVector index, the WireID vector indices of the
454  // intersecting wireIDs increase. This matches the direction in which ChannelToWire
455  // builds its output WireID vector in the APA/35t Alg
456  std::sort(IntersectVector.begin(), IntersectVector.end());
457 
458  // return true if any intersection points were found
459  return not IntersectVector.empty();
460  }
461 
462 } //end namespace apa
Point_t const & GetCenter() const
Returns the world coordinate of the center of the wire [cm].
Definition: WireGeo.h:219
double z
z position of intersection
Definition: geo_types.h:584
Functions to help with numbers.
Encapsulate the construction of a single cyostat .
Z view on the larger-x side of the APA.
enum geo::_plane_proj View_t
Enumerate the possible plane projections.
WireID NearestWireID(Point_t const &pos) const
Returns the ID of wire closest to the specified position.
Definition: PlaneGeo.cxx:485
Planes which measure V.
Definition: geo_types.h:132
unsigned int NTPC(CryostatID const &cryoid=details::cryostat_zero) const
Returns the total number of TPCs in the specified cryostat.
Definition: GeometryCore.h:416
virtual unsigned int Nchannels() const =0
Returns the total number of channels present (not necessarily contiguous)
Double_t z
Definition: plot.C:276
Z view on the smaller-x side of the APA.
The data type to uniquely identify a Plane.
Definition: geo_types.h:364
unsigned int ChannelsInAPAView(APAView_t apaview) const
uint32_t FirstChannelInView(geo::View_t geoview, unsigned int apa, unsigned int cryo) const
constexpr auto abs(T v)
Returns the absolute value of the argument.
CryostatID_t Cryostat
Index of cryostat.
Definition: geo_types.h:195
Planes which measure Z direction.
Definition: geo_types.h:134
cout<< "Opened file "<< fin<< " ixs= "<< ixs<< endl;if(ixs==0) hhh=(TH1F *) fff-> Get("h1")
Definition: AddMC.C:8
WireID_t Wire
Index of the wire within its plane.
Definition: geo_types.h:430
unsigned int fAPAsPerCryo
bool APAChannelsIntersect(uint32_t chan1, uint32_t chan2, std::vector< geo::WireIDIntersection > &IntersectVector) const
If the channels intersect, get all intersections.
WireGeo const & Wire(WireID const &wireid) const
Returns the specified wire.
double Length() const
Length is associated with z coordinate [cm].
Definition: TPCGeo.h:110
APAView_t APAView(uint32_t chan) const
Get which of the 4 APA views the channel is in.
Planes which measure U.
Definition: geo_types.h:131
unsigned int ChannelToAPA(uint32_t chan) const
Get number of the APA containing the given channel.
Point_t middlePoint(BeginIter begin, EndIter end)
Returns the middle of the specified points.
bool LineSegChanIntersect(geo::Point_t const &xyzStart, geo::Point_t const &xyzEnd, uint32_t chan, std::vector< geo::WireID > &widsCrossed, bool ExtendLine) const
If a line given by start/end points intersects a channel.
unsigned int ChannelsInView(geo::View_t geoview) const
U view on both sides of the APA.
unsigned int fChannelsPerAPA
All APAs have this same number of channels.
The data type to uniquely identify a TPC.
Definition: geo_types.h:306
PlaneID_t Plane
Index of the plane within its TPC.
Definition: geo_types.h:373
Definition of data types for geometry description.
V view on both sides of the APA.
bool WireIDsIntersect(WireID const &wid1, WireID const &wid2, Point_t &intersection) const
Computes the intersection between two wires.
Encapsulate the geometry of a wire .
bool ValueInRange(double value, double min, double max)
Returns whether a value is within the specified range.
Definition: NumericUtils.cxx:7
ROOT::Math::PositionVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Point_t
Type for representation of position in physical 3D space.
Definition: geo_vectors.h:180
art::ServiceHandle< geo::Geometry const > fGeom
virtual std::vector< WireID > ChannelToWire(raw::ChannelID_t channel) const =0
View_t View(raw::ChannelID_t const channel) const
Returns the view (wire orientation) on the specified TPC channel.
geo::WireID NearestWireIDOnChan(geo::Point_t const &WorldLoc, uint32_t chan, geo::PlaneID const &planeID) const
double y
y position of intersection
Definition: geo_types.h:583
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
std::vector< geo::WireID > ChanSegsPerSide(uint32_t chan, unsigned int side) const
PlaneGeo const & Plane(TPCID const &tpcid, View_t view) const
Returns the specified wire.
TPCID_t TPC
Index of the TPC within its cryostat.
Definition: geo_types.h:315
TPCGeo const & TPC(TPCID const &tpcid=details::tpc_zero) const
Returns the specified TPC.
Definition: GeometryCore.h:448
geo::WireReadoutGeom const * fWireReadoutGeom
std::vector< double > ThreeChanPos(uint32_t u, uint32_t v, uint32_t z) const
Find the center of the 3 intersections, choose best if multiple.
recob::tracking::Plane Plane
Definition: TrackState.h:17
Float_t w
Definition: plot.C:20
TPCID PositionToTPCID(Point_t const &point) const
Returns the ID of the TPC at specified location.
Interface to geometry for wire readouts .
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
double WirePitch() const
Return the wire pitch (in centimeters). It is assumed constant.
Definition: PlaneGeo.h:312
Encapsulate the construction of a single detector plane .