LSST Applications g063fba187b+cac8b7c890,g0f08755f38+6aee506743,g1653933729+a8ce1bb630,g168dd56ebc+a8ce1bb630,g1a2382251a+b4475c5878,g1dcb35cd9c+8f9bc1652e,g20f6ffc8e0+6aee506743,g217e2c1bcf+73dee94bd0,g28da252d5a+1f19c529b9,g2bbee38e9b+3f2625acfc,g2bc492864f+3f2625acfc,g3156d2b45e+6e55a43351,g32e5bea42b+1bb94961c2,g347aa1857d+3f2625acfc,g35bb328faa+a8ce1bb630,g3a166c0a6a+3f2625acfc,g3e281a1b8c+c5dd892a6c,g3e8969e208+a8ce1bb630,g414038480c+5927e1bc1e,g41af890bb2+8a9e676b2a,g7af13505b9+809c143d88,g80478fca09+6ef8b1810f,g82479be7b0+f568feb641,g858d7b2824+6aee506743,g89c8672015+f4add4ffd5,g9125e01d80+a8ce1bb630,ga5288a1d22+2903d499ea,gb58c049af0+d64f4d3760,gc28159a63d+3f2625acfc,gcab2d0539d+b12535109e,gcf0d15dbbd+46a3f46ba9,gda6a2b7d83+46a3f46ba9,gdaeeff99f8+1711a396fd,ge79ae78c31+3f2625acfc,gef2f8181fd+0a71e47438,gf0baf85859+c1f95f4921,gfa517265be+6aee506743,gfa999e8aa5+17cd334064,w.2024.51
LSST Data Management Base Package
Loading...
Searching...
No Matches
frameSetUtils.cc
Go to the documentation of this file.
1/*
2 * LSST Data Management System
3 * Copyright 2017 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 <algorithm>
24#include <exception>
25#include <memory>
26#include <ostream>
27#include <set>
28#include <vector>
29#include <cmath>
30
31#include "boost/format.hpp"
32
33#include "astshim.h"
34#include "lsst/geom/Point.h"
37#include "lsst/afw/image/ImageBase.h" // for wcsNameForXY0
40#include "lsst/pex/exceptions.h"
41#include "lsst/log/Log.h"
42
43namespace lsst {
44namespace afw {
45namespace geom {
46namespace detail {
47namespace {
48
49// destructively make a set of strings from a vector of strings
52}
53
54/*
55 * Copy a FITS header card from a FitsChan to a PropertyList.
56 *
57 * Internal function for use by getPropertyListFromFitsChan.
58 *
59 * @param[in,out] metadata PropertyList to which to copy the value
60 * @param[in] name FITS header card name; used as the name for the new entry in `metadata`
61 * @param[in] foundValue Value and found flag returned by ast::FitsChan.getFits{X};
62 * foundValue.found must be true.
63 * @param[in] comment Card comment; if blank then no comment is written
64 *
65 * @throw lsst::pex::exceptions::LogicError if foundValue.found false.
66 */
67template <typename T>
68void setMetadataFromFoundValue(daf::base::PropertyList& metadata, std::string const& name,
69 ast::FoundValue<T> const& foundValue, std::string const& comment = "") {
70 if (!foundValue.found) {
71 throw LSST_EXCEPT(pex::exceptions::LogicError, "Bug! FitsChan card \"" + name + "\" not found");
72 }
73 if (comment.empty()) {
74 metadata.set(name, foundValue.value);
75 } else {
76 metadata.set(name, foundValue.value, comment);
77 }
78}
79
80} // namespace
81
83 // Exclude WCS A keywords because LSST uses them to store XY0
84 auto wcsANames = createTrivialWcsMetadata("A", lsst::geom::Point2I(0, 0))->names();
85 std::set<std::string> excludeNames(wcsANames.begin(), wcsANames.end());
86 // Ignore NAXIS1, NAXIS2 because if they are zero then AST will fail to read a WCS
87 // Ignore LTV1/2 because LSST adds it and this code should ignore it and not strip it
88 // Exclude comments and history to reduce clutter
89 std::set<std::string> moreNames{"NAXIS1", "NAXIS2", "LTV1", "LTV2", "COMMENT", "HISTORY"};
90 excludeNames.insert(moreNames.begin(), moreNames.end());
91
92 // Replace RADECSYS with RADESYS if only the former is present
93 if (metadata.exists("RADECSYS") && !metadata.exists("RADESYS")) {
94 metadata.set("RADESYS", metadata.getAsString("RADECSYS"));
95 metadata.remove("RADECSYS");
96 }
97
98 auto channel = getFitsChanFromPropertyList(metadata, excludeNames,
99 "Encoding=FITS-WCS, IWC=1, SipReplace=0, ReportLevel=3");
100 auto const initialNames = strip ? setFromVector(channel.getAllCardNames()) : std::set<std::string>();
102 try {
103 obj = channel.read();
104 } catch (std::runtime_error const&) {
105 throw LSST_EXCEPT(pex::exceptions::TypeError, "The metadata does not describe an AST object");
106 }
107
108 auto frameSet = std::dynamic_pointer_cast<ast::FrameSet>(obj);
109 if (!frameSet) {
111 "metadata describes a " + obj->getClassName() + ", not a FrameSet");
112 }
113 if (strip) {
114 auto const finalNames = setFromVector(channel.getAllCardNames());
115
116 // FITS keywords that FitsChan stripped
117 std::set<std::string> namesChannelStripped;
118 std::set_difference(initialNames.begin(), initialNames.end(), finalNames.begin(), finalNames.end(),
119 std::inserter(namesChannelStripped, namesChannelStripped.begin()));
120
121 // FITS keywords that FitsChan may strip that we want to keep in `metadata`
122 std::set<std::string> const namesToKeep = {"DATE-OBS", "MJD-OBS"};
123
124 std::set<std::string> namesToStrip; // names to strip from metadata
125 std::set_difference(namesChannelStripped.begin(), namesChannelStripped.end(), namesToKeep.begin(),
126 namesToKeep.end(), std::inserter(namesToStrip, namesToStrip.begin()));
127 for (auto const& name : namesToStrip) {
128 metadata.remove(name);
129 }
130 }
131 return frameSet;
132}
133
135 // Exclude WCS A keywords because LSST uses them to store XY0
136 auto wcsANames = createTrivialWcsMetadata("A", lsst::geom::Point2I(0, 0))->names();
137 std::set<std::string> excludeNames(wcsANames.begin(), wcsANames.end());
138 // Ignore NAXIS1, NAXIS2 because if they are zero then AST will fail to read a WCS
139 // Ignore LTV1/2 because LSST adds it and this code should ignore it and not strip it
140 // Exclude comments and history to reduce clutter
141 std::set<std::string> moreNames{"NAXIS1", "NAXIS2", "LTV1", "LTV2", "COMMENT", "HISTORY"};
142 excludeNames.insert(moreNames.begin(), moreNames.end());
143
144 // Replace RADECSYS with RADESYS if only the former is present
145 if (metadata.exists("RADECSYS") && !metadata.exists("RADESYS")) {
146 metadata.set("RADESYS", metadata.getAsString("RADECSYS"));
147 metadata.remove("RADECSYS");
148 }
149
150 auto channel = getFitsChanFromPropertyList(metadata, excludeNames,
151 "Encoding=FITS-WCS, IWC=1, SipReplace=0, ReportLevel=3");
152 auto const initialNames = setFromVector(channel.getAllCardNames());
153
155 try {
156 obj = channel.read();
157 } catch (std::runtime_error const&) {
158 return;
159 }
160
161 auto const finalNames = setFromVector(channel.getAllCardNames());
162
163 // FITS keywords that FitsChan stripped
164 std::set<std::string> namesChannelStripped;
165 std::set_difference(initialNames.begin(), initialNames.end(), finalNames.begin(), finalNames.end(),
166 std::inserter(namesChannelStripped, namesChannelStripped.begin()));
167
168 // FITS keywords that FitsChan may strip that we want to keep in `metadata`
169 std::set<std::string> const namesToKeep = {"DATE-OBS", "MJD-OBS"};
170
171 std::set<std::string> namesToStrip; // names to strip from metadata
172 std::set_difference(namesChannelStripped.begin(), namesChannelStripped.end(), namesToKeep.begin(),
173 namesToKeep.end(), std::inserter(namesToStrip, namesToStrip.begin()));
174 for (auto const& name : namesToStrip) {
175 metadata.remove(name);
176 }
177}
178
180 // Record CRPIX in GRID coordinates
181 // so we can compute CRVAL after standardizing the SkyFrame to ICRS
182 // (that standardization is why we don't simply save CRVAL now)
183 std::vector<double> crpixGrid(2);
184 try {
185 crpixGrid[0] = metadata.getAsDouble("CRPIX1");
186 crpixGrid[1] = metadata.getAsDouble("CRPIX2");
188 // std::string used because e.what() returns a C string and two C strings cannot be added
190 e.what() + std::string("; cannot read metadata as a SkyWcs"));
191 }
192
193 auto rawFrameSet = readFitsWcs(metadata, strip);
194 auto const initialBaseIndex = rawFrameSet->getBase();
195
196 // Find the GRID frame
197 auto gridIndex = ast::FrameSet::NOFRAME;
198 if (rawFrameSet->findFrame(ast::Frame(2, "Domain=GRID"))) {
199 gridIndex = rawFrameSet->getCurrent();
200 } else {
201 // No appropriate GRID frame found; if the original base frame is of type Frame
202 // with 2 axes and a blank domain then use that, else give up
203 auto const baseFrame = rawFrameSet->getFrame(initialBaseIndex, false);
204 auto const baseClassName = rawFrameSet->getClassName();
205 if (baseFrame->getClassName() != "Frame") {
207 "The base frame is of type " + baseFrame->getClassName() +
208 "instead of Frame; cannot read metadata as a SkyWcs");
209 }
210 if (baseFrame->getNAxes() != 2) {
212 "The base frame has " + std::to_string(baseFrame->getNAxes()) +
213 " axes instead of 2; cannot read metadata as a SkyWcs");
214 }
215 if (baseFrame->getDomain() != "") {
217 "The base frame has domain \"" + baseFrame->getDomain() +
218 "\" instead of blank or GRID; cannot read metadata as a SkyWcs");
219 }
220 // Original base frame has a blank Domain, is of type Frame, and has 2 axes, so
221 // Set its domain to GRID, and set some other potentially useful attributes.
222 gridIndex = initialBaseIndex;
223 }
224
225 // Find the IWC frame
226 if (!rawFrameSet->findFrame(ast::Frame(2, "Domain=IWC"))) {
227 throw LSST_EXCEPT(pex::exceptions::TypeError, "No IWC frame found; cannot read metadata as a SkyWcs");
228 }
229 auto const iwcIndex = rawFrameSet->getCurrent();
230 auto const iwcFrame = rawFrameSet->getFrame(iwcIndex);
231
232 // Create a standard sky frame: ICRS with axis order RA, Dec
233
234 // Create the a template for the standard sky frame
235 auto const stdSkyFrameTemplate = ast::SkyFrame("System=ICRS");
236
237 // Locate a Frame in the target FrameSet that looks like the template
238 // and hence can be used as the original sky frame.
239 // We ignore the frame set returned by findFrame because that goes from pixels to sky,
240 // and using it would add an unwanted extra branch to our WCS; instead, later on,
241 // we compute a mapping from the old sky frame to the new sky frame and add that.
242 if (!rawFrameSet->findFrame(stdSkyFrameTemplate)) {
244 "Could not find a SkyFrame; cannot read metadata as a SkyWcs");
245 }
246 auto initialSkyIndex = rawFrameSet->getCurrent();
247
248 // Compute a frame set that maps from the original sky frame to our desired sky frame template;
249 // this contains the mapping and sky frame we will insert into the frame set.
250 // (Temporarily set the base frame to the sky frame, because findFrame
251 // produces a mapping from base to the found frame).
252 rawFrameSet->setBase(initialSkyIndex);
253 auto stdSkyFrameSet = rawFrameSet->findFrame(stdSkyFrameTemplate);
254 if (!stdSkyFrameSet) {
256 "Bug: found a SkyFrame the first time, but not the second time");
257 }
258
259 // Add the new mapping into rawFrameSet, connecting it to the original SkyFrame.
260 // Note: we use stdSkyFrameSet as the new frame (meaning stdSkyFrameSet's current frame),
261 // because, unlike stdSkyFrameTemplate, stdSkyFrameSet's current frame has inherited some
262 // potentially useful attributes from the old sky frame, such as epoch.
263 rawFrameSet->addFrame(initialSkyIndex, *stdSkyFrameSet->getMapping()->simplified(),
264 *stdSkyFrameSet->getFrame(ast::FrameSet::CURRENT));
265 auto const stdSkyIndex = rawFrameSet->getCurrent();
266 auto const stdSkyFrame = rawFrameSet->getFrame(stdSkyIndex, false);
267
268 // Compute a mapping from PIXELS (0-based in parent coordinates)
269 // to GRID (1-based in local coordinates)
271 std::vector<double> pixelToGridArray = {1.0 - xy0[0], 1.0 - xy0[1]}; // 1.0 for FITS vs LSST convention
272 auto pixelToGrid = ast::ShiftMap(pixelToGridArray);
273
274 // Now construct the returned FrameDict
275 auto const gridToIwc = rawFrameSet->getMapping(gridIndex, iwcIndex)->simplified();
276 auto const pixelToIwc = pixelToGrid.then(*gridToIwc).simplified();
277 auto const iwcToStdSky = rawFrameSet->getMapping(iwcIndex, stdSkyIndex);
278
279 auto frameDict = std::make_shared<ast::FrameDict>(ast::Frame(2, "Domain=PIXELS"), *pixelToIwc, *iwcFrame);
280 frameDict->addFrame("IWC", *iwcToStdSky, *stdSkyFrame);
281
282 // Record CRVAL as SkyRef in the SkyFrame so it can easily be obtained later;
283 // set SkyRefIs = "Ignored" (the default) so SkyRef value is ignored instead of used as an offset
284 auto crpixPixels = pixelToGrid.applyInverse(crpixGrid);
285 auto crvalRad = frameDict->applyForward(crpixPixels);
286 auto skyFrame = std::dynamic_pointer_cast<ast::SkyFrame>(frameDict->getFrame("SKY", false));
287 if (!skyFrame) {
288 throw LSST_EXCEPT(pex::exceptions::LogicError, "SKY frame is not a SkyFrame");
289 }
290 skyFrame->setSkyRefIs("Ignored");
291 skyFrame->setSkyRef(crvalRad);
292
293 return frameDict;
294}
295
297 int const numCards = fitsChan.getNCard();
298 auto metadata = std::make_shared<daf::base::PropertyList>();
299 for (int cardNum = 1; cardNum <= numCards; ++cardNum) {
300 fitsChan.setCard(cardNum);
301 auto const cardType = fitsChan.getCardType();
302 auto const cardName = fitsChan.getCardName();
303 auto const cardComment = fitsChan.getCardComm();
304 switch (cardType) {
306 auto foundValue = fitsChan.getFitsF();
307 setMetadataFromFoundValue(*metadata, cardName, foundValue, cardComment);
308 break;
309 }
310 case ast::CardType::INT: {
311 auto foundValue = fitsChan.getFitsI();
312 setMetadataFromFoundValue(*metadata, cardName, foundValue, cardComment);
313 break;
314 }
316 auto foundValue = fitsChan.getFitsS();
317 setMetadataFromFoundValue(*metadata, cardName, foundValue, cardComment);
318 break;
319 }
321 auto foundValue = fitsChan.getFitsL();
322 setMetadataFromFoundValue(*metadata, cardName, foundValue, cardComment);
323 break;
324 }
326 if (cardComment.empty()) {
327 metadata->set(cardName, nullptr);
328 } else {
329 metadata->set(cardName, nullptr, cardComment);
330 }
331 break;
332 }
334 auto foundValue = fitsChan.getFitsCN();
335 setMetadataFromFoundValue(*metadata, cardName, foundValue, cardComment);
336 break;
337 }
339 // Drop HISTORY and COMMENT cards
340 break;
343 // PropertyList supports neither complex numbers nor cards with no value
345 os << "Card " << cardNum << " with name \"" << cardName << "\" has type "
346 << static_cast<int>(cardType) << ", which is not supported by PropertyList";
348 }
350 // This should only occur if cardNum is invalid, and that should be impossible
352 os << "Bug! Card " << cardNum << " with name \"" << cardName
353 << "\" has type NOTYPE, which should not be possible";
355 }
356 }
357 }
358 return metadata;
359}
360
362 std::set<std::string> const& excludeNames,
364 // Create FitsChan to receive each of the parameters
365 auto stream = ast::StringStream("");
366 auto fc = ast::FitsChan(stream, options);
367
368 // Get the parameter names (in order if necessary)
369 daf::base::PropertyList const *pl = dynamic_cast<daf::base::PropertyList const *>(&metadata);
370 std::vector<std::string> allParamNames;
371 if (pl) {
372 allParamNames = pl->getOrderedNames();
373 } else {
374 allParamNames = metadata.paramNames(false);
375 }
376
377 // Loop over the names and add them to the FitsChan if not excluded
378 for (auto const &name : allParamNames) {
379 if (excludeNames.count(name) == 0) {
380 std::type_info const &type = metadata.typeOf(name);
381
382 if (name.size() > 8) {
383 continue; // The name is too long for a FITS keyword; skip this item
384 }
385
386 if (type == typeid(bool)) {
387 fc.setFitsL(name, metadata.get<bool>(name));
388 } else if (type == typeid(std::uint8_t)) {
389 fc.setFitsI(name, static_cast<int>(metadata.get<std::uint8_t>(name)));
390 } else if (type == typeid(int)) {
391 fc.setFitsI(name, metadata.get<int>(name));
392 } else if (type == typeid(double) || type == typeid(float)) {
393 double value;
394 if (type == typeid(double)) {
395 value = metadata.get<double>(name);
396 } else {
397 value = static_cast<double>(metadata.get<float>(name));
398 }
399 // NaN is not allowed in a FitsChan (or in FITS)
400 if (!std::isnan(value)) {
401 fc.setFitsF(name, value);
402 } else {
403 // Treat it like an undefined value but warn about it
404 LOGLS_WARN("lsst.afw.geom.frameSetUtils",
405 boost::format("Found NaN in metadata item '%s'") % name);
406 }
407 } else if (type == typeid(std::string)) {
408 std::string str = metadata.get<std::string>(name);
409 // No support for long strings yet so skip those
410 if (str.size() <= 68) {
411 fc.setFitsS(name, metadata.get<std::string>(name));
412 }
413 }
414 }
415 }
416 // Rewind the channel
417 fc.setCard(0);
418 return fc;
419}
420
421} // namespace detail
422} // namespace geom
423} // namespace afw
424} // namespace lsst
table::Key< std::string > name
Definition Amplifier.cc:116
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition Exception.h:48
LSST DM logging module built on log4cxx.
#define LOGLS_WARN(logger, message)
Log a warn-level message using an iostream-based interface.
Definition Log.h:659
std::ostream * os
Definition Schema.cc:557
table::Key< table::Array< std::uint8_t > > frameSet
T begin(T... args)
A specialized form of Channel which reads and writes FITS header cards.
Definition FitsChan.h:202
std::string getCardComm() const
Get CardComm: the comment of the current card.
Definition FitsChan.h:491
FoundValue< std::string > getFitsS(std::string const &name="", std::string defval="") const
Get the value of a string card.
Definition FitsChan.cc:98
void setCard(int ind)
Set Card: the index of the current card, where 1 is the first card.
Definition FitsChan.h:1036
FoundValue< bool > getFitsL(std::string const &name="", bool defval=false) const
Get the value of a bool card.
Definition FitsChan.cc:91
std::string getCardName() const
Get CardName: the keyword name of the current card.
Definition FitsChan.h:496
FoundValue< int > getFitsI(std::string const &name="", int defval=0) const
Get the value of a int card.
Definition FitsChan.cc:84
CardType getCardType() const
Get CardType: data type of the current FITS card.
Definition FitsChan.h:501
int getNCard() const
Get NCard: the number of cards.
Definition FitsChan.h:555
FoundValue< double > getFitsF(std::string const &name="", double defval=0) const
Get the value of a double card.
Definition FitsChan.cc:77
FoundValue< std::string > getFitsCN(std::string const &name="", std::string defval="") const
Get the value of a CONTINUE card.
Definition FitsChan.cc:69
A value and associated validity flag.
Definition FitsChan.h:69
T value
The found value; ignore if found is false.
Definition FitsChan.h:82
bool found
Was the value found?
Definition FitsChan.h:81
Frame is used to represent a coordinate system.
Definition Frame.h:157
static int constexpr CURRENT
index of current frame
Definition FrameSet.h:105
static int constexpr NOFRAME
an invalid frame index
Definition FrameSet.h:106
ShiftMap is a linear Mapping which shifts each axis by a specified constant value.
Definition ShiftMap.h:40
SkyFrame is a specialised form of Frame which describes celestial longitude/latitude coordinate syste...
Definition SkyFrame.h:66
String-based source and sink for channels.
Definition Stream.h:180
Class for storing ordered metadata with comments.
std::vector< std::string > getOrderedNames() const
Get the list of property names, in the order they were added.
Class for storing generic metadata.
Definition PropertySet.h:66
std::string getAsString(std::string const &name) const
Get the last value for a string property name (possibly hierarchical).
virtual void remove(std::string const &name)
Remove all values for a property name (possibly hierarchical).
bool exists(std::string const &name) const
Determine if a name (possibly hierarchical) exists.
void set(std::string const &name, T const &value)
Replace all values for a property name (possibly hierarchical) with a new scalar value.
std::type_info const & typeOf(std::string const &name) const
Get the type of values for a property name (possibly hierarchical).
double getAsDouble(std::string const &name) const
Get the last value for any arithmetic property name (possibly hierarchical).
T get(std::string const &name) const
Get the last value for a property name (possibly hierarchical).
std::vector< std::string > paramNames(bool topLevelOnly=true) const
A variant of names that excludes the names of subproperties.
virtual char const * what(void) const noexcept
Return a character string summarizing this exception.
Definition Exception.cc:99
Reports errors in the logical structure of the program.
Definition Runtime.h:46
Reports attempts to access elements using an invalid key.
Definition Runtime.h:151
Reports errors from accepting an object of an unexpected or inappropriate type.
Definition Runtime.h:167
T count(T... args)
T end(T... args)
bool strip
Definition fits.cc:930
T insert(T... args)
T inserter(T... args)
T isnan(T... args)
T make_move_iterator(T... args)
@ NOTYPE
card does not exist (card number invalid)
@ CONTINUE
CONTINUE card.
@ LOGICAL
boolean
@ COMPLEXI
complex integer
@ INT
integer
@ COMPLEXF
complex floating point
@ STRING
string
@ UNDEF
card has no value
@ COMMENT
card is a comment-style card with no "=" (COMMENT, HISTORY, ...)
std::shared_ptr< ast::FrameSet > readFitsWcs(daf::base::PropertySet &metadata, bool strip=true)
Read a FITS convention WCS FrameSet from FITS metadata.
ast::FitsChan getFitsChanFromPropertyList(daf::base::PropertySet &metadata, std::set< std::string > const &excludeNames={}, std::string options="")
Construct AST FitsChan from PropertyList.
void stripWcsMetadata(daf::base::PropertySet &metadata)
Strip all WCS metadata from a header.
std::shared_ptr< ast::FrameDict > readLsstSkyWcs(daf::base::PropertySet &metadata, bool strip=true)
Read an LSST celestial WCS FrameDict from a FITS header.
std::shared_ptr< daf::base::PropertyList > getPropertyListFromFitsChan(ast::FitsChan &fitsChan)
Copy values from an AST FitsChan into a PropertyList.
std::shared_ptr< daf::base::PropertyList > createTrivialWcsMetadata(std::string const &wcsName, lsst::geom::Point2I const &xy0)
Definition wcsUtils.cc:48
lsst::geom::Point2I getImageXY0FromMetadata(daf::base::PropertySet &metadata, std::string const &wcsName, bool strip=false)
Definition wcsUtils.cc:95
std::string const wcsNameForXY0
Definition ImageBase.h:70
T set_difference(T... args)
T to_string(T... args)