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