LSSTApplications  17.0+11,17.0+34,17.0+56,17.0+57,17.0+59,17.0+7,17.0-1-g377950a+33,17.0.1-1-g114240f+2,17.0.1-1-g4d4fbc4+28,17.0.1-1-g55520dc+49,17.0.1-1-g5f4ed7e+52,17.0.1-1-g6dd7d69+17,17.0.1-1-g8de6c91+11,17.0.1-1-gb9095d2+7,17.0.1-1-ge9fec5e+5,17.0.1-1-gf4e0155+55,17.0.1-1-gfc65f5f+50,17.0.1-1-gfc6fb1f+20,17.0.1-10-g87f9f3f+1,17.0.1-11-ge9de802+16,17.0.1-16-ga14f7d5c+4,17.0.1-17-gc79d625+1,17.0.1-17-gdae4c4a+8,17.0.1-2-g26618f5+29,17.0.1-2-g54f2ebc+9,17.0.1-2-gf403422+1,17.0.1-20-g2ca2f74+6,17.0.1-23-gf3eadeb7+1,17.0.1-3-g7e86b59+39,17.0.1-3-gb5ca14a,17.0.1-3-gd08d533+40,17.0.1-30-g596af8797,17.0.1-4-g59d126d+4,17.0.1-4-gc69c472+5,17.0.1-6-g5afd9b9+4,17.0.1-7-g35889ee+1,17.0.1-7-gc7c8782+18,17.0.1-9-gc4bbfb2+3,w.2019.22
LSSTDataManagementBasePackage
TransformMap.cc
Go to the documentation of this file.
1 /*
2  * LSST Data Management System
3  * Copyright 2014 LSST Corporation.
4  *
5  * This product includes software developed by the
6  * LSST Project (http://www.lsst.org/).
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the LSST License Statement and
19  * the GNU General Public License along with this program. If not,
20  * see <http://www.lsstcorp.org/LegalNotices/>.
21  */
22 
23 #include <exception>
24 #include <memory>
25 #include <sstream>
26 #include <type_traits>
27 #include <set>
28 
29 #include "boost/optional.hpp"
30 
31 #include "lsst/pex/exceptions.h"
35 #include "lsst/afw/table/io/Persistable.cc"
37 
38 namespace lsst {
39 namespace afw {
40 namespace cameraGeom {
41 
43  CameraSys const & reference,
44  Transforms const & transforms
45 ) {
46  return Builder(reference).connect(transforms).build();
47 }
48 
49 lsst::afw::geom::Point2Endpoint TransformMap::_pointConverter;
50 
51 // All resources owned by value or by smart pointer
52 TransformMap::~TransformMap() noexcept = default;
53 
55  CameraSys const &toSys) const {
56  auto mapping = _getMapping(fromSys, toSys);
57  return _pointConverter.pointFromData(mapping->applyForward(_pointConverter.dataFromPoint(point)));
58 }
59 
61  CameraSys const &fromSys,
62  CameraSys const &toSys) const {
63  auto mapping = _getMapping(fromSys, toSys);
64  return _pointConverter.arrayFromData(mapping->applyForward(_pointConverter.dataFromArray(pointList)));
65 }
66 
67 bool TransformMap::contains(CameraSys const &system) const noexcept { return _frameIds.count(system) > 0; }
68 
70  CameraSys const &toSys) const {
71  return std::make_shared<geom::TransformPoint2ToPoint2>(*_getMapping(fromSys, toSys));
72 }
73 
74 int TransformMap::_getFrame(CameraSys const &system) const {
75  try {
76  return _frameIds.at(system);
77  } catch (std::out_of_range const &e) {
78  std::ostringstream buffer;
79  buffer << "Unsupported coordinate system: " << system;
81  }
82 }
83 
84 std::shared_ptr<ast::Mapping const> TransformMap::_getMapping(CameraSys const &fromSys,
85  CameraSys const &toSys) const {
86  return _transforms->getMapping(_getFrame(fromSys), _getFrame(toSys));
87 }
88 
89 size_t TransformMap::size() const noexcept { return _frameIds.size(); }
90 
92  CameraSysFrameIdMap && frameIds,
93  std::vector<std::pair<int, int>> && canonicalConnections) :
94  _transforms(std::move(transforms)),
95  _frameIds(std::move(frameIds)),
96  _canonicalConnections(std::move(canonicalConnections))
97 {}
98 
99 
100 namespace {
101 
102 struct PersistenceHelper {
103 
104  static PersistenceHelper const & get() {
105  static PersistenceHelper const instance;
106  return instance;
107  }
108 
109  // Schema and keys for the catalog that stores TransformMap._frameIds.
110  // Considered as a graph, this is a list of all of the vertices with the
111  // integers that identify them in the list of edges below.
112  table::Schema sysSchema;
113  table::Key<std::string> sysName;
114  table::Key<std::string> detectorName;
115  table::Key<int> id;
116 
117  // Schema and keys for the catalog that stores
118  // TransformMap._canonicalConnections entries and the associated Transform
119  // extracted from TransformMap._transforms.
120  // Considered as a graph, 'from' and 'to' identify vertices, and
121  // 'transform' identifies an edge.
122  table::Schema connectionSchema;
123  table::Key<int> from;
124  table::Key<int> to;
125  table::Key<int> transform;
126 
127  CameraSys makeCameraSys(table::BaseRecord const & record) const {
128  return CameraSys(record.get(sysName), record.get(detectorName));
129  }
130 
131 private:
132 
133  PersistenceHelper() :
134  sysSchema(),
135  sysName(sysSchema.addField<std::string>("sysName", "Camera coordinate system name", "", 0)),
136  detectorName(sysSchema.addField<std::string>("detectorName",
137  "Camera coordinate system detector name", "", 0)),
138  id(sysSchema.addField<int>("id", "AST ID of the Frame for the CameraSys", "")),
140  from(connectionSchema.addField<int>("from", "AST ID of the Frame this transform maps from.", "")),
141  to(connectionSchema.addField<int>("to", "AST ID of the Frame this transform maps to.", "")),
142  transform(connectionSchema.addField<int>("transform", "Archive ID of the transform.", ""))
143  {
144  sysSchema.getCitizen().markPersistent();
145  connectionSchema.getCitizen().markPersistent();
146  }
147 
148  PersistenceHelper(PersistenceHelper const &) = delete;
149  PersistenceHelper(PersistenceHelper &&) = delete;
150 
151  PersistenceHelper & operator=(PersistenceHelper const &) = delete;
152  PersistenceHelper & operator=(PersistenceHelper &&) = delete;
153 
154 };
155 
156 std::string makeFrameName(CameraSys const & sys) {
157  std::string r = "Ident=" + sys.getSysName();
158  if (sys.hasDetectorName()) {
159  r += "_";
160  r += sys.getDetectorName();
161  }
162  return r;
163 }
164 
165 } // namespace
166 
167 
168 std::string TransformMap::getPersistenceName() const {
169  return "TransformMap";
170 }
171 
172 std::string TransformMap::getPythonModule() const {
173  return "lsst.afw.cameraGeom";
174 }
175 
176 void TransformMap::write(OutputArchiveHandle& handle) const {
177  auto const & keys = PersistenceHelper::get();
178 
179  auto sysCat = handle.makeCatalog(keys.sysSchema);
180  for (auto const & sysPair : _frameIds) {
181  auto sysRecord = sysCat.addNew();
182  sysRecord->set(keys.sysName, sysPair.first.getSysName());
183  sysRecord->set(keys.detectorName, sysPair.first.getDetectorName());
184  sysRecord->set(keys.id, sysPair.second);
185  }
186  sysCat.sort(keys.id);
187  handle.saveCatalog(sysCat);
188 
189  auto connectionCat = handle.makeCatalog(keys.connectionSchema);
190  for (auto const & connectionPair : _canonicalConnections) {
191  auto connectionRecord = connectionCat.addNew();
192  connectionRecord->set(keys.from, connectionPair.first);
193  connectionRecord->set(keys.to, connectionPair.second);
195  *_transforms->getMapping(connectionPair.first, connectionPair.second));
196  connectionRecord->set(keys.transform, handle.put(transform));
197  }
198  handle.saveCatalog(connectionCat);
199 }
200 
202 public:
203 
204  Factory() : PersistableFactory("TransformMap") {}
205 
207  CatalogVector const& catalogs) const override {
208  auto const & keys = PersistenceHelper::get();
209 
210  LSST_ARCHIVE_ASSERT(catalogs.size() == 2u);
211  auto const & sysCat = catalogs[0];
212  auto const & connectionCat = catalogs[1];
213  LSST_ARCHIVE_ASSERT(sysCat.getSchema() == keys.sysSchema);
214  LSST_ARCHIVE_ASSERT(connectionCat.getSchema() == keys.connectionSchema);
215  LSST_ARCHIVE_ASSERT(sysCat.size() == connectionCat.size() + 1);
216  LSST_ARCHIVE_ASSERT(sysCat.isSorted(keys.id));
217 
218  CameraSysFrameIdMap frameIdsBySys;
220  for (auto const & sysRecord : sysCat) {
221  auto sys = keys.makeCameraSys(sysRecord);
222  frameIdsBySys.emplace(sys, sysRecord.get(keys.id));
223  framesById.emplace(sysRecord.get(keys.id), ast::Frame(2, makeFrameName(sys)));
224  }
225 
226  auto baseFrameIter = framesById.find(1);
227  LSST_ARCHIVE_ASSERT(baseFrameIter != framesById.end());
228  auto frameSet = std::make_unique<ast::FrameSet>(baseFrameIter->second);
229  std::vector<std::pair<int, int>> canonicalConnections;
230  for (auto const & connectionRecord : connectionCat) {
231  int const fromId = connectionRecord.get(keys.from);
232  int const toId = connectionRecord.get(keys.to);
233  auto const transform = archive.get<geom::TransformPoint2ToPoint2>(
234  connectionRecord.get(keys.transform)
235  );
236  canonicalConnections.emplace_back(fromId, toId);
237  auto toFrameIter = framesById.find(toId);
238  LSST_ARCHIVE_ASSERT(toFrameIter != framesById.end());
239  frameSet->addFrame(fromId, *transform->getMapping(), toFrameIter->second);
240  }
241 
243  std::move(frameIdsBySys),
244  std::move(canonicalConnections)));
245  }
246 
247 };
248 
249 TransformMap::Factory const TransformMap::registration;
250 
251 
252 TransformMap::Builder::Builder(CameraSys const & reference) : _reference(reference) {}
253 
254 TransformMap::Builder::Builder(Builder const &) = default;
256 
259 
260 TransformMap::Builder::~Builder() noexcept = default;
261 
263  CameraSys const & fromSys,
264  CameraSys const & toSys,
266 ) {
267  if (fromSys == toSys) {
268  std::ostringstream buffer;
269  buffer << "Identity connection detected for " << fromSys << ".";
271  }
272  if (!transform->hasForward()) {
273  std::ostringstream buffer;
274  buffer << "Connection from " << fromSys << " to "
275  << toSys << " has no forward transform.";
277  }
278  if (!transform->hasInverse()) {
279  std::ostringstream buffer;
280  buffer << "Connection from " << fromSys << " to "
281  << toSys << " has no inverse transform.";
283  }
284  _connections.push_back(Connection{false, std::move(transform), fromSys, toSys});
285  return *this;
286 }
287 
288 
290  CameraSys const & fromSys,
291  Transforms const & transforms
292 ) {
293  Builder other(_reference); // temporary for strong exception safety
294  for (auto const & item : transforms) {
295  other.connect(fromSys, item.first, item.second);
296  }
297  _connections.insert(_connections.end(), other._connections.begin(), other._connections.end());
298  return *this;
299 }
300 
301 
302 namespace {
303 
304 /*
305  * RAII object that just executes a functor in its destructor.
306  */
307 template <typename Callable>
308 class OnScopeExit {
309 public:
310 
311  explicit OnScopeExit(Callable callable) : _callable(std::move(callable)) {}
312 
313  ~OnScopeExit() noexcept(noexcept(std::declval<Callable>())) { // noexcept iff "_callable()"" is
314  _callable();
315  }
316 
317 private:
318  Callable _callable;
319 };
320 
321 // Factory function for OnScopeExit.
322 template <typename Callable>
323 OnScopeExit<Callable> onScopeExit(Callable callable) {
324  return OnScopeExit<Callable>(std::move(callable));
325 }
326 
327 } // namespace
328 
329 
331 
332  int nFrames = 0; // tracks frameSet->getNFrame() to avoid those (expensive) calls
333  CameraSysFrameIdMap frameIds; // mapping from CameraSys to Frame ID (int)
334  std::vector<std::pair<int, int>> canonicalConnections; // remembers the frame IDs we've connected
335 
336  // Local helper function that looks up the Frame ID for a CameraSys, with
337  // results returned via boost::optional.
338  auto findFrameIdForSys = [&frameIds](CameraSys const & sys) -> boost::optional<int> {
339  auto iter = frameIds.find(sys);
340  if (iter != frameIds.end()) {
341  return boost::optional<int>(iter->second);
342  } else {
343  return boost::none;
344  }
345  };
346 
347  // Local helper function that creates a Frame, updates the nFrames counter,
348  // and adds an entry to the frameIds map. Returns the new Frame.
349  // Should always be called in concert with an update to frameSet.
350  auto addFrameForSys = [&frameIds, &nFrames](CameraSys const & sys) mutable -> ast::Frame {
351  frameIds.emplace(sys, ++nFrames);
352  return ast::Frame(2, makeFrameName(sys));
353  };
354 
355  // FrameSet that manages all transforms; should always be updated in
356  // concert with a call to addFrameForSys.
357  auto frameSet = std::make_unique<ast::FrameSet>(addFrameForSys(_reference));
358 
359  // RAII: make sure all 'processed' fields are reset, no matter how we exit
360  auto guard = onScopeExit(
361  [this]() noexcept {
362  for (auto const & c : _connections) { c.processed = false; }
363  }
364  );
365 
366  std::size_t nProcessedTotal = 0;
367  while (nProcessedTotal != _connections.size()) {
368 
369  // Loop over all connections, only inserting those that are connected
370  // to already-inserted connections.
371  std::size_t nProcessedThisPass = 0;
372  for (auto const & c : _connections) {
373  if (c.processed) continue;
374  auto fromId = findFrameIdForSys(c.fromSys);
375  auto toId = findFrameIdForSys(c.toSys);
376  if (fromId && toId) { // We've already inserted both fromSys and toSys. That's a problem.
377  std::ostringstream buffer;
378  buffer << "Duplicate connection from " << c.fromSys << " to " << c.toSys << ".";
380  } else if (fromId) { // We've already inserted fromSys (only)
381  frameSet->addFrame(*fromId, *c.transform->getMapping(), addFrameForSys(c.toSys));
382  canonicalConnections.emplace_back(*fromId, nFrames);
383  c.processed = true;
384  ++nProcessedThisPass;
385  } else if (toId) { // We've already inserted toSys (only)
386  frameSet->addFrame(*toId, *c.transform->inverted()->getMapping(), addFrameForSys(c.fromSys));
387  canonicalConnections.emplace_back(*toId, nFrames);
388  c.processed = true;
389  ++nProcessedThisPass;
390  }
391  // If we haven't inserted either yet, just continue; hopefully
392  // we'll have inserted one in a future pass.
393  }
394 
395  if (nProcessedThisPass == 0u) { // We're not making progress, so we must have orphans
396  // Use std::set to compile the list of orphaned coordinate systems
397  // for a friendlier (unique, predictably-ordered) error message.
398  std::set<CameraSys> orphaned;
399  for (auto const & c : _connections) {
400  if (!c.processed) {
401  orphaned.insert(c.fromSys);
402  orphaned.insert(c.toSys);
403  }
404  }
405  std::ostringstream buffer;
406  auto o = orphaned.begin();
407  buffer << "Orphaned coordinate system(s) found: " << *o;
408  for (++o; o != orphaned.end(); ++o) {
409  buffer << ", " << *o;
410  }
411  buffer << ".";
413  }
414 
415  nProcessedTotal += nProcessedThisPass;
416  }
417 
418  // We've maintained our own counter for frame IDs for performance and
419  // convenience reasons, but it had better match AST's internal counter.
420  assert(frameSet->getNFrame() == nFrames);
421 
422  // Return the new TransformMap.
423  // We can't use make_shared because TransformMap ctor is private.
425  std::move(frameIds),
426  std::move(canonicalConnections)));
427 }
428 
429 } // namespace cameraGeom
430 
431 namespace table {
432 namespace io {
433 
434 template class PersistableFacade<cameraGeom::TransformMap>;
435 
436 } // namespace io
437 } // namespace table
438 
439 } // namespace afw
440 } // namespace lsst
Camera coordinate system; used as a key in in TransformMap.
Definition: CameraSys.h:83
table::Key< int > from
A base class for factory classes used to reconstruct objects from records.
Definition: Persistable.h:228
Point pointFromData(std::vector< double > const &data) const override
Get a single point from raw data.
Definition: Endpoint.cc:138
T end(T... args)
table::Schema connectionSchema
lsst::geom::Point2D transform(lsst::geom::Point2D const &point, CameraSys const &fromSys, CameraSys const &toSys) const
Convert a point from one camera coordinate system to another.
Definition: TransformMap.cc:54
table::Key< std::string > detectorName
Transform< Point2Endpoint, Point2Endpoint > TransformPoint2ToPoint2
Definition: Transform.h:300
STL class.
Helper class used to incrementally construct TransformMap instances.
Definition: TransformMap.h:239
T at(T... args)
Array arrayFromData(ndarray::Array< double, 2, 2 > const &data) const override
Get an array of points from raw data.
Definition: Endpoint.cc:148
TransformMap & operator=(TransformMap const &)=delete
T push_back(T... args)
std::shared_ptr< TransformMap const > build() const
Construct a TransformMap from the connections in the builder.
table::Key< int > to
A base class for image defects.
Frame is used to represent a coordinate system.
Definition: Frame.h:157
T throw_with_nested(T... args)
Builder(CameraSys const &reference)
Construct an empty builder with no transforms and only the given coordinate system.
T str(T... args)
bool contains(CameraSys const &system) const noexcept
Can this transform to and from the specified coordinate system?
Definition: TransformMap.cc:67
table::Schema sysSchema
T move(T... args)
table::Key< std::string > sysName
std::vector< double > dataFromPoint(Point const &point) const override
Definition: Endpoint.cc:115
T count(T... args)
T insert(T... args)
static std::shared_ptr< TransformMap const > make(CameraSys const &reference, Transforms const &transforms)
Construct a TransformMap with all transforms relative to a single reference CameraSys.
Definition: TransformMap.cc:42
T find(T... args)
T size(T... args)
TransformMap(TransformMap const &other)=delete
STL class.
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition: Exception.h:48
A vector of catalogs used by Persistable.
Definition: CatalogVector.h:29
STL class.
STL class.
T begin(T... args)
std::shared_ptr< Persistable > read(InputArchive const &archive, CatalogVector const &catalogs) const override
Construct a new object from the given InputArchive and vector of catalogs.
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
Definition: Persistable.h:48
size_t size() const noexcept
Get the number of supported coordinate systems.
Definition: TransformMap.cc:89
T emplace(T... args)
Builder & connect(CameraSys const &fromSys, CameraSys const &toSys, std::shared_ptr< geom::TransformPoint2ToPoint2 const > transform)
Add a new coordinate system to the builder.
Reports invalid arguments.
Definition: Runtime.h:66
io::OutputArchiveHandle OutputArchiveHandle
Definition: Persistable.h:108
An endpoint for lsst::geom::Point2D.
Definition: Endpoint.h:261
ItemVariant const * other
Definition: Schema.cc:56
A multi-catalog archive object used to load table::io::Persistable objects.
Definition: InputArchive.h:31
table::Key< int > id
std::shared_ptr< Mapping > getMapping(int from=BASE, int to=CURRENT) const
Obtain a Mapping that converts between two Frames in a FrameSet.
Definition: FrameSet.h:304
Transform LSST spatial data, such as lsst::geom::Point2D and lsst::geom::SpherePoint, using an AST mapping.
Definition: Transform.h:67
ndarray::Array< double, 2, 2 > dataFromArray(Array const &arr) const override
Definition: Endpoint.cc:124
std::shared_ptr< Persistable > get(int id) const
Load the Persistable with the given ID and return it.
std::shared_ptr< geom::TransformPoint2ToPoint2 > getTransform(CameraSys const &fromSys, CameraSys const &toSys) const
Get a Transform from one camera coordinate system to another.
Definition: TransformMap.cc:69
T emplace_back(T... args)