SourceXtractorPlusPlus  0.19
SourceXtractor++, the next generation SExtractor
WCS.cpp
Go to the documentation of this file.
1 
18 /*
19  * WCS.cpp
20  *
21  * Created on: Nov 17, 2016
22  * Author: mschefer
23  */
24 
26 
27 #include <boost/algorithm/string/trim.hpp>
28 #include <fitsio.h>
29 #include <mutex>
30 #include <wcslib/dis.h>
31 #include <wcslib/wcs.h>
32 #include <wcslib/wcsfix.h>
33 #include <wcslib/wcshdr.h>
34 #include <wcslib/wcsprintf.h>
35 
37 #include "ElementsKernel/Logging.h"
38 
39 namespace SourceXtractor {
40 
41 static auto logger = Elements::Logging::getLogger("WCS");
42 
43 decltype(&wcssub) safe_wcssub = &wcssub;
44 
48 static void wcsRaiseOnParseError(int ret_code) {
49  switch (ret_code) {
50  case WCSHDRERR_SUCCESS:
51  break;
52  case WCSHDRERR_MEMORY:
53  throw Elements::Exception() << "wcslib failed to allocate memory";
54  case WCSHDRERR_PARSER:
55  throw Elements::Exception() << "wcslib failed to parse the FITS headers";
56  default:
57  throw Elements::Exception() << "Unexpected error when parsing the FITS WCS header: "
58  << ret_code;
59  }
60 }
61 
62 static void wcsLogErr(wcserr *err) {
63  if (err) {
64  logger.error() << err->file << ":" << err->line_no << " " << err->function;
65  logger.error() << err->msg;
66  }
67 }
68 
72 static void wcsRaiseOnTransformError(wcsprm *wcs, int ret_code) {
73  if (ret_code != WCSERR_SUCCESS) {
74  wcsLogErr(wcs->err);
75  wcsLogErr(wcs->lin.err);
76  if (wcs->lin.dispre) {
77  wcsLogErr(wcs->lin.dispre->err);
78  }
79  if (wcs->lin.disseq) {
80  wcsLogErr(wcs->lin.disseq->err);
81  }
82  throw InvalidCoordinatesException() << wcs_errmsg[ret_code];
83  }
84 }
85 
89 static std::set<std::string> wcsExtractKeywords(const char *header, int number_of_records) {
90  std::set<std::string> result;
91  for (const char *p = header; *p != '\0' && number_of_records; --number_of_records, p += 80) {
92  size_t len = strcspn(p, "= ");
93  result.insert(std::string(p, len));
94  }
95  return result;
96 }
97 
101 static void wcsCheckHeaders(const wcsprm *wcs, const char *headers_str, int number_of_records) {
102  auto headers = wcsExtractKeywords(headers_str, number_of_records);
103 
104  // SIP present, but not specified in CTYPE
105  // See https://github.com/astrorama/SourceXtractorPlusPlus/issues/254#issuecomment-765235869
106  if (wcs->lin.dispre) {
107  bool sip_used = false, sip_specified = false;
108  for (int i = 0; i < wcs->naxis; ++i) {
109  sip_used |= (strncmp(wcs->lin.dispre->dtype[i], "SIP", 3) == 0);
110  size_t ctype_len = strlen(wcs->ctype[i]);
111  sip_specified |= (strncmp(wcs->ctype[i] + ctype_len - 4, "-SIP", 4) == 0);
112  }
113  if (sip_used && !sip_specified) {
114  logger.warn() << "SIP coefficients present, but CTYPE has not the '-SIP' suffix";
115  logger.warn() << "SIP distortion will be applied, but this may not be desired";
116  logger.warn() << "To suppress this warning, explicitly add the '-SIP' suffix to the CTYPE,";
117  logger.warn() << "or remove the SIP distortion coefficients";
118  }
119  }
120 }
121 
125 static void wcsReportWarnings(const char *err_buffer) {
126  if (err_buffer[0]) {
127  logger.warn() << "WCS generated some errors in strict mode. This may be OK.";
128  logger.warn() << "Will run in relaxed mode.";
129  const char *eol;
130  do {
131  eol = strchr(err_buffer, '\n');
132  if (eol) {
133  logger.warn() << std::string(err_buffer, eol - err_buffer);
134  err_buffer = eol + 1;
135  }
136  else {
137  logger.warn() << std::string(err_buffer);
138  }
139  } while (eol);
140  }
141 }
142 
148 static int wrapped_wcssub(int alloc, const struct wcsprm* wcssrc, int* nsub, int axes[], struct wcsprm* wcsdst) {
149  static std::mutex cpy_mutex;
151 
152  return wcssub(alloc, wcssrc, nsub, axes, wcsdst);
153 }
154 
155 WCS::WCS(const FitsImageSource& fits_image_source) : m_wcs(nullptr, nullptr) {
156  int number_of_records = 0;
157  auto fits_headers = fits_image_source.getFitsHeaders(number_of_records);
158 
159  init(&(*fits_headers)[0], number_of_records);
160 }
161 
162 WCS::WCS(const WCS& original) : m_wcs(nullptr, nullptr) {
163 
164  //FIXME Horrible hack: I couldn't figure out how to properly do a deep copy wcsprm so instead
165  // of making a copy, I use the ascii headers output from the original to recreate a new one
166 
167  int number_of_records;
168  char *raw_header;
169 
170  if (wcshdo(WCSHDO_none, original.m_wcs.get(), &number_of_records, &raw_header) != 0) {
171  throw Elements::Exception() << "Failed to get the FITS headers for the WCS coordinate system when copying WCS";
172  }
173 
174  init(raw_header, number_of_records);
175 
176  free(raw_header);
177 }
178 
179 
180 void WCS::init(char* headers, int number_of_records) {
181  wcserr_enable(1);
182 
183  int nreject = 0, nwcs = 0, nreject_strict = 0;
184  wcsprm* wcs;
185 
186  // Write warnings to a buffer
187  wcsprintf_set(nullptr);
188 
189  // Do a first pass, in strict mode, and ignore the result.
190  // Log the reported errors as warnings
191  int ret = wcspih(headers, number_of_records, WCSHDR_strict, 2, &nreject_strict, &nwcs, &wcs);
193  wcsReportWarnings(wcsprintf_buf());
194 
195  // Do a second pass, in relaxed mode. We use the result.
196  ret = wcspih(headers, number_of_records, WCSHDR_all, 0, &nreject, &nwcs, &wcs);
198  wcsset(wcs);
199 
200  // There are some things worth reporting about which WCS will not necessarily complain
201  wcsCheckHeaders(wcs, headers, number_of_records);
202 
203  m_wcs = decltype(m_wcs)(wcs, [nwcs](wcsprm* ptr) {
204  int nwcs_copy = nwcs;
205  wcsfree(ptr);
206  wcsvfree(&nwcs_copy, &ptr);
207  });
208 
209  int wcsver[3];
210  wcslib_version(wcsver);
211  if (wcsver[0] < 5 || (wcsver[0] == 5 && wcsver[1] < 18)) {
212  logger.info() << "wcslib " << wcsver[0] << "." << wcsver[1]
213  << " is not fully thread safe, using wrapped lincpy call!";
215  }
216 }
217 
218 
220 }
221 
223  // wcsprm is in/out
224  wcsprm wcs_copy;
225  wcs_copy.flag = -1;
226  safe_wcssub(true, m_wcs.get(), nullptr, nullptr, &wcs_copy);
227 
228  // +1 as fits standard coordinates start at 1
229  double pc_array[2] {image_coordinate.m_x + 1, image_coordinate.m_y + 1};
230 
231  double ic_array[2] {0, 0};
232  double wc_array[2] {0, 0};
233  double phi, theta;
234 
235  int status = 0;
236  int ret_val = wcsp2s(&wcs_copy, 1, 1, pc_array, ic_array, &phi, &theta, wc_array, &status);
237  wcsRaiseOnTransformError(&wcs_copy, ret_val);
238  wcsfree(&wcs_copy);
239 
240  return WorldCoordinate(wc_array[0], wc_array[1]);
241 }
242 
244  // wcsprm is in/out
245  wcsprm wcs_copy;
246  wcs_copy.flag = -1;
247  safe_wcssub(true, m_wcs.get(), nullptr, nullptr, &wcs_copy);
248 
249  double pc_array[2] {0, 0};
250  double ic_array[2] {0, 0};
251  double wc_array[2] {world_coordinate.m_alpha, world_coordinate.m_delta};
252  double phi, theta;
253 
254  int status = 0;
255  int ret_val = wcss2p(&wcs_copy, 1, 1, wc_array, &phi, &theta, ic_array, pc_array, &status);
256  wcsRaiseOnTransformError(&wcs_copy, ret_val);
257  wcsfree(&wcs_copy);
258 
259  return ImageCoordinate(pc_array[0] - 1, pc_array[1] - 1); // -1 as fits standard coordinates start at 1
260 }
261 
263  int nkeyrec;
264  char *raw_header;
265 
266  if (wcshdo(WCSHDO_none, m_wcs.get(), &nkeyrec, &raw_header) != 0) {
267  throw Elements::Exception() << "Failed to get the FITS headers for the WCS coordinate system";
268  }
269 
271  for (int i = 0; i < nkeyrec; ++i) {
272  char *hptr = &raw_header[80 * i];
273  std::string key(hptr, hptr + 8);
274  boost::trim(key);
275  std::string value(hptr + 9, hptr + 72);
276  boost::trim(value);
277  if (!key.empty()) {
278  headers.emplace(std::make_pair(key, value));
279  }
280  }
281 
282  free(raw_header);
283  return headers;
284 }
285 
287  m_wcs->crpix[0] -= pc.m_x;
288  m_wcs->crpix[1] -= pc.m_y;
289 }
290 
291 
292 }
static Logging getLogger(const std::string &name="")
std::unique_ptr< std::vector< char > > getFitsHeaders(int &number_of_records) const
WCS(const FitsImageSource &fits_image_source)
Definition: WCS.cpp:155
void addOffset(PixelCoordinate pc)
Definition: WCS.cpp:286
std::unique_ptr< wcsprm, std::function< void(wcsprm *)> > m_wcs
Definition: WCS.h:54
void init(char *headers, int number_of_records)
Definition: WCS.cpp:180
std::map< std::string, std::string > getFitsHeaders() const override
Definition: WCS.cpp:262
WorldCoordinate imageToWorld(ImageCoordinate image_coordinate) const override
Definition: WCS.cpp:222
ImageCoordinate worldToImage(WorldCoordinate world_coordinate) const override
Definition: WCS.cpp:243
virtual ~WCS()
Definition: WCS.cpp:219
T emplace(T... args)
T empty(T... args)
T free(T... args)
T get(T... args)
T insert(T... args)
T lock(T... args)
T make_pair(T... args)
constexpr double pc
static void wcsRaiseOnTransformError(wcsprm *wcs, int ret_code)
Definition: WCS.cpp:72
static int wrapped_wcssub(int alloc, const struct wcsprm *wcssrc, int *nsub, int axes[], struct wcsprm *wcsdst)
Definition: WCS.cpp:148
static void wcsLogErr(wcserr *err)
Definition: WCS.cpp:62
static void wcsCheckHeaders(const wcsprm *wcs, const char *headers_str, int number_of_records)
Definition: WCS.cpp:101
static std::set< std::string > wcsExtractKeywords(const char *header, int number_of_records)
Definition: WCS.cpp:89
decltype(&wcssub) safe_wcssub
Definition: WCS.cpp:43
static auto logger
Definition: WCS.cpp:41
static void wcsRaiseOnParseError(int ret_code)
Definition: WCS.cpp:48
static void wcsReportWarnings(const char *err_buffer)
Definition: WCS.cpp:125
T strchr(T... args)
T strcspn(T... args)
T strlen(T... args)
T strncmp(T... args)
A pixel coordinate made of two integers m_x and m_y.