LSST Applications g02d81e74bb+86cf3d8bc9,g180d380827+7a4e862ed4,g2079a07aa2+86d27d4dc4,g2305ad1205+e1ca1c66fa,g29320951ab+012e1474a1,g295015adf3+341ea1ce94,g2bbee38e9b+0e5473021a,g337abbeb29+0e5473021a,g33d1c0ed96+0e5473021a,g3a166c0a6a+0e5473021a,g3ddfee87b4+c429d67c83,g48712c4677+f88676dd22,g487adcacf7+27e1e21933,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+b41db86c35,g5a732f18d5+53520f316c,g64a986408d+86cf3d8bc9,g858d7b2824+86cf3d8bc9,g8a8a8dda67+585e252eca,g99cad8db69+84912a7fdc,g9ddcbc5298+9a081db1e4,ga1e77700b3+15fc3df1f7,ga8c6da7877+a2b54eae19,gb0e22166c9+60f28cb32d,gba4ed39666+c2a2e4ac27,gbb8dafda3b+6681f309db,gc120e1dc64+f0fcc2f6d8,gc28159a63d+0e5473021a,gcf0d15dbbd+c429d67c83,gdaeeff99f8+f9a426f77a,ge6526c86ff+0433e6603d,ge79ae78c31+0e5473021a,gee10cc3b42+585e252eca,gff1a9f87cc+86cf3d8bc9,w.2024.17
LSST Data Management Base Package
Loading...
Searching...
No Matches
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
382 return "TransformMap";
383}
384
386 return "lsst.afw.cameraGeom";
387}
388
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
table::Key< int > id
Definition Detector.cc:162
#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
table::Key< std::string > sysName
table::Key< std::string > fromSysName
table::Schema sysSchema
table::Key< std::string > toSysDetectorName
table::Key< std::string > fromSysDetectorName
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
virtual void addFrame(int iframe, Mapping const &map, Frame const &frame)
Add a new Frame and an associated Mapping to this FrameSet so as to define a new coordinate system,...
Definition FrameSet.h:210
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
int getNFrame() const
Get FrameSet_NFrame "NFrame": number of Frames in the FrameSet, starting from 1.
Definition FrameSet.h:316
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.
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.
std::string getPersistenceName() const override
Return the unique name used to persist this object and look up its factory.
void write(OutputArchiveHandle &handle) const override
Write the object to one or more catalogs.
TransformMap(TransformMap const &other)=delete
std::string getPythonModule() const override
Return the fully-qualified Python module that should be imported to guarantee that its factory is reg...
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
std::shared_ptr< RecordT > addNew()
Create a new record, add it to the end of the catalog, and return a pointer to it.
Definition Catalog.h:489
A vector of catalogs used by Persistable.
A multi-catalog archive object used to load table::io::Persistable objects.
std::shared_ptr< Persistable > get(int id) const
Load the Persistable with the given ID and return it.
An object passed to Persistable::write to allow it to persist itself.
void saveCatalog(BaseCatalog const &catalog)
Save a catalog in the archive.
BaseCatalog makeCatalog(Schema const &schema)
Return a new, empty catalog with the given schema.
int put(Persistable const *obj, bool permissive=false)
Save an object to the archive and return a unique ID that can be used to retrieve it from an InputArc...
A base class for factory classes used to reconstruct objects from records.
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
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.
void reverse()
Reverse the connection, by swapping fromSys and toSys and inverting the transform.
T swap(T... args)
T throw_with_nested(T... args)