LSSTApplications  18.1.0
LSSTDataManagementBasePackage
simpleFits.cc
Go to the documentation of this file.
1 /*
2  * LSST Data Management System
3  * Copyright 2008, 2009, 2010 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 /*
24  * Write a FITS image to a file descriptor; useful for talking to DS9
25  *
26  * This version knows about LSST data structures
27  */
28 #ifndef DOXYGEN // Doxygen doesn't like includes inside namespaces
29 namespace posix { // here so no-one includes them first outside namespace posix {}
30 #include <unistd.h>
31 #include <fcntl.h>
32 }
33 #endif // !DOXYGEN
34 using namespace posix;
35 #include <cstdint>
36 #include <cstdlib>
37 #include <cstdio>
38 #include <cstring>
39 #include <cassert>
40 #include <cctype>
41 
42 #include "lsst/pex/exceptions.h"
43 #include "boost/any.hpp"
44 #include "lsst/afw/fits.h"
45 
46 #include "simpleFits.h"
47 
48 namespace image = lsst::afw::image;
50 
51 #define FITS_SIZE 2880
52 
54 class Card {
55 public:
56  Card(const std::string &name, bool val, const char *commnt = "")
57  : keyword(name), value(val), comment(commnt) {}
58  Card(const std::string &name, int val, const char *commnt = "")
59  : keyword(name), value(val), comment(commnt) {}
60  Card(const std::string &name, double val, const char *commnt = "")
61  : keyword(name), value(val), comment(commnt) {}
62  Card(const std::string &name, float val, const char *commnt = "")
63  : keyword(name), value(val), comment(commnt) {}
64  Card(const std::string &name, const std::string &val, const char *commnt = "")
65  : keyword(name), value(val), comment(commnt) {}
66  Card(const std::string &name, const char *val, const char *commnt = "")
67  : keyword(name), value(std::string(val)), comment(commnt) {}
68 
69  ~Card() {}
70 
71  int write(int fd, int ncard, char *record) const;
72 
73  std::string keyword;
74  boost::any value;
75  std::string comment;
76 };
77 
78 /*
79  * Write a Card
80  */
81 int Card::write(int fd, int ncard, char *record) const {
82  char *card = &record[80 * ncard];
83 
84  if (value.type() == typeid(std::string)) {
85  const char *str = boost::any_cast<std::string>(value).c_str();
86  if (keyword == "" || keyword == "COMMENT" || keyword == "END" || keyword == "HISTORY") {
87  sprintf(card, "%-8.8s%-72s", keyword.c_str(), str);
88  } else {
89  sprintf(card, "%-8.8s= '%s' %c%-*s", keyword.c_str(), str, (comment == "" ? ' ' : '/'),
90  (int)(80 - 14 - strlen(str)), comment.c_str());
91  }
92  } else {
93  sprintf(card, "%-8.8s= ", keyword.c_str());
94  card += 10;
95  if (value.type() == typeid(bool)) {
96  sprintf(card, "%20s", boost::any_cast<bool>(value) ? "T" : "F");
97  } else if (value.type() == typeid(int)) {
98  sprintf(card, "%20d", boost::any_cast<int>(value));
99  } else if (value.type() == typeid(double)) {
100  sprintf(card, "%20.10f", boost::any_cast<double>(value));
101  } else if (value.type() == typeid(float)) {
102  sprintf(card, "%20.7f", boost::any_cast<float>(value));
103  }
104  card += 20;
105  sprintf(card, " %c%-48s", (comment == "" ? ' ' : '/'), comment.c_str());
106  }
107  /*
108  * Write record if full
109  */
110  if (++ncard == 36) {
111  if (posix::write(fd, record, FITS_SIZE) != FITS_SIZE) {
112  throw LSST_EXCEPT(lsst::pex::exceptions::RuntimeError, "Cannot write header record");
113  }
114  ncard = 0;
115  }
116 
117  return ncard;
118 }
120 
121 /*
122  * Utilities
123  *
124  * Flip high-order bit so as to write unsigned short to FITS. Grrr.
125  */
126 namespace {
127 void flip_high_bit(char *arr, // array that needs bits swapped
128  const int n) { // number of bytes in arr
129  if (n % 2 != 0) {
131  (boost::format("Attempt to bit flip odd number of bytes: %d") % n).str());
132  }
133 
134  unsigned short *uarr = reinterpret_cast<unsigned short *>(arr);
135  for (unsigned short *end = uarr + n / 2; uarr < end; ++uarr) {
136  *uarr ^= 0x8000;
137  }
138 }
139 }
140 
141 /*
142  * Byte swap ABABAB -> BABABAB in place
143  */
144 namespace {
145 void swap_2(char *arr, // array to swap
146  const int n) { // number of bytes
147  if (n % 2 != 0) {
149  (boost::format("Attempt to byte swap odd number of bytes: %d") % n).str());
150  }
151 
152  for (char *end = arr + n; arr < end; arr += 2) {
153  char t = arr[0];
154  arr[0] = arr[1];
155  arr[1] = t;
156  }
157 }
158 /*
159  * Byte swap ABCDABCD -> DCBADCBA in place (e.g. sun <--> vax)
160  */
161 void swap_4(char *arr, // array to swap
162  const int n) { // number of bytes
163  if (n % 4 != 0) {
165  (boost::format("Attempt to byte swap non-multiple of 4 bytes: %d") % n).str());
166  }
167 
168  for (char *end = arr + n; arr < end; arr += 4) {
169  char t = arr[0];
170  arr[0] = arr[3];
171  arr[3] = t;
172  t = arr[1];
173  arr[1] = arr[2];
174  arr[2] = t;
175  }
176 }
177 
178 /*
179  * Byte swap ABCDEFGH -> HGFEDCBA in place (e.g. sun <--> vax)
180  */
181 void swap_8(char *arr, // array to swap
182  const int n) { // number of bytes
183  if (n % 8 != 0) {
185  (boost::format("Attempt to byte swap non-multiple of 8 bytes: %d") % n).str());
186  }
187 
188  for (char *end = arr + n; arr < end; arr += 8) {
189  char t = arr[0];
190  arr[0] = arr[7];
191  arr[7] = t;
192  t = arr[1];
193  arr[1] = arr[6];
194  arr[6] = t;
195  t = arr[2];
196  arr[2] = arr[5];
197  arr[5] = t;
198  t = arr[3];
199  arr[3] = arr[4];
200  arr[4] = t;
201  }
202 }
203 
204 int write_fits_hdr(int fd, int bitpix, int naxis, int *naxes, std::list<Card> &cards, /* extra header cards */
205  int primary) /* is this the primary HDU? */
206 {
207  int i;
208  char record[FITS_SIZE + 1]; /* write buffer */
209 
210  int ncard = 0;
211  if (primary) {
212  Card card("SIMPLE", true);
213  ncard = card.write(fd, ncard, record);
214  } else {
215  Card card("XTENSION", "IMAGE");
216  ncard = card.write(fd, ncard, record);
217  }
218 
219  {
220  Card card("BITPIX", bitpix);
221  ncard = card.write(fd, ncard, record);
222  }
223  {
224  Card card("NAXIS", naxis);
225  ncard = card.write(fd, ncard, record);
226  }
227  for (i = 0; i < naxis; i++) {
228  char key[] = "NAXIS.";
229  sprintf(key, "NAXIS%d", i + 1);
230  Card card(key, naxes[i]);
231  ncard = card.write(fd, ncard, record);
232  }
233  if (primary) {
234  Card card("EXTEND", true, "There may be extensions");
235  ncard = card.write(fd, ncard, record);
236  }
237  /*
238  * Write extra header cards
239  */
240  for (std::list<Card>::const_iterator card = cards.begin(); card != cards.end(); card++) {
241  ncard = card->write(fd, ncard, record);
242  }
243 
244  {
245  Card card("END", "");
246  ncard = card.write(fd, ncard, record);
247  }
248  while (ncard != 0) {
249  Card card("", "");
250  ncard = card.write(fd, ncard, record);
251  }
252 
253  return 0;
254 }
255 
256 /*
257  * Pad out to a FITS record boundary
258  */
259 void pad_to_fits_record(int fd, // output file descriptor
260  int npixel, // number of pixels already written to HDU
261  int bitpix // bitpix for this datatype
262  ) {
263  const int bytes_per_pixel = (bitpix > 0 ? bitpix : -bitpix) / 8;
264  int nbyte = npixel * bytes_per_pixel;
265 
266  if (nbyte % FITS_SIZE != 0) {
267  char record[FITS_SIZE + 1]; /* write buffer */
268 
269  nbyte = FITS_SIZE - nbyte % FITS_SIZE;
270  memset(record, ' ', nbyte);
271  if (write(fd, record, nbyte) != nbyte) {
273  "error padding file to multiple of fits block size");
274  }
275  }
276 }
277 
278 int write_fits_data(int fd, int bitpix, char *begin, char *end) {
279  const int bytes_per_pixel = (bitpix > 0 ? bitpix : -bitpix) / 8;
280  int swap_bytes = 0; // the default
281 #if defined(LSST_LITTLE_ENDIAN) // we'll need to byte swap FITS
282  if (bytes_per_pixel > 1) {
283  swap_bytes = 1;
284  }
285 #endif
286 
287  char *buff = NULL; // I/O buffer
288  bool allocated = false; // do I need to free it?
289  if (swap_bytes || bitpix == 16) {
290  buff = new char[FITS_SIZE * bytes_per_pixel];
291  allocated = true;
292  }
293 
294  int nbyte = end - begin;
295  int nwrite = (nbyte > FITS_SIZE) ? FITS_SIZE : nbyte;
296  for (char *ptr = begin; ptr != end; nbyte -= nwrite, ptr += nwrite) {
297  if (end - ptr < nwrite) {
298  nwrite = end - ptr;
299  }
300 
301  if (swap_bytes) {
302  memcpy(buff, ptr, nwrite);
303  if (bitpix == 16) { // flip high-order bit
304  flip_high_bit(buff, nwrite);
305  }
306 
307  if (bytes_per_pixel == 2) {
308  swap_2(buff, nwrite);
309  } else if (bytes_per_pixel == 4) {
310  swap_4(buff, nwrite);
311  } else if (bytes_per_pixel == 8) {
312  swap_8(buff, nwrite);
313  } else {
314  fprintf(stderr, "You cannot get here\n");
315  abort();
316  }
317  } else {
318  if (bitpix == 16) { // flip high-order bit
319  memcpy(buff, ptr, nwrite);
320  flip_high_bit(buff, nwrite);
321  } else {
322  buff = ptr;
323  }
324  }
325 
326  if (write(fd, buff, nwrite) != nwrite) {
327  perror("Error writing image: ");
328  break;
329  }
330  }
331 
332  if (allocated) {
333  delete buff;
334  }
335 
336 
337  return (nbyte == 0 ? 0 : -1);
338 }
339 
340 void
341 addWcs(std::string const & wcsName, std::list<Card> & cards, int x0=0.0, int y0=0.0)
342 {
343  cards.push_back(Card(str(boost::format("CRVAL1%s") % wcsName),
344  x0, "(output) Column pixel of Reference Pixel"));
345  cards.push_back(Card(str(boost::format("CRVAL2%s") % wcsName),
346  y0, "(output) Row pixel of Reference Pixel"));
347  cards.push_back(Card(str(boost::format("CRPIX1%s") % wcsName), 1.0,
348  "Column Pixel Coordinate of Reference"));
349  cards.push_back(Card(str(boost::format("CRPIX2%s") % wcsName), 1.0,
350  "Row Pixel Coordinate of Reference"));
351  cards.push_back(Card(str(boost::format("CTYPE1%s") % wcsName), "LINEAR", "Type of projection"));
352  cards.push_back(Card(str(boost::format("CTYPE1%s") % wcsName), "LINEAR", "Type of projection"));
353  cards.push_back(Card(str(boost::format("CUNIT1%s") % wcsName), "PIXEL", "Column unit"));
354  cards.push_back(Card(str(boost::format("CUNIT2%s") % wcsName), "PIXEL", "Row unit"));
355 }
356 }
357 
358 namespace lsst {
359 namespace afw {
360 namespace display {
361 
362 template <typename ImageT>
363 void writeBasicFits(int fd, // file descriptor to write to
364  ImageT const &data, // The data to write
365  geom::SkyWcs const *Wcs, // which Wcs to use for pixel
366  char const *title // title to write to DS9
367  ) {
368  /*
369  * Allocate cards for FITS headers
370  */
371  std::list<Card> cards;
372  /*
373  * What sort if image is it?
374  */
375  int bitpix = lsst::afw::fits::getBitPix<typename ImageT::Pixel>();
376  if (bitpix == 20) { // cfitsio for "Unsigned short"
377  cards.push_back(Card("BZERO", 32768.0, ""));
378  cards.push_back(Card("BSCALE", 1.0, ""));
379  bitpix = 16;
380  } else if (bitpix == 0) {
381  throw LSST_EXCEPT(lsst::pex::exceptions::RuntimeError, "Unsupported image type");
382  }
383  /*
384  * Generate WcsA, pixel coordinates, allowing for X0 and Y0
385  */
386  addWcs("A", cards, data.getX0(), data.getY0());
387  /*
388  * Now WcsB, so that pixel (0,0) is correctly labelled (but ignoring XY0)
389  */
390  addWcs("B", cards);
391 
392  if (title) {
393  cards.push_back(Card("OBJECT", title, "Image being displayed"));
394  }
395  /*
396  * Was there something else?
397  */
398  if (Wcs == NULL) {
399  addWcs("", cards); // works around a ds9 bug that WCSA/B is ignored if no Wcs is present
400  } else {
401  typedef std::vector<std::string> NameList;
402 
403  auto shift = lsst::geom::Extent2D(-data.getX0(), -data.getY0());
404  auto newWcs = Wcs->copyAtShiftedPixelOrigin(shift);
405 
406  std::shared_ptr<lsst::daf::base::PropertySet> metadata = newWcs->getFitsMetadata();
407 
408  NameList paramNames = metadata->paramNames();
409 
410  for (NameList::const_iterator i = paramNames.begin(), end = paramNames.end(); i != end; ++i) {
411  if (*i == "SIMPLE" || *i == "BITPIX" || *i == "NAXIS" || *i == "NAXIS1" || *i == "NAXIS2" ||
412  *i == "XTENSION" || *i == "PCOUNT" || *i == "GCOUNT") {
413  continue;
414  }
415  std::type_info const &type = metadata->typeOf(*i);
416  if (type == typeid(bool)) {
417  cards.push_back(Card(*i, metadata->get<bool>(*i)));
418  } else if (type == typeid(int)) {
419  cards.push_back(Card(*i, metadata->get<int>(*i)));
420  } else if (type == typeid(float)) {
421  cards.push_back(Card(*i, metadata->get<float>(*i)));
422  } else if (type == typeid(double)) {
423  cards.push_back(Card(*i, metadata->get<double>(*i)));
424  } else {
425  cards.push_back(Card(*i, metadata->get<std::string>(*i)));
426  }
427  }
428  }
429  /*
430  * Basic FITS stuff
431  */
432  const int naxis = 2; // == NAXIS
433  int naxes[naxis]; /* values of NAXIS1 etc */
434  naxes[0] = data.getWidth();
435  naxes[1] = data.getHeight();
436 
437  write_fits_hdr(fd, bitpix, naxis, naxes, cards, 1);
438  for (int y = 0; y != data.getHeight(); ++y) {
439  if (write_fits_data(fd, bitpix, (char *)(data.row_begin(y)), (char *)(data.row_end(y))) < 0) {
441  (boost::format("Error writing data for row %d") % y).str());
442  }
443  }
444 
445  pad_to_fits_record(fd, data.getWidth() * data.getHeight(), bitpix);
446 }
447 
448 template <typename ImageT>
449 void writeBasicFits(std::string const &filename, // file to write, or "| cmd"
450  ImageT const &data, // The data to write
451  geom::SkyWcs const *Wcs, // which Wcs to use for pixel
452  char const *title // title to write to DS9
453  ) {
454  int fd;
455  if ((filename.c_str())[0] == '|') { // a command
456  const char *cmd = filename.c_str() + 1;
457  while (isspace(*cmd)) {
458  cmd++;
459  }
460 
461  fd = fileno(popen(cmd, "w"));
462  } else {
463  fd = creat(filename.c_str(), 777);
464  }
465 
466  if (fd < 0) {
468  (boost::format("Cannot open \"%s\"") % filename).str());
469  }
470 
471  try {
472  writeBasicFits(fd, data, Wcs, title);
474  (void)close(fd);
475  throw;
476  }
477 
478  (void)close(fd);
479 }
480 
482 #define INSTANTIATE(IMAGET) \
483  template void writeBasicFits(int, IMAGET const &, geom::SkyWcs const *, char const *); \
484  template void writeBasicFits(std::string const &, IMAGET const &, geom::SkyWcs const *, char const *)
485 
486 #define INSTANTIATE_IMAGE(T) INSTANTIATE(lsst::afw::image::Image<T>)
487 #define INSTANTIATE_MASK(T) INSTANTIATE(lsst::afw::image::Mask<T>)
488 
490 INSTANTIATE_IMAGE(int);
491 INSTANTIATE_IMAGE(float);
492 INSTANTIATE_IMAGE(double);
494 
495 INSTANTIATE_MASK(std::uint16_t);
496 INSTANTIATE_MASK(image::MaskPixel);
498 }
499 }
500 }
uint64_t * ptr
Definition: RangeSet.cc:88
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Definition: SkyWcs.h:117
int y
Definition: SpanSet.cc:49
ImageT val
Definition: CR.cc:146
T end(T... args)
Provides consistent interface for LSST exceptions.
Definition: Exception.h:107
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.
STL class.
T push_back(T... args)
void writeBasicFits(std::string const &filename, ImageT const &data, geom::SkyWcs const *Wcs, char const *title)
Definition: simpleFits.cc:449
A base class for image defects.
char * data
Definition: BaseRecord.cc:62
table::Key< int > type
Definition: Detector.cc:167
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168
STL class.
std::shared_ptr< SkyWcs > copyAtShiftedPixelOrigin(lsst::geom::Extent2D const &shift) const
Return a copy of this SkyWcs with the pixel origin shifted by the specified amount.
Definition: SkyWcs.cc:212
T get(T... args)
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition: Exception.h:48
T begin(T... args)
Key< U > key
Definition: Schema.cc:281
Class for storing generic metadata.
Definition: PropertySet.h:68
T c_str(T... args)
#define FITS_SIZE
Definition: simpleFits.cc:51
afw::table::Key< afw::table::Array< ImagePixelT > > image
Extent< double, 2 > Extent2D
Definition: Extent.h:400
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
int end
Reports errors that are due to events beyond the control of the program.
Definition: Runtime.h:104
#define INSTANTIATE_IMAGE(IMAGE)
Definition: ImagePca.cc:125