LSSTApplications  11.0-24-g0a022a1,14.0+77,15.0,15.0+1
LSSTDataManagementBasePackage
utils.cc
Go to the documentation of this file.
1 // -*- lsst-C++ -*-
2 // Astrometry.net include files:
3 extern "C" {
4 #include "astrometry/solver.h"
5 #include "astrometry/index.h"
6 #include "astrometry/starkd.h"
7 #include "astrometry/fitsioutils.h"
8 #include "astrometry/fitstable.h"
9 
10 #undef ATTRIB_FORMAT
11 #undef FALSE
12 #undef TRUE
13 }
14 
15 #include <set>
16 #include <cstdint>
17 #include "boost/format.hpp"
18 
20 #include "lsst/base.h"
21 #include "lsst/pex/exceptions.h"
22 #include "lsst/afw/table/Source.h"
23 #include "lsst/afw/image/Calib.h"
24 #pragma clang diagnostic push
25 #pragma clang diagnostic ignored "-Wunused-variable"
26 #include "lsst/afw/geom/Angle.h"
27 #include "lsst/afw/geom/Point.h"
28 #pragma clang diagnostic pop
29 
30 namespace afwTable = lsst::afw::table;
31 namespace afwGeom = lsst::afw::geom;
32 
33 namespace lsst {
34 namespace meas {
35 namespace extensions {
36 namespace astrometryNet {
37 namespace detail {
38 
39 static float *
40 read_column(
41  fitstable_t* tag,
42  char const *colName,
43  tfits_type type,
44  int *starinds,
45  int nstars,
46  char const *indexName)
47 {
48  float *col = static_cast<float*>(fitstable_read_column_inds(tag, colName, type, starinds, nstars));
49  if (!col) {
51  str(boost::format("Unable to read data for %s from %s") % colName % indexName));
52  }
53 
54  return col;
55 }
56 
57 
60  lsst::afw::coord::Coord const &ctrCoord,
61  lsst::afw::geom::Angle const &radius,
62  char const* idCol,
63  std::vector<MagColInfo> const& magColInfoList,
64  char const* isStarCol,
65  char const* isVarCol,
66  bool uniqueIds)
67 {
68  /*
69  If uniqueIds == true: return only reference sources with unique IDs;
70  arbitrarily keep the first star found with each ID.
71  */
72 
73  size_t const nMag = magColInfoList.size(); /* number of magnitude[error] columns */
74 
75  for (auto mc = magColInfoList.cbegin(); mc != magColInfoList.cend(); ++mc) {
76  if (mc->filterName.empty()) {
78  "Magnitude names cannot be empty strings.");
79  }
80  // We enforce this condition because we convert the mags to fluxes, and
81  // we need a flux to compute a flux error!
82  if (mc->magCol.empty()) {
84  "Magnitude column names cannot be empty strings.");
85  }
86  //printf("mag col \"%s\", \"%s\", \"%s\"\n", mc->filterName.c_str(), mc->magCol.c_str(), mc->magErrCol.c_str());
87  }
88 
89  auto icrsCoord = ctrCoord.toIcrs();
90  double raDeg = icrsCoord.getLongitude().asDegrees();
91  double decDeg = icrsCoord.getLatitude().asDegrees();
92  double xyz[3];
93  radecdeg2xyzarr(raDeg, decDeg, xyz);
94  double r2 = deg2distsq(radius.asDegrees());
95 
97 
98  afw::table::PointKey<double>::addFields(schema, "centroid",
99  "centroid on some exposure; invalid unless \"hasCentroid\" is true)", "pixels");
100  auto hasCentroidKey = schema.addField<afwTable::Flag>("hasCentroid",
101  "true if centroid field has been set");
102 
103  std::vector<afwTable::Key<double> > fluxKey; // these are double for consistency with measured fluxes;
104  std::vector<afwTable::Key<double> > fluxErrKey; // double may be unnecessary, but less surprising.
105  fluxKey.reserve(nMag);
106  fluxErrKey.reserve(nMag);
107 
108  for (auto mc = magColInfoList.cbegin(); mc != magColInfoList.cend(); ++mc) {
109  // Add schema elements for each requested flux (and optionally flux error)
110  // avoid the comment "flux flux"
111  fluxKey.push_back(
112  schema.addField<double>(
113  mc->filterName + "_flux",
114  mc->filterName + " flux"));
115  if (mc->hasErr()) {
116  fluxErrKey.push_back(
117  schema.addField<double>(
118  mc->filterName + "_fluxSigma",
119  mc->filterName + " flux uncertainty (sigma)"));
120  }
121  }
122 
123  afwTable::Key<afwTable::Flag> resolvedKey;
124  if (isStarCol) {
125  resolvedKey = schema.addField<afwTable::Flag>(
126  "resolved",
127  "set if the reference object is resolved");
128  }
129  afwTable::Key<afwTable::Flag> variableKey;
130  if (isVarCol) {
131  variableKey = schema.addField<afwTable::Flag>(
132  "variable",
133  "set if the reference object is variable");
134  }
135  afwTable::Key<afwTable::Flag> photometricKey = schema.addField<afwTable::Flag>(
136  "photometric",
137  "set if the reference object can be used in photometric calibration");
138 
140  if (idCol) {
141  // make catalog with no IdFactory, since IDs are external
143  } else {
144  // let the catalog assign IDs
146  }
147 
148  // for uniqueIds: keep track of the IDs we have already added to the result set.
150 
151  for (std::vector<index_t*>::iterator pind = inds.begin(); pind != inds.end(); ++pind) {
152  index_t* ind = (*pind);
153  // Find nearby stars
154  double *radecs = NULL;
155  int *starinds = NULL;
156  int nstars = 0;
157  startree_search_for(ind->starkd, xyz, r2, NULL, &radecs, &starinds, &nstars);
158  //printf("found %i in \"%s\"\n", nstars, ind->indexname);
159  if (nstars == 0) {
160  continue;
161  }
162 
164  std::vector<float*> magErr;
165  mag.reserve(nMag);
166  magErr.reserve(nMag);
167  std::int64_t* id = NULL;
168  bool* stargal = NULL;
169  bool* var = NULL;
170  if (idCol || nMag || isStarCol || isVarCol) {
171  fitstable_t* tag = startree_get_tagalong(ind->starkd);
172  tfits_type flt = fitscolumn_float_type();
173  tfits_type boo = fitscolumn_boolean_type();
174  tfits_type i64 = fitscolumn_i64_type();
175 
176  if (!tag) {
177  std::string msg = boost::str(boost::format(
178  "astrometry_net_data index file %s does not contain a tag-along table, "
179  "so can't retrieve extra columns. idCol=%s, isStarCol=%s, isVarCol=%s") %
180  ind->indexname % idCol % isStarCol % isVarCol);
181  msg += ", mag columns=[";
182  for (unsigned int i=0; i<nMag; i++) {
183  if (i) {
184  msg += ",";
185  }
186  msg += " name='" + magColInfoList[i].filterName +
187  "', mag='" + magColInfoList[i].magCol +
188  "', magErr='" + magColInfoList[i].magErrCol + "'";
189  }
190  msg += " ]. You may need to edit the $ASTROMETRY_NET_DATA_DIR/andConfig.py file to set idColumn=None, etc.";
192  }
193 
194  if (idCol) {
195  id = static_cast<int64_t*>(fitstable_read_column_inds(tag, idCol, i64, starinds, nstars));
196  if (!id) {
198  str(boost::format("Unable to read data for %s from %s") % idCol % ind->indexname));
199  }
200  }
201  if (id && uniqueIds) {
202  // remove duplicate IDs.
203 
204  // FIXME -- this shouldn't be necessary once we get astrometry_net 0.40
205  // multi-index functionality in place.
206 
207  if (uids.empty()) {
208  uids = std::set<std::int64_t>(id, id+nstars);
209  } else {
210  int nkeep = 0;
211  for (int i=0; i<nstars; i++) {
212  //std::pair<std::set<std::int64_t>::iterator, bool>
213  if (uids.insert(id[i]).second) {
214  // inserted; keep this one.
215  if (nkeep != i) {
216  // compact the arrays.
217  starinds[nkeep] = starinds[i];
218  radecs[nkeep*2+0] = radecs[i*2+0];
219  radecs[nkeep*2+1] = radecs[i*2+1];
220  id[nkeep] = id[i];
221  }
222  nkeep++;
223  } else {
224  // did not insert (this id has already been found);
225  // drop this star.
226  }
227  }
228  nstars = nkeep;
229  // if they were all duplicate IDs...
230  if (nstars == 0) {
231  free(starinds);
232  free(radecs);
233  free(id);
234  continue;
235  }
236  }
237  }
238 
239  for (auto mc = magColInfoList.cbegin(); mc != magColInfoList.cend(); ++mc) {
240  char const* col = mc->magCol.c_str();
241  mag.push_back(read_column(tag, col, flt, starinds, nstars, ind->indexname));
242  if (mc->hasErr()) {
243  char const* col = mc->magErrCol.c_str();
244  magErr.push_back(read_column(tag, col, flt, starinds, nstars, ind->indexname));
245  }
246  }
247  if (isStarCol) {
248  /* There is something weird going on with handling of bools; maybe "T" vs "F"?
249  stargal = static_cast<bool*>(fitstable_read_column_inds(tag, isStarCol, boo, starinds, nstars));
250  for (int j=0; j<nstars; j++) {
251  printf(" sg %i = %i, vs %i\n", j, (int)sg[j], stargal[j] ? 1:0);
252  }
253  */
254  uint8_t* sg = static_cast<uint8_t*>(fitstable_read_column_inds(
255  tag, isStarCol, fitscolumn_u8_type(), starinds, nstars));
256  stargal = static_cast<bool*>(malloc(nstars));
257  if (!stargal) {
259  str(boost::format("Unable to read data for %s from %s") % isStarCol % ind->indexname));
260  }
261  for (int j=0; j<nstars; j++) {
262  stargal[j] = (sg[j] > 0);
263  }
264  free(sg);
265  }
266  if (isVarCol) {
267  var = static_cast<bool*>(fitstable_read_column_inds(tag, isVarCol, boo, starinds, nstars));
268  if (!var) {
270  str(boost::format("Unable to read data for %s from %s") % isVarCol % ind->indexname));
271  }
272  }
273  }
274 
275  for (int i=0; i<nstars; i++) {
276  PTR(afwTable::SimpleRecord) src = cat.addNew();
277 
278  // Note that all coords in afwTable catalogs are ICRS; hopefully that's what the
279  // reference catalogs are (and that's what the code assumed before JFB modified it).
280  src->setCoord(
282  radecs[i * 2 + 0] * afwGeom::degrees,
283  radecs[i * 2 + 1] * afwGeom::degrees
284  )
285  );
286 
287  if (id) {
288  src->setId(id[i]);
289  }
290 
291  src->set(hasCentroidKey, false);
292 
293  assert(fluxKey.size() == nMag);
294  // only non-empty error columns are populated in these vectors.
295  assert(fluxErrKey.size() == magErr.size());
296  // index into non-empty error columns.
297  size_t ej = 0;
298  size_t j = 0;
299  for (auto mc = magColInfoList.cbegin(); mc != magColInfoList.cend(); ++mc, ++j) {
300  // There is some dispute about returning flux or magnitudes
301  // flux is not conventional, but is convenient for several reasons
302  // - it simplifies matching to sources for astrometric calibration
303  // - flux errors are easier to combine than magnitude errors
304  //printf("mag %s = %g\n", mc->filterName.c_str(), mag[j][i]);
305  double flux = lsst::afw::image::fluxFromABMag(mag[j][i]);
306  //printf("flux %g\n", flux);
307  src->set(fluxKey[j], flux);
308  if (mc->hasErr()) {
309  //printf("mag err = %g\n", magErr[ej][i]);
310  double fluxErr = lsst::afw::image::fluxErrFromABMagErr(magErr[ej][i], mag[j][i]);
311  //printf("flux err = %g\n", fluxErr);
312  src->set(fluxErrKey[ej], fluxErr);
313  ej++;
314  }
315  }
316  assert(ej == fluxErrKey.size());
317 
318  bool photometric = true;
319  if (stargal) {
320  src->set(resolvedKey, !stargal[i]);
321  photometric &= stargal[i];
322  }
323  if (var) {
324  src->set(variableKey, var[i]);
325  photometric &= (!var[i]);
326  }
327  src->set(photometricKey, photometric);
328  }
329 
330  free(id);
331  for (size_t j=0; j<mag.size(); ++j) {
332  free(mag[j]);
333  }
334  for (size_t j=0; j<magErr.size(); ++j) {
335  free(magErr[j]);
336  }
337  free(stargal);
338  free(var);
339  free(radecs);
340  free(starinds);
341  }
342  return cat;
343 }
344 
345 }}}}}
Defines the fields and offsets for a table.
Definition: Schema.h:47
lsst::afw::table::SimpleCatalog getCatalogImpl(std::vector< index_t *> inds, lsst::afw::coord::Coord const &ctrCoord, lsst::afw::geom::Angle const &radius, const char *idCol, std::vector< MagColInfo > const &magColInfoList, const char *starGalCol, const char *varCol, bool uniqueIds=true)
Implementation for index_t::getCatalog method.
Definition: utils.cc:59
T empty(T... args)
lsst::afw::geom::Angle getLongitude() const
The main access method for the longitudinal coordinate.
Definition: Coord.h:166
list extensions
Definition: conf.py:22
static PointKey addFields(Schema &schema, std::string const &name, std::string const &doc, std::string const &unit)
Add a pair of _x, _y fields to a Schema, and return a PointKey that points to them.
static Schema makeMinimalSchema()
Return a minimal schema for Simple tables and records.
Definition: Simple.h:130
afw::table::Schema schema
Definition: GaussianPsf.cc:43
double fluxErrFromABMagErr(double magErr, double mag)
Compute flux error in Janskys from AB magnitude error and AB magnitude.
Definition: Calib.h:69
AngleUnit constexpr degrees
constant with units of degrees
Definition: Angle.h:88
virtual IcrsCoord toIcrs() const
Convert ourself to ICRS: RA, Dec (basically J2000)
Definition: Coord.cc:572
T cend(T... args)
STL class.
Include files required for standard LSST Exception handling.
double fluxFromABMag(double mag)
Compute flux in Janskys from AB magnitude.
Definition: Calib.h:66
T push_back(T... args)
A class representing an angle.
Definition: Angle.h:102
A base class for image defects.
Definition: cameraGeom.dox:3
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:63
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:134
A polymorphic functor base class for generating record IDs for a table.
Definition: IdFactory.h:21
T insert(T... args)
T size(T... args)
STL class.
#define LSST_EXCEPT(type,...)
Create an exception with a given type and message and optionally other arguments (dependent on the ty...
Definition: Exception.h:46
STL class.
A class used as a handle to a particular field in a table.
Definition: fwd.h:45
T cbegin(T... args)
int id
Definition: CR.cc:155
Basic LSST definitions.
#define PTR(...)
Definition: base.h:41
Record class that must contain a unique ID field and a celestial coordinate field.
Definition: Simple.h:48
constexpr double asDegrees() const noexcept
Return an Angle&#39;s value in degrees.
Definition: Angle.h:144
This is the base class for spherical coordinates.
Definition: Coord.h:63
SortedCatalogT< SimpleRecord > SimpleCatalog
Definition: fwd.h:79
A class to handle Icrs coordinates (inherits from Coord)
Definition: Coord.h:291
int col
Definition: CR.cc:156
static std::shared_ptr< SimpleTable > make(Schema const &schema, std::shared_ptr< IdFactory > const &idFactory)
Construct a new table.
T reserve(T... args)