PURIFY
Next-generation radio interferometric imaging
pfitsio.h
Go to the documentation of this file.
1 #ifndef PURIFY_PFITSIO_H
2 #define PURIFY_PFITSIO_H
3 #include "purify/config.h"
4 #include "purify/types.h"
5 #include "purify/logging.h"
6 #include "purify/uvw_utilities.h"
7 
8 #include <fitsio.h>
9 #include <string>
10 
11 namespace purify::pfitsio {
12 
13 struct header_params {
14  // structure containing parameters for fits header
15  std::string fits_name = "output_image.fits";
16  t_real mean_frequency = 0; // in MHz
17  t_real cell_x = 1; // in arcseconds
18  t_real cell_y = 1; // in arcseconds
19  t_real ra = 0; // in radians, converted to decimal degrees before write
20  t_real dec = 0; // in radians, converted to decimal degrees before write
21  t_int pix_ref_x = 0;
22  t_int pix_ref_y = 0;
23  std::string pix_units = "Jy/BEAM";
24  t_real channels_total = 1;
25  t_real channel_width = 8; // in MHz
26  t_real polarisation = 1;
27  t_int niters = 0; // number of iterations
28  bool hasconverged = false; // stating if model has converged
29  t_real relative_variation = 0;
31  t_real epsilon = 0;
33  header_params(const std::string &fits_name_, const std::string &pix_units_,
34  const t_real channels_total_, const t_real ra_, const t_real dec_, const stokes pol,
35  const t_real cellx_, const t_real celly_, const t_real mean_frequency_,
36  const t_real channel_width_, const t_uint niters_, const bool hasconverged_,
37  const t_real relative_variation_, const t_real residual_convergence_,
38  const t_real epsilon_)
39  : fits_name(fits_name_),
40  pix_units(pix_units_),
41  channels_total(channels_total_),
42  mean_frequency(mean_frequency_),
43  channel_width(channel_width_),
44  cell_x(cellx_),
45  cell_y(celly_),
46  ra(ra_),
47  dec(dec_),
48  niters(niters_),
49  hasconverged(hasconverged_),
50  epsilon(epsilon_),
51  relative_variation(relative_variation_),
52  residual_convergence(residual_convergence_) {
53  try {
54  this->polarisation = stokes_int.at(pol);
55  } catch (std::out_of_range &e) {
56  PURIFY_LOW_LOG("Polarisation type not recognised by FITS, assuming Stokes I.");
57  this->polarisation = stokes_int.at(stokes::I);
58  };
59  };
62 #define PURIFY_MACRO(VAR, H2) (this->VAR == H2.VAR)
63  bool operator==(const header_params &h2) const {
64  return PURIFY_MACRO(mean_frequency, h2) and PURIFY_MACRO(cell_x, h2) and
65  PURIFY_MACRO(cell_y, h2) and PURIFY_MACRO(ra, h2) and PURIFY_MACRO(dec, h2) and
71  PURIFY_MACRO(epsilon, h2);
72  }
73 #undef PURIFY_MACRO
74  bool operator!=(const header_params &h2) const { return not(*this == h2); }
75 };
76 
78 void write_key(fitsfile *fptr, const std::string &key, const std::string &value,
79  const std::string &comment, int *status);
80 
81 void write_key(fitsfile *fptr, const std::string &key, const char *value,
82  const std::string &comment, int *status);
83 
85 void write_history(fitsfile *fptr, const std::string &context, const std::string &history,
86  int *status);
87 template <class T>
88 typename std::enable_if<std::is_scalar<T>::value, void>::type write_history(
89  fitsfile *fptr, const std::string &context, const T &history, int *status) {
90  T value = history;
91  write_history(fptr, context, std::to_string(value), status);
92 }
93 
94 template <class T>
95 typename std::enable_if<std::is_scalar<T>::value, void>::type write_key(fitsfile *fptr,
96  const std::string &key,
97  const T &value,
98  const std::string &comment,
99  int *status) {
100  int datatype = 0;
101  if (std::is_same<T, double>::value)
102  datatype = TDOUBLE;
103  else if (std::is_same<T, float>::value)
104  datatype = TFLOAT;
105  else if (std::is_same<T, int>::value)
106  datatype = TINT;
107  else if (std::is_same<T, t_int>::value)
108  datatype = TINT;
109  else if (std::is_same<T, bool>::value)
110  datatype = TLOGICAL;
111  else
112  throw std::runtime_error("Key type of value not supported by PURIFY template for " + key);
113 
114  T entry = value;
115  if (fits_write_key(fptr, datatype, const_cast<char *>(key.c_str()),
116  reinterpret_cast<void *>(&entry), const_cast<char *>(comment.c_str()), status))
117  throw std::runtime_error("Problem writing key in fits file: " + key);
118 }
119 
121 template <typename T>
122 T read_key(fitsfile *fptr, const std::string &key, int *status) {
123  T value;
124  char comment[FLEN_COMMENT];
125  if constexpr (std::is_same_v<T, std::string>) {
126  if (fits_read_key(fptr, TSTRING, key.data(), value.data(), comment, status)) {
127  throw std::runtime_error("Error reading value from key " + key);
128  }
129  } else {
130  int datatype = 0;
131  if (std::is_same<T, double>::value)
132  datatype = TDOUBLE;
133  else if (std::is_same_v<T, float>)
134  datatype = TFLOAT;
135  else if (std::is_same_v<T, int>)
136  datatype = TINT;
137  else if (std::is_same_v<T, bool>)
138  datatype = TLOGICAL;
139  else {
140  throw std::runtime_error("Key type of value not supported by PURIFY template for " + key);
141  }
142 
143  if (fits_read_key(fptr, datatype, key.data(), &value, comment, status)) {
144  throw std::runtime_error("Error reading value from key " + key);
145  }
146  }
147  return value;
148 }
149 
151 void write2d(const Image<t_real> &image, const pfitsio::header_params &header,
152  const bool &overwrite = true);
153 
155 void write2d(const Image<t_real> &image, const std::string &fits_name,
156  const std::string &pix_units = "Jy/Beam", const bool &overwrite = true);
157 
158 template <typename DERIVED>
159 void write2d(const Eigen::EigenBase<DERIVED> &input, int nx, int ny, const std::string &fits_name,
160  const std::string &pix_units = "Jy/Beam", const bool &overwrite = true) {
161  Image<t_real> const data = input.derived().real().template cast<t_real>();
162  write2d(Image<t_real>::Map(data.data(), nx, ny), fits_name, pix_units, overwrite);
163 }
164 template <typename DERIVED>
165 void write2d(const Eigen::EigenBase<DERIVED> &input, int nx, int ny,
166  const pfitsio::header_params &header, const bool overwrite = true) {
167  Image<t_real> const data = input.derived().real().template cast<t_real>();
168  write2d(Image<t_real>::Map(data.data(), nx, ny), header, overwrite);
169 }
170 
172 void write3d(const std::vector<Image<t_real>> &image, const pfitsio::header_params &header,
173  const bool &overwrite = true);
174 
176 void write3d(const std::vector<Image<t_real>> &image, const std::string &fits_name,
177  const std::string &pix_units = "Jy/Beam", const bool &overwrite = true);
178 
179 template <typename DERIVED>
180 void write3d(const std::vector<Eigen::EigenBase<DERIVED>> &input, int nx, int ny,
181  const std::string &fits_name, const std::string &pix_units = "Jy/Beam",
182  const bool &overwrite = true) {
183  std::vector<Image<t_real>> images;
184  for (int i = 0; i < input.size(); i++) {
185  Image<t_real> const data = input.derived().real().template cast<t_real>();
186  images.push_back(Image<t_real>::Map(data.data(), nx, ny));
187  }
188  write3d(images, fits_name, pix_units, overwrite);
189 }
190 template <typename DERIVED>
191 void write3d(const std::vector<Eigen::EigenBase<DERIVED>> &input, int nx, int ny,
192  const pfitsio::header_params &header, const bool &overwrite = true) {
193  std::vector<Image<t_real>> images;
194  for (int i = 0; i < input.size(); i++) {
195  Image<t_real> const data = input.derived().real().template cast<t_real>();
196  images.push_back(Image<t_real>::Map(data.data(), nx, ny));
197  }
198  write3d(images, header, overwrite);
199 }
200 
201 template <typename T>
202 typename std::enable_if<std::is_same<t_real, typename T::Scalar>::value, void>::type write3d(
203  const Eigen::EigenBase<T> &image, const t_int rows, const t_int cols, const t_int chans,
204  const std::string &fits_name, const std::string &pix_units = "Jy/Beam",
205  const bool &overwrite = true) {
206  pfitsio::header_params header;
207  header.fits_name = fits_name;
208  header.pix_units = pix_units;
209  write3d<T>(image, rows, cols, chans, header, overwrite);
210 }
211 
213 template <typename T>
214 typename std::enable_if<std::is_same<t_real, typename T::Scalar>::value, void>::type write3d(
215  const Eigen::EigenBase<T> &image, const t_int rows, const t_int cols, const t_int chans,
216  const pfitsio::header_params &header, const bool &overwrite) {
217  /*
218  Writes an image to a fits file.
219  image:: image data, a 2d Image.
220  header:: structure containing header information
221  overwrite:: if true, overwrites old fits file with same name
222  */
223  if (image.size() != rows * cols * chans)
224  throw std::runtime_error("image or cube size does not match dimensions.");
225 
226  PURIFY_LOW_LOG("Writing fits file {}", header.fits_name);
227  if (overwrite == true) {
228  remove(header.fits_name.c_str());
229  }
230  fitsfile *fptr;
231  int status = 0;
232  std::vector<long> naxes = {static_cast<long>(rows), static_cast<long>(cols),
233  static_cast<long>(chans), 1};
234  std::vector<long> fpixel = {1, 1, 1, 1};
235 
236  if (fits_create_file(&fptr, header.fits_name.c_str(), &status))
237  throw std::runtime_error("Problem creating fits file:" + header.fits_name);
238  if (fits_create_img(fptr, DOUBLE_IMG, static_cast<t_int>(naxes.size()), naxes.data(), &status))
239  throw std::runtime_error("Problem creating HDU for image in fits file:" + header.fits_name);
240  if (fits_write_pix(fptr, TDOUBLE, fpixel.data(), static_cast<long>(image.size()),
241  const_cast<t_real *>(image.derived().data()), &status))
242  throw std::runtime_error("Problem writing image in fits file:" + header.fits_name);
243 
244 #define PURIFY_MACRO(KEY, VALUE, COMMENT) write_key(fptr, KEY, VALUE, COMMENT, &status);
245 
246  // Writing to fits header
247  PURIFY_MACRO("BUNIT", header.pix_units, ""); // units
248  PURIFY_MACRO("NAXIS", static_cast<t_int>(naxes.size()), ""); // number of axes
249  PURIFY_MACRO("NAXIS1", static_cast<t_int>(naxes.at(0)), "");
250  PURIFY_MACRO("NAXIS2", static_cast<t_int>(naxes.at(1)), "");
251  PURIFY_MACRO("NAXIS3", static_cast<t_int>(naxes.at(2)), "");
252  PURIFY_MACRO("NAXIS4", static_cast<t_int>(naxes.at(3)), "");
253  PURIFY_MACRO("CRPIX1", static_cast<t_int>(std::floor(naxes.at(0) / 2)) + 1, "");
254  PURIFY_MACRO("CRPIX2", static_cast<t_int>(std::floor(naxes.at(1) / 2)) + 1, "");
255  PURIFY_MACRO("CRPIX3", 1, "");
256  PURIFY_MACRO("CRPIX4", 1, "");
257  PURIFY_MACRO("CRVAL1", static_cast<t_real>(header.ra * 180. / purify::constant::pi), "");
258  PURIFY_MACRO("CRVAL2", static_cast<t_real>(header.dec * 180. / purify::constant::pi), "");
259  PURIFY_MACRO("CRVAL3", static_cast<t_real>(header.mean_frequency * 1.), "");
260  PURIFY_MACRO("CRVAL4", static_cast<t_real>(1), "");
261  PURIFY_MACRO("CTYPE1", "RA--SIN", "");
262  PURIFY_MACRO("CTYPE2", "DEC--SIN", "");
263  PURIFY_MACRO("CTYPE3", "FREQ", "");
264  PURIFY_MACRO("CTYPE4", "STOKES", "");
265  PURIFY_MACRO("CDELT1", static_cast<t_real>(-header.cell_x / 3600.), "");
266  PURIFY_MACRO("CDELT2", static_cast<t_real>(header.cell_y / 3600.), "");
267  PURIFY_MACRO("CDELT3", static_cast<t_real>(header.channel_width * 1.), "");
268  PURIFY_MACRO("CDELT4", 1., "");
269  PURIFY_MACRO("BTYPE", "intensity", "");
270  PURIFY_MACRO("EQUINOX", 2000, "");
271 
272 #undef PURIFY_MACRO
273  write_history(fptr, "SOFTWARE", "Purify", &status);
274  write_history(fptr, "PURIFY-NITERS", header.niters, &status);
275  if (header.hasconverged) {
276  write_history(fptr, "PURIFY-CONVERGED", "True", &status);
277  } else {
278  write_history(fptr, "PURIFY-CONVERGED", "False", &status);
279  }
280  write_history(fptr, "PURIFY-RELATIVEVARIATION", header.relative_variation, &status);
281  write_history(fptr, "PURIFY-RESIDUALCONVERGENCE", header.residual_convergence, &status);
282  write_history(fptr, "PURIFY-EPSILON", header.epsilon, &status);
283  if (fits_write_date(fptr, &status))
284  throw std::runtime_error("Problem writing date in fits file:" + header.fits_name);
285 
286  if (fits_close_file(fptr, &status))
287  throw std::runtime_error("Problem closing fits file:" + header.fits_name);
288 }
289 
290 template <typename T, typename = std::enable_if_t<std::is_same_v<double, typename T::Scalar>>>
291 void read3d(const std::string &fits_name, Eigen::EigenBase<T> &output, int &rows, int &cols,
292  int &channels, int &pols) {
293  /*
294  Reads in a cube from a fits file and returns the vector of images.
295  fits_name:: name of fits file
296  */
297 
298  fitsfile *fptr;
299  int status = 0;
300  PURIFY_LOW_LOG("Reading fits file {}", fits_name);
301  if (fits_open_image(&fptr, fits_name.c_str(), READONLY, &status))
302  throw std::runtime_error("Error opening image " + fits_name);
303  const int naxis = pfitsio::read_key<int>(fptr, "NAXIS", &status);
304  if (naxis < 1) throw std::runtime_error("Image contains zero axes.");
305  rows = pfitsio::read_key<int>(fptr, "NAXIS1", &status);
306  cols = (naxis > 1) ? pfitsio::read_key<int>(fptr, "NAXIS2", &status) : 1;
307  channels = (naxis > 2) ? pfitsio::read_key<int>(fptr, "NAXIS3", &status) : 1;
308  pols = (naxis > 3) ? pfitsio::read_key<int>(fptr, "NAXIS4", &status) : 1;
309  PURIFY_LOW_LOG("Axes {}", naxis);
310  std::vector<long> fpixel(naxis, 1);
311  PURIFY_LOW_LOG("Dimensions {}x{}x{}x{}", rows, cols, channels, pols);
312  if (pols > 1) throw std::runtime_error("Too many polarisations when reading " + fits_name);
313  t_real nulval = 0;
314  int anynul = 0;
315  output.derived() = Vector<typename T::Scalar>::Zero(rows * cols * channels * pols);
316  if (fits_read_pix(fptr, TDOUBLE, fpixel.data(), static_cast<long>(output.size()), &nulval,
317  output.derived().data(), &anynul, &status))
318  throw std::runtime_error("Error reading data from image " + fits_name);
319  if (anynul) PURIFY_LOW_LOG("There are bad values when reading " + fits_name);
320  if (fits_close_file(fptr, &status))
321  throw std::runtime_error("Problem closing fits file: " + fits_name);
322 }
323 
325 std::vector<Image<t_complex>> read3d(const std::string &fits_name);
326 
328 Image<t_complex> read2d(const std::string &fits_name);
329 
330 } // namespace purify::pfitsio
331 
332 #endif
#define PURIFY_LOW_LOG(...)
Low priority message.
Definition: logging.h:207
const t_real pi
mathematical constant
Definition: types.h:70
void write2d(const Image< t_real > &eigen_image, const pfitsio::header_params &header, const bool &overwrite)
Write image to fits file.
Definition: pfitsio.cc:30
void write_key(fitsfile *fptr, const std::string &key, const std::string &value, const std::string &comment, int *status)
write key to fits file header
Definition: pfitsio.cc:8
Image< t_complex > read2d(const std::string &fits_name)
Read image from fits file.
Definition: pfitsio.cc:109
void write3d(const std::vector< Image< t_real >> &eigen_images, const pfitsio::header_params &header, const bool &overwrite)
Write cube to fits file using header information.
Definition: pfitsio.cc:63
std::vector< Image< t_complex > > read3d(const std::string &fits_name)
Read cube from fits file.
Definition: pfitsio.cc:95
void write_history(fitsfile *fptr, const std::string &context, const std::string &history, int *status)
write history to fits file
Definition: pfitsio.cc:22
T read_key(fitsfile *fptr, const std::string &key, int *status)
read fits key and return as tuple
Definition: pfitsio.h:122
const std::map< stokes, t_int > stokes_int
Definition: types.h:45
stokes
Definition: types.h:44
#define PURIFY_MACRO(VAR, H2)
Definition: pfitsio.h:62
bool operator!=(const header_params &h2) const
Definition: pfitsio.h:74
bool operator==(const header_params &h2) const
Definition: pfitsio.h:63
header_params()
create empty fits header
Definition: pfitsio.h:61
header_params(const std::string &fits_name_, const std::string &pix_units_, const t_real channels_total_, const t_real ra_, const t_real dec_, const stokes pol, const t_real cellx_, const t_real celly_, const t_real mean_frequency_, const t_real channel_width_, const t_uint niters_, const bool hasconverged_, const t_real relative_variation_, const t_real residual_convergence_, const t_real epsilon_)
create fits header object and fill
Definition: pfitsio.h:33