LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
TransformMap.cc
Go to the documentation of this file.
1// -*- lsst-c++ -*-
2/*
3 * Developed for the LSST Data Management System.
4 * This product includes software developed by the LSST Project
5 * (https://www.lsst.org).
6 * See the COPYRIGHT file at the top-level directory of this distribution
7 * for details of code ownership.
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22#include <sstream>
23#include <unordered_set>
24
25#include "lsst/log/Log.h"
26#include "lsst/pex/exceptions.h"
31
32namespace lsst {
33namespace afw {
34namespace cameraGeom {
35
36namespace {
37
38// Allows conversions between LSST and AST data formats
39static lsst::afw::geom::Point2Endpoint const POINT2_ENDPOINT;
40
41static auto LOGGER = LOG_GET("lsst.afw.cameraGeom.TransformMap");
42
43// Make an AST Frame name for a CameraSys.
44std::string makeFrameName(CameraSys const & sys) {
45 std::string r = "Ident=" + sys.getSysName();
46 if (sys.hasDetectorName()) {
47 r += "_";
48 r += sys.getDetectorName();
49 }
50 return r;
51}
52
53/*
54 * Make a vector of `Connection` instances that can be safely passed to
55 * TransformMap's private constructor.
56 *
57 * This guarantees that:
58 * - Connections are sorted according to their distance (in number of
59 * intermediate connections) from the given reference `CameraSys`;
60 * - The `fromSys` of each `Connection` is closer to the reference than the
61 * `toSys`.
62 *
63 * @param[in] reference Reference coordinate system. All systems must be
64 * (indirectly) connected to this system, and will be
65 * sorted according to the number of connections to it.
66 * @param[in] connections Vector of `Connection` instances. Passed by value so
67 * we can either move into it (avoiding a copy) or copy
68 * into it (when we have a const reference and a copy is
69 * unavoidable), depending on the context.
70 *
71 * @returns connections An updated version of the connections vector.
72 *
73 * @throws pex::exceptions::InvalidParameterError Thrown if the vector of
74 * connections graph is empty, contains cycles, is not fully connected, or
75 * includes any connections in which `fromSys == toSys`.
76 */
77std::vector<TransformMap::Connection> standardizeConnections(
78 CameraSys const & reference,
80) {
81 if (connections.empty()) {
82 throw LSST_EXCEPT(
83 pex::exceptions::InvalidParameterError,
84 "Cannot create a TransformMap with no connections."
85 );
86 }
87 // Iterator to the first unprocessed connection in result; will be
88 // incremented as we proceed.
89 auto firstUnprocessed = connections.begin();
90 // All CameraSys whose associated Connections are already in the processed
91 // part of `connections`.
92 std::unordered_set<CameraSys> knownSystems = {reference};
93 // The set of CameraSys whose associated Connections are being processed
94 // in this iteration of the outer (while) loop. These are all some common
95 // distance N from the reference system (in number of connections), where
96 // N increases for each iteration (but is not tracked).
97 std::unordered_set<CameraSys> currentSystems = {reference};
98 // The set of CameraSys that will become currentSys at the next
99 // iteration.
101 LOGLS_DEBUG(LOGGER, "Standardizing: starting with reference " << reference);
102 while (!currentSystems.empty()) {
103 LOGLS_DEBUG(LOGGER, "Standardizing: beginning iteration with currentSystems={ ");
104 for (auto const & sys : currentSystems) {
105 LOGLS_DEBUG(LOGGER, "Standardizing: " << sys << ", ");
106 }
107 LOGLS_DEBUG(LOGGER, "Standardizing: }");
108 // Iterate over all unsorted connections, looking for those associated
109 // with a CameraSys in currentSystems.
110 for (auto connection = firstUnprocessed; connection != connections.end(); ++connection) {
111 bool related = currentSystems.count(connection->fromSys) > 0;
112 if (!related && currentSystems.count(connection->toSys)) {
113 LOGLS_DEBUG(LOGGER, "Standardizing: reversing " << (*connection));
114 // Safe because `connections` is passed by value.
115 connection->reverse();
116 related = true;
117 }
118 if (related) {
119 if (connection->toSys == connection->fromSys) {
121 ss << "Identity connection found: " << (*connection) << ".";
122 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError, ss.str());
123 }
124 if (knownSystems.count(connection->toSys)) {
126 ss << "Multiple paths between reference " << reference
127 << " and " << connection->toSys << ".";
128 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError, ss.str());
129 }
130 LOGLS_DEBUG(LOGGER, "Standardizing: adding " << (*connection));
131 nextSystems.insert(connection->toSys);
132 knownSystems.insert(connection->toSys);
133 std::swap(*firstUnprocessed, *connection);
134 ++firstUnprocessed;
135 }
136 }
137 currentSystems.swap(nextSystems);
138 nextSystems.clear();
139 }
140 // Any connections we haven't processed yet must include only CameraSys
141 // we've never seen before.
142 if (firstUnprocessed != connections.end()) {
144 ss << "Disconnected connection(s) found: " << (*firstUnprocessed);
145 ++firstUnprocessed;
146 for (auto connection = firstUnprocessed; connection != connections.end(); ++connection) {
147 ss << ", " << (*connection);
148 }
149 ss << ".";
150 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError, ss.str());
151 }
152 // No RVO, because this is a function argument, but it's still a move so we
153 // don't care.
154 return connections;
155}
156
157// Return the reference coordinate system from an already-standardized vector of connections.
158CameraSys getReferenceSys(std::vector<TransformMap::Connection> const & connections) {
159 return connections.front().fromSys;
160}
161
162} // anonymous
163
165 transform = transform->inverted();
167}
168
170 return os << connection.fromSys << "->" << connection.toSys;
171}
172
174 CameraSys const & reference,
175 Transforms const & transforms
176) {
177 std::vector<Connection> connections;
178 connections.reserve(transforms.size());
179 for (auto const & pair : transforms) {
180 connections.push_back(Connection{pair.second, reference, pair.first});
181 }
182 // We can't use make_shared because TransformMap ctor is private.
184 new TransformMap(standardizeConnections(reference, std::move(connections)))
185 );
186}
187
189 CameraSys const &reference,
190 std::vector<Connection> const & connections
191) {
192 // We can't use make_shared because TransformMap ctor is private.
194 new TransformMap(standardizeConnections(reference, connections))
195 );
196}
197
198
199// All resources owned by value or by smart pointer
200TransformMap::~TransformMap() noexcept = default;
201
202lsst::geom::Point2D TransformMap::transform(lsst::geom::Point2D const &point, CameraSys const &fromSys,
203 CameraSys const &toSys) const {
204 auto mapping = _getMapping(fromSys, toSys);
205 return POINT2_ENDPOINT.pointFromData(mapping->applyForward(POINT2_ENDPOINT.dataFromPoint(point)));
206}
207
209 CameraSys const &fromSys,
210 CameraSys const &toSys) const {
211 auto mapping = _getMapping(fromSys, toSys);
212 return POINT2_ENDPOINT.arrayFromData(mapping->applyForward(POINT2_ENDPOINT.dataFromArray(pointList)));
213}
214
215bool TransformMap::contains(CameraSys const &system) const noexcept { return _frameIds.count(system) > 0; }
216
218 CameraSys const &toSys) const {
219 return std::make_shared<geom::TransformPoint2ToPoint2>(*_getMapping(fromSys, toSys));
220}
221
222int TransformMap::_getFrame(CameraSys const &system) const {
223 try {
224 return _frameIds.at(system);
225 } catch (std::out_of_range const &e) {
226 std::ostringstream buffer;
227 buffer << "Unsupported coordinate system: " << system;
229 }
230}
231
232std::shared_ptr<ast::Mapping const> TransformMap::_getMapping(CameraSys const &fromSys,
233 CameraSys const &toSys) const {
234 return _frameSet->getMapping(_getFrame(fromSys), _getFrame(toSys));
235}
236
237size_t TransformMap::size() const noexcept { return _frameIds.size(); }
238
239
241 _connections(std::move(connections))
242{
243 // standardizeConnections must be run by anything that calls the
244 // constructor, and that should throw on all of the conditions we assert
245 // on below (which is why those are asserts).
246 assert(!_connections.empty());
247
248 int nFrames = 0; // tracks frameSet->getNFrame() to avoid those (expensive) calls
249
250 // Local helper function that creates a Frame, updates the nFrames counter,
251 // and adds an entry to the frameIds map. Returns the new Frame.
252 // Should always be called in concert with an update to frameSet.
253 auto addFrameForSys = [this, &nFrames](CameraSys const & sys) mutable -> ast::Frame {
254 #ifndef NDEBUG
255 auto r = // We only care about this return value for the assert below;
256 #endif
257 _frameIds.emplace(sys, ++nFrames);
258 assert(r.second); // this must actually insert something, not find an already-inserted CameraSys.
259 return ast::Frame(2, makeFrameName(sys));
260 };
261
262 // FrameSet that manages all transforms; should always be updated in
263 // concert with a call to addFrameForSys.
264 _frameSet = std::make_unique<ast::FrameSet>(addFrameForSys(getReferenceSys(_connections)));
265
266 for (auto const & connection : _connections) {
267 auto fromSysIdIter = _frameIds.find(connection.fromSys);
268 assert(fromSysIdIter != _frameIds.end());
269 _frameSet->addFrame(fromSysIdIter->second, *connection.transform->getMapping(),
270 addFrameForSys(connection.toSys));
271 }
272
273 // We've maintained our own counter for frame IDs for performance and
274 // convenience reasons, but it had better match AST's internal counter.
275 assert(_frameSet->getNFrame() == nFrames);
276}
277
279
280
281namespace {
282
283struct PersistenceHelper {
284
285 static PersistenceHelper const & get() {
286 static PersistenceHelper const instance;
287 return instance;
288 }
289
290 // Schema and keys for the catalog that stores Connection objects.
291 // Considered as a graph, 'from' and 'to' identify vertices, and
292 // 'transform' identifies an edge.
293 table::Schema schema;
294 table::Key<std::string> fromSysName;
295 table::Key<std::string> fromSysDetectorName;
296 table::Key<std::string> toSysName;
297 table::Key<std::string> toSysDetectorName;
298 table::Key<int> transform;
299
300private:
301
302 PersistenceHelper() :
303 schema(),
304 fromSysName(schema.addField<std::string>("fromSysName",
305 "Camera coordinate system name.", "", 0)),
306 fromSysDetectorName(schema.addField<std::string>("fromSysDetectorName",
307 "Camera coordinate system detector name.", "", 0)),
308 toSysName(schema.addField<std::string>("toSysName",
309 "Camera coordinate system name.", "", 0)),
310 toSysDetectorName(schema.addField<std::string>("toSysDetectorName",
311 "Camera coordinate system detector name.", "", 0)),
312 transform(schema.addField<int>("transform", "Archive ID of the transform.", ""))
313 {}
314
315 PersistenceHelper(PersistenceHelper const &) = delete;
316 PersistenceHelper(PersistenceHelper &&) = delete;
317
318 PersistenceHelper & operator=(PersistenceHelper const &) = delete;
319 PersistenceHelper & operator=(PersistenceHelper &&) = delete;
320
321};
322
323
324// PersistenceHelper for a previous format version; now only supported in
325// reading.
326struct OldPersistenceHelper {
327
328 static OldPersistenceHelper const & get() {
329 static OldPersistenceHelper const instance;
330 return instance;
331 }
332
333 // Schema and keys for the catalog that stores TransformMap._frameIds.
334 // Considered as a graph, this is a list of all of the vertices with the
335 // integers that identify them in the list of edges below.
336 table::Schema sysSchema;
337 table::Key<std::string> sysName;
338 table::Key<std::string> detectorName;
339 table::Key<int> id;
340
341 // Schema and keys for the catalog that stores
342 // TransformMap._canonicalConnections entries and the associated Transform
343 // extracted from TransformMap._transforms.
344 // Considered as a graph, 'from' and 'to' identify vertices, and
345 // 'transform' identifies an edge.
346 table::Schema connectionSchema;
347 table::Key<int> from;
348 table::Key<int> to;
349 table::Key<int> transform;
350
351 CameraSys makeCameraSys(table::BaseRecord const & record) const {
352 return CameraSys(record.get(sysName), record.get(detectorName));
353 }
354
355private:
356
357 OldPersistenceHelper() :
358 sysSchema(),
359 sysName(sysSchema.addField<std::string>("sysName", "Camera coordinate system name", "", 0)),
360 detectorName(sysSchema.addField<std::string>("detectorName",
361 "Camera coordinate system detector name", "", 0)),
362 id(sysSchema.addField<int>("id", "AST ID of the Frame for the CameraSys", "")),
364 from(connectionSchema.addField<int>("from", "AST ID of the Frame this transform maps from.", "")),
365 to(connectionSchema.addField<int>("to", "AST ID of the Frame this transform maps to.", "")),
366 transform(connectionSchema.addField<int>("transform", "Archive ID of the transform.", ""))
367 {}
368
369 OldPersistenceHelper(OldPersistenceHelper const &) = delete;
370 OldPersistenceHelper(OldPersistenceHelper &&) = delete;
371
372 OldPersistenceHelper & operator=(OldPersistenceHelper const &) = delete;
373 OldPersistenceHelper & operator=(OldPersistenceHelper &&) = delete;
374
375};
376
377
378} // namespace
379
380
381std::string TransformMap::getPersistenceName() const {
382 return "TransformMap";
383}
384
385std::string TransformMap::getPythonModule() const {
386 return "lsst.afw.cameraGeom";
387}
388
389void TransformMap::write(OutputArchiveHandle& handle) const {
390 auto const & keys = PersistenceHelper::get();
391
392 auto cat = handle.makeCatalog(keys.schema);
393 for (auto const & connection : _connections) {
394 auto record = cat.addNew();
395 record->set(keys.fromSysName, connection.fromSys.getSysName());
396 record->set(keys.fromSysDetectorName, connection.fromSys.getDetectorName());
397 record->set(keys.toSysName, connection.toSys.getSysName());
398 record->set(keys.toSysDetectorName, connection.toSys.getDetectorName());
399 record->set(keys.transform, handle.put(connection.transform));
400 }
401 handle.saveCatalog(cat);
402}
403
405public:
406
407 Factory() : PersistableFactory("TransformMap") {}
408
410 CatalogVector const& catalogs) const {
411 auto const & keys = OldPersistenceHelper::get();
412
413 LSST_ARCHIVE_ASSERT(catalogs.size() == 2u);
414 auto const & sysCat = catalogs[0];
415 auto const & connectionCat = catalogs[1];
416 LSST_ARCHIVE_ASSERT(sysCat.getSchema() == keys.sysSchema);
417 LSST_ARCHIVE_ASSERT(connectionCat.getSchema() == keys.connectionSchema);
418 LSST_ARCHIVE_ASSERT(sysCat.size() == connectionCat.size() + 1);
419 LSST_ARCHIVE_ASSERT(sysCat.isSorted(keys.id));
420
422 for (auto const & sysRecord : sysCat) {
423 auto sys = keys.makeCameraSys(sysRecord);
424 sysById.emplace(sysRecord.get(keys.id), sys);
425 }
426
427 auto const referenceSysIter = sysById.find(1);
428 LSST_ARCHIVE_ASSERT(referenceSysIter != sysById.end());
429 std::vector<Connection> connections;
430 for (auto const & connectionRecord : connectionCat) {
431 auto const fromSysIter = sysById.find(connectionRecord.get(keys.from));
432 LSST_ARCHIVE_ASSERT(fromSysIter != sysById.end());
433 auto const toSysIter = sysById.find(connectionRecord.get(keys.to));
434 LSST_ARCHIVE_ASSERT(toSysIter != sysById.end());
435 auto const transform = archive.get<geom::TransformPoint2ToPoint2>(
436 connectionRecord.get(keys.transform)
437 );
438
439 connections.push_back(Connection{transform, fromSysIter->second, toSysIter->second});
440 }
441
442 connections = standardizeConnections(referenceSysIter->second, std::move(connections));
443 return std::shared_ptr<TransformMap>(new TransformMap(std::move(connections)));
444 }
445
447 CatalogVector const& catalogs) const override {
448 if (catalogs.size() == 2u) {
449 return readOld(archive, catalogs);
450 }
451
452 auto const & keys = PersistenceHelper::get();
453
454 LSST_ARCHIVE_ASSERT(catalogs.size() == 1u);
455 auto const & cat = catalogs[0];
456 LSST_ARCHIVE_ASSERT(cat.getSchema() == keys.schema);
457
458 std::vector<Connection> connections;
459 for (auto const & record : cat) {
460 CameraSys const fromSys(record.get(keys.fromSysName), record.get(keys.fromSysDetectorName));
461 CameraSys const toSys(record.get(keys.toSysName), record.get(keys.toSysDetectorName));
462 auto const transform = archive.get<geom::TransformPoint2ToPoint2>(record.get(keys.transform));
463 connections.push_back(Connection{transform, fromSys, toSys});
464 }
465
466 // Deserialized connections should already be standardized, but be
467 // defensive anyway.
468 auto const referenceSys = getReferenceSys(connections);
469 connections = standardizeConnections(referenceSys, std::move(connections));
470 return std::shared_ptr<TransformMap>(new TransformMap(std::move(connections)));
471 }
472
473 static Factory const registration;
474
475};
476
478
479} // namespace cameraGeom
480
481namespace table {
482namespace io {
483
484template class PersistableFacade<cameraGeom::TransformMap>;
485
486} // namespace io
487} // namespace table
488
489} // namespace afw
490} // namespace lsst
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition: Exception.h:48
LSST DM logging module built on log4cxx.
#define LOG_GET(logger)
Returns a Log object associated with logger.
Definition: Log.h:75
#define LOGLS_DEBUG(logger, message)
Log a debug-level message using an iostream-based interface.
Definition: Log.h:619
std::ostream * os
Definition: Schema.cc:557
table::Key< int > id
table::Key< std::string > sysName
table::Key< std::string > fromSysName
table::Key< int > from
table::Schema sysSchema
table::Schema schema
table::Key< std::string > toSysDetectorName
table::Key< std::string > fromSysDetectorName
table::Key< int > to
table::Key< int > transform
table::Key< std::string > toSysName
table::Key< std::string > detectorName
table::Schema connectionSchema
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
Definition: Persistable.h:48
T at(T... args)
T begin(T... args)
Frame is used to represent a coordinate system.
Definition: Frame.h:157
Camera coordinate system; used as a key in in TransformMap.
Definition: CameraSys.h:83
void swap(CameraSys &other) noexcept
Definition: CameraSys.h:107
std::shared_ptr< Persistable > readOld(InputArchive const &archive, CatalogVector const &catalogs) const
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.
A registry of 2-dimensional coordinate transforms for a specific camera.
Definition: TransformMap.h:62
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.
TransformMap(TransformMap const &other)=delete
size_t size() const noexcept
Get the number of supported coordinate systems.
bool contains(CameraSys const &system) const noexcept
Can this transform to and from the specified coordinate system?
std::vector< Connection > getConnections() const
Return the sequence of connections used to construct this Transform.
std::shared_ptr< geom::TransformPoint2ToPoint2 > getTransform(CameraSys const &fromSys, CameraSys const &toSys) const
Get a Transform from one camera coordinate system to another.
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.
An endpoint for lsst::geom::Point2D.
Definition: Endpoint.h:261
ndarray::Array< double, 2, 2 > dataFromArray(Array const &arr) const override
Get raw data from an array of points.
Definition: Endpoint.cc:123
std::vector< double > dataFromPoint(Point const &point) const override
Get raw data from a single point.
Definition: Endpoint.cc:114
Point pointFromData(std::vector< double > const &data) const override
Get a single point from raw data.
Definition: Endpoint.cc:137
Array arrayFromData(ndarray::Array< double, 2, 2 > const &data) const override
Get an array of points from raw data.
Definition: Endpoint.cc:147
A vector of catalogs used by Persistable.
Definition: CatalogVector.h:29
A multi-catalog archive object used to load table::io::Persistable objects.
Definition: InputArchive.h:31
std::shared_ptr< Persistable > get(int id) const
Load the Persistable with the given ID and return it.
A base class for factory classes used to reconstruct objects from records.
Definition: Persistable.h:228
PersistableFactory(std::string const &name)
Constructor for the factory.
Definition: Persistable.cc:74
Reports invalid arguments.
Definition: Runtime.h:66
T clear(T... args)
T count(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T find(T... args)
T front(T... args)
T insert(T... args)
T move(T... args)
std::ostream & operator<<(std::ostream &os, CameraSysPrefix const &detSysPrefix)
Definition: CameraSys.cc:47
Transform< Point2Endpoint, Point2Endpoint > TransformPoint2ToPoint2
Definition: Transform.h:300
Point< double, 2 > Point2D
Definition: Point.h:324
A base class for image defects.
STL namespace.
T push_back(T... args)
T reserve(T... args)
T size(T... args)
T str(T... args)
Representation of a single edge in the graph defined by a TransformMap.
Definition: TransformMap.h:80
void reverse()
Reverse the connection, by swapping fromSys and toSys and inverting the transform.
std::shared_ptr< geom::TransformPoint2ToPoint2 const > transform
Definition: TransformMap.h:81
T swap(T... args)
T throw_with_nested(T... args)