All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
TrajectoryUtils.h
Go to the documentation of this file.
1 /**
2  * @file icaruscode/Utilities/TrajectoryUtils.h
3  * @brief Algorithms dealing with a trajectory as a sequence of 3D points.
4  * @author Gianluca Petrillo (petrillo@slac.stanford.edu)
5  * @date July 26, 2022
6  *
7  * This is a header-only library.
8  *
9  * @todo Move this file into `icarusalg/Utilities`.
10  */
11 
12 #ifndef ICARUSALG_UTILITIES_TRAJECTORYUTILS_H
13 #define ICARUSALG_UTILITIES_TRAJECTORYUTILS_H
14 
15 
16 // LArSoft libraries
17 #include "larcorealg/Geometry/geo_vectors_utils.h" // geo::vect::norm()
18 
19 // C/C++ standard libraries
20 #include <vector>
21 #include <utility> // std::pair
22 #include <iterator> // std::next()
23 
24 
25 namespace util {
26 
27  // ---------------------------------------------------------------------------
28  /// Data returned by `findMiddlePointInPath()`.
29  template <typename Iter>
30  struct PathPointInfo_t {
31  double length { -1 }; ///< Path length; negative means not available.
32  double step { -1 }; ///< Distance from the detected point to the next one.
33  double frac { 0.5 }; ///< The point is `frac*step` away from the point.
34  Iter itPoint; ///< Iterator to the path point just before the required one.
35  }; // PathPointInfo_t
36 
37  /**
38  * @brief Returns information to identify the middle of the specified path.
39  * @tparam FIter type of iterator to a point in the path
40  * @tparam LIter type of iterator to a point in the path
41  * @param itFirst iterator to the first point in the path
42  * @param itLast iterator to the last point in the path (must be valid point)
43  * @param relTarget (default: `0.5`) fraction of the total length to pursue
44  * @return information to identify the desired point
45  *
46  * The sequence between the iterators `itFirst` and `itLast`, both included,
47  * defines a path. This function returns the point that cuts that path into
48  * two subpaths of the same length. The point usually does not match any of
49  * the points in the sequence, but rather is in between two of them.
50  *
51  * The path is considered a sequence of straight segments connecting the
52  * points.
53  *
54  * Example of use with a LArSoft `recob::Track` object (`track`):
55  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
56  * auto const& path = track.Trajectory().Positions();
57  * geo::Point_t const middle
58  * = util::PathMiddlePoint(path.begin(), std::prev(path.end()));
59  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
60  * (note that here the function actually returns a
61  * `recob::tracking::Point_t`).
62  *
63  *
64  * Requirements
65  * -------------
66  *
67  * The `FIter` type is a forward iterator, the type `LIter` is a backward
68  * iterator.
69  * * `FIter` must support prefix `operator++`, `LIter` must support prefix
70  * `operator--()`.
71  * * `FIter` and `LIter` must compare equal (`operator == (FIter, LIter)`)
72  * if they point to the same element of the sequence.
73  * * The two iterators must point to the same type of point (called `Point`
74  * in the following text).
75  *
76  * The type `Point` must satisfy the following operations.
77  * * `Vector operator- (Point, Point)` returns an object describing the
78  * displacement to go from the second point to the first one.
79  * * `geo::vect::norm(Vector)` is a function returning the magnitude of the
80  * specified displacement vector.
81  *
82  * LArSoft `geo::Point_t` type is known to satisfy the requirements of
83  * `Point` (`geo::Vector_t` being its corresponding `Vector` type). The
84  * modulus function `geo::vect::norm()` is provided for `geo::Vector_t` in the
85  * LArSoft header `larcorealg/CoreUtils/geo_vector_utils.h`.
86  *
87  */
88  template <typename FIter, typename LIter>
90  (FIter itFirst, LIter itLast, double relTarget = 0.5);
91 
92 
93  // ---------------------------------------------------------------------------
94  /**
95  * @brief Returns the geometric point in the middle of the specified path.
96  * @tparam FIter type of iterator to a point in the path
97  * @tparam LIter type of iterator to a point in the path
98  * @param itFirst iterator to the first point in the path
99  * @param itLast iterator to the last point in the path (must be valid point)
100  * @param relTarget (default: `0.5`) fraction of the total length to pursue
101  * @return the geometric middle point of the path
102  *
103  * The sequence between the iterators `itFirst` and `itLast`, both included,
104  * defines a path. This function returns the point that cuts that path into
105  * two subpaths, the first of which has `relTarget` fraction of the length of
106  * the total path (hence, with the default `relTarget` value of `0.5` the two
107  * subpaths have the same length). The point usually does not match any of
108  * the points in the sequence, but rather is in between two of them.
109  *
110  * The path is considered a sequence of straight segments connecting the
111  * points.
112  *
113  * Example of use with a LArSoft `recob::Track` object (`track`):
114  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
115  * auto const& path = track.Trajectory().Positions();
116  * geo::Point_t const middle
117  * = util::PathMiddlePoint(path.begin(), std::prev(path.end()));
118  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
119  * (note that here the function actually returns a
120  * `recob::tracking::Point_t`).
121  *
122  *
123  * Requirements
124  * -------------
125  *
126  * The `FIter` type is a forward iterator, the type `LIter` is a backward
127  * iterator.
128  * * `FIter` must support prefix `operator++`, `LIter` must support prefix
129  * `operator--()`.
130  * * `FIter` and `LIter` must compare equal (`operator == (FIter, LIter)`)
131  * if they point to the same element of the sequence.
132  * * The two iterators must point to the same type of point (called `Point`
133  * in the following text).
134  *
135  * The type `Point` must satisfy the following operations.
136  * * `Vector operator- (Point, Point)` returns an object describing the
137  * displacement to go from the second point to the first one.
138  * * `Point operator+ (Point, Vector)` returns the point after being
139  * displaced by the specified vector.
140  * * `Vector operator* (double)` returns a displacement vector with its
141  * magnitude rescaled by the specified factor.
142  * * `geo::vect::norm(Vector)` is a function returning the magnitude of the
143  * specified displacement vector.
144  *
145  * The returned object is an instance of `Point`.
146  *
147  * LArSoft `geo::Point_t` type is known to satisfy the requirements of
148  * `Point` (`geo::Vector_t` being its corresponding `Vector` type). The
149  * modulus function `geo::vect::norm()` is provided for `geo::Vector_t` in the
150  * LArSoft header `larcorealg/CoreUtils/geo_vector_utils.h`.
151  *
152  */
153  template <typename FIter, typename LIter>
154  auto pathMiddlePoint(FIter itFirst, LIter itLast, double relTarget = 0.5);
155 
156 
157  // ---------------------------------------------------------------------------
158  /**
159  * @brief Returns the total length of the specified path.
160  * @tparam BIter type of iterator to the start of the trajectory
161  * @tparam EIter type of iterator to the end of the trajectory
162  * @param begin iterator to the first point of the trajectory
163  * @param end iterator past the last point of the trajectory
164  * @return the length of the path
165  * @see `partialPathLengths()`
166  *
167  * The sequence of partial lengths is returned, with one entry per point.
168  * The length is the sum of the distances between adjacent points of the
169  * sequence.
170  * Empty sequences and sequences of a single points have length `0`.
171  *
172  * The length are in the same units as the input point coordinates.
173  *
174  *
175  * Requirements
176  * -------------
177  *
178  * The `BIter` type is a forward iterator.
179  * * `BIter` must support prefix `operator++`
180  * * `operator != (BIter, EIter)` must return whether the first iterator
181  * is equivalent to the second (or rather, whether the sequence is over).
182  *
183  * The type `Point` must satisfy the following operations.
184  * * `Vector operator- (Point, Point)` returns an object describing the
185  * displacement to go from the second point to the first one.
186  * * `geo::vect::norm(Vector)` is a function returning the magnitude of the
187  * specified displacement vector.
188  */
189  template <typename BIter, typename EIter>
190  double pathLength(BIter begin, EIter end);
191 
192 
193  // ---------------------------------------------------------------------------
194  /**
195  * @brief Returns a sequences of partial lengths for the specified path.
196  * @tparam BIter type of iterator to the start of the trajectory
197  * @tparam EIter type of iterator to the end of the trajectory
198  * @param begin iterator to the first point of the trajectory
199  * @param end iterator past the last point of the trajectory
200  * @return a sequence of partial lengths, one per point
201  * @see `pathLength()`
202  *
203  * The sequence of partial lengths is returned, with one entry per point.
204  * A partial length up to the point _i_ is the sum of the distances between
205  * adjacent points starting from the first point (`*begin`) to the _i_-th,
206  * included.
207  * The first entry of the sequence is always `0`.
208  *
209  * The length are in the same units as the input point coordinates.
210  *
211  *
212  * Requirements
213  * -------------
214  *
215  * The `BIter` type is a forward iterator.
216  * * `BIter` must support prefix `operator++`
217  * * `operator != (BIter, EIter)` must return whether the first iterator
218  * is equivalent to the second (or rather, whether the sequence is over).
219  *
220  * The type `Point` must satisfy the following operations.
221  * * `Vector operator- (Point, Point)` returns an object describing the
222  * displacement to go from the second point to the first one.
223  * * `geo::vect::norm(Vector)` is a function returning the magnitude of the
224  * specified displacement vector.
225  */
226  template <typename BIter, typename EIter>
227  std::vector<double> partialPathLengths(BIter begin, EIter end);
228 
229 
230  // ---------------------------------------------------------------------------
231  /**
232  * @brief Returns a path segment with ends on different sides than path ends.
233  * @tparam Iter type of iterator to the points in the path
234  * @tparam SideFunc type of functor returning the side of a point
235  * @param begin iterator to the first point of the path
236  * @param end iterator past the last point of the path
237  * @param sideOf functor returning the side of the point in argument
238  * @return a pair of iterators pointing to the points found, `{ end, end }`
239  * if the path does not change side at all
240  *
241  * The path is defined as a sequence of positions of arbitrary types.
242  * The functor `sideOf` takes as argument one such position, and returns an
243  * also arbitrary value representing the side of that position.
244  * This algorithm returns a pair with the first point where the side changes,
245  * and the point after the last side change.
246  * The two points match only if there is no side change at all (in which case
247  * a pair with two `end` iterators is returned). The two returned iterators
248  * are adjacent only if there is only one change of side.
249  *
250  * Note that this algorithm is effectively not specific to a space path but
251  * it's rather a generic classification algorithm: given an ordered sequence,
252  * it finds the first and the last change of class.
253  *
254  *
255  * Requirements
256  * -------------
257  *
258  * * `Iter` must be a bidirectional operator (ask if this is too restrictive)
259  * * `SideFunc` must support a call like.
260  * `Side SideFunc::operator() (Iter::value_type) const`, i.e. must accept
261  * as argument the position pointed by an iterator of type `Iter` and return
262  * an arbitrary `Side` value.
263  * * `Side` type (returned by `sideOf`) must support
264  * `operator!= (Side, Side)`.
265  *
266  */
267  template <typename Iter, typename SideFunc>
268  std::pair<Iter, Iter> findCrossingSegment
269  (Iter begin, Iter end, SideFunc sideOf);
270 
271  // ---------------------------------------------------------------------------
272 
273 } // namespace util
274 
275 
276 // -----------------------------------------------------------------------------
277 // --- template implementation
278 // -----------------------------------------------------------------------------
279 template <typename FIter, typename LIter>
281  (FIter itFirst, LIter itLast, double relTarget /* = 0.5 */)
282 {
283 
284  // We don't check that `LIter` also points to `Point_t`.
285  // The return value will be `Point_t` anyway.
286 
287  // TODO if this algorithm works as of now, replace SegmentInfo_t with a simple double
288  struct SegmentInfo_t {
289  double length = 0.0;
290 
291  bool operator< (SegmentInfo_t const& other) const
292  { return length < other.length; }
293 
294  };
295 
296  // single point: return that point
297  if (itFirst == itLast) {
298  return PathPointInfo_t<FIter>{
299  0.0 // length
300  , 0.0 // step
301  , 0.0 // frac
302  , itFirst //itPoint
303  };
304  }
305 
306  SegmentInfo_t startChunk, endChunk;
307  FIter itStart = itFirst, itPrevStart = itStart;
308  LIter itEnd = itLast, itPrevEnd = itEnd;
309 
310  do {
311  // advance the shortest of the chunks
312  if (startChunk < endChunk) { // advance start
313  itPrevStart = itStart;
314  if (++itStart == itEnd) {
315  // note that in this case start chunk is already shorter so itPrevStart
316  // will not be needed later (at most, the last end step will be undone)
317  itStart = itPrevStart; // we went too far: restore to the previous point
318  break;
319  }
320  startChunk.length += geo::vect::norm(*itStart - *itPrevStart);
321  }
322  else { // advance end
323  itPrevEnd = itEnd;
324  if (itStart == --itEnd) {
325  // note that in this case end chunk is already shorter so itPrevEnd
326  // will not be needed later (at most the last start step will be undone)
327  itEnd = itPrevEnd; // we went too far: restore to the previous point
328  break;
329  }
330  endChunk.length += geo::vect::norm(*itPrevEnd - *itEnd);
331  }
332  assert(!(itStart == itEnd));
333  } while(true);
334 
335  // now we have the two points at the sides of a segment,
336  // and the amount of path behind each;
337  // it may still be that the last step of one of the two ends jumped over the
338  // middle point; in that case we move back
339 
340  assert(!(itStart == itEnd));
341 
342  auto delta = *itEnd - *itStart;
343  double deltaLength = geo::vect::norm(delta);
344 
345  double const pathLength = startChunk.length + deltaLength + endChunk.length;
346 
347  double const targetLength = pathLength * relTarget;
348 
349  if (startChunk.length > targetLength) {
350  // give the last step back to end...
351  --itEnd;
352  endChunk.length += deltaLength;
353  assert(itStart == itEnd);
354  // ... and take it away from start
355  itStart = itPrevStart;
356  delta = *itEnd - *itStart;
357  deltaLength = geo::vect::norm(delta);
358  startChunk.length -= deltaLength;
359  }
360  else if (endChunk.length > targetLength) {
361  // give one step back to start...
362  ++itStart;
363  startChunk.length += deltaLength;
364  assert(itStart == itEnd);
365  // ... and take it away from end
366  itEnd = itPrevEnd;
367  delta = *itEnd - *itStart;
368  deltaLength = geo::vect::norm(delta);
369  endChunk.length -= deltaLength;
370  }
371 
372  // now we have the two points at the side of the middle point
373  assert(!(itStart == itEnd));
374  assert(startChunk.length <= targetLength);
375  assert(startChunk.length + deltaLength >= targetLength);
376  assert(endChunk.length <= pathLength - targetLength);
377  assert(endChunk.length + deltaLength >= pathLength - targetLength);
378 
379  // f is the relative position of the middlepoint between itStart and itEnd:
380  // f=0 means itStart, f=1 means itEnd, f=0.5 means exact middle point
381  double const f = (deltaLength != 0.0)
382  ? ((targetLength - startChunk.length) / deltaLength): 0.0;
383  assert(f >= 0.0);
384  assert(f <= 1.0);
385 
386  return PathPointInfo_t<FIter>{
387  pathLength // length
388  , deltaLength // step
389  , f // frac
390  , itStart //itPoint
391  };
392 
393 } // util::findMiddlePointInPath()
394 
395 
396 // -----------------------------------------------------------------------------
397 template <typename FIter, typename LIter>
399  (FIter itFirst, LIter itLast, double relTarget /* = 0.5 */)
400 {
401 
402  // We don't check that `LIter` also points to `Point_t`.
403  // The return value will be `Point_t` anyway.
404 
405  PathPointInfo_t<FIter> const targetInfo
406  = findMiddlePointInPath(itFirst, itLast, relTarget);
407 
408  // "next" point might be not there (e.g. if length is 0):
409  // let's not use it if possible
410  if (targetInfo.frac == 0.0) return *(targetInfo.itPoint);
411 
412  auto const delta = *std::next(targetInfo.itPoint) - *(targetInfo.itPoint);
413  return *(targetInfo.itPoint) + delta * targetInfo.frac;
414 
415 } // util::pathMiddlePoint()
416 
417 
418 // ---------------------------------------------------------------------------
419 template <typename BIter, typename EIter>
420 double util::pathLength(BIter begin, EIter end) {
421 
422  if (!(begin != end)) return 0.0;
423 
424  double totalLength = 0.0;
425  BIter it = begin, prev = begin;
426  while (++it != end) {
427  totalLength += geo::vect::norm(*it - *prev);
428  prev = it;
429  }
430  return totalLength;
431 
432 } // util::pathLength()
433 
434 
435 // ---------------------------------------------------------------------------
436 template <typename BIter, typename EIter>
437 std::vector<double> util::partialPathLengths(BIter begin, EIter end) {
438  if (!(begin != end)) return {};
439  std::vector<double> lengths;
440  lengths.reserve(std::distance(begin, end));
441  double totalLength = 0.0;
442  BIter it = begin, prev = begin;
443  do {
444  totalLength += geo::vect::norm(*it - *prev);
445  lengths.push_back(totalLength);
446  prev = it;
447  } while (++it != end);
448  return lengths;
449 } // util::partialPathLengths()
450 
451 
452 // -----------------------------------------------------------------------------
453 template <typename Iter, typename SideFunc>
454 std::pair<Iter, Iter> util::findCrossingSegment
455  (Iter begin, Iter end, SideFunc sideOf)
456 {
457 
458  // find the place from start where the side first changes
459  Iter itStart = begin;
460  auto const startSide = sideOf(*itStart);
461  while (++itStart != end) {
462  if (sideOf(*itStart) != startSide) break; // changed side
463  }
464 
465  if (itStart == end) return { end, end }; // path is not crossing side at all
466  --itStart; // let have itStart point to the last point on the original side
467 
468  // find the place from end where the side last changes
469  // (if it starts from the same side as the other end, it's weird but we
470  // pretend nothing happened)
471  Iter itEnd = std::prev(end);
472  auto const endSide = sideOf(*itEnd);
473  while (--itEnd != itStart) {
474  if (sideOf(*itEnd) != endSide) break; // changed side
475  }
476  ++itEnd; // ... and itEnd points to the last point on the end original side
477 
478  return { itStart, itEnd };
479 
480 } // util::findCrossingSegment()
481 
482 
483 // -----------------------------------------------------------------------------
484 
485 
486 #endif // ICARUSALG_UTILITIES_TRAJECTORYUTILS_H
std::vector< double > partialPathLengths(BIter begin, EIter end)
Returns a sequences of partial lengths for the specified path.
double step
Distance from the detected point to the next one.
constexpr bool operator<(CryostatID const &a, CryostatID const &b)
Order cryostats with increasing ID.
Definition: geo_types.h:706
Iter itPoint
Iterator to the path point just before the required one.
decltype(auto) constexpr end(T &&obj)
ADL-aware version of std::end.
Definition: StdUtils.h:77
double distance(geo::Point_t const &point, CathodeDesc_t const &cathode)
Returns the distance of a point from the cathode.
auto end(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:585
Utilities to extend the interface of geometry vectors.
double length
Path length; negative means not available.
PathPointInfo_t< FIter > findMiddlePointInPath(FIter itFirst, LIter itLast, double relTarget=0.5)
Returns information to identify the middle of the specified path.
double pathLength(BIter begin, EIter end)
Returns the total length of the specified path.
auto norm(Vector const &v)
Return norm of the specified vector.
auto begin(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:573
double frac
The point is frac*step away from the point.
auto pathMiddlePoint(FIter itFirst, LIter itLast, double relTarget=0.5)
Returns the geometric point in the middle of the specified path.
decltype(auto) constexpr begin(T &&obj)
ADL-aware version of std::begin.
Definition: StdUtils.h:72
std::pair< Iter, Iter > findCrossingSegment(Iter begin, Iter end, SideFunc sideOf)
Returns a path segment with ends on different sides than path ends.
Data returned by findMiddlePointInPath().