PURIFY
Next-generation radio interferometric imaging
utilities.cc
Go to the documentation of this file.
1 #include "purify/utilities.h"
2 #include "purify/config.h"
3 #include <fstream>
4 #include <random>
5 #include <sys/stat.h>
6 #include "purify/logging.h"
7 #include "purify/operators.h"
8 
9 namespace purify {
10 namespace utilities {
11 
12 t_int sub2ind(const t_int &row, const t_int &col, const t_int &rows, const t_int &cols) {
13  /*
14  Converts (row, column) of a matrix to a single index. This does the same as the matlab funciton
15  sub2ind, converts subscript to index.
16  Though order of cols and rows is probably transposed.
17 
18  row:: row of matrix (y)
19  col:: column of matrix (x)
20  cols:: number of columns for matrix
21  rows:: number of rows for matrix
22  */
23  return row * cols + col;
24 }
25 
26 std::tuple<t_int, t_int> ind2sub(const t_int &sub, const t_int &cols, const t_int &rows) {
27  /*
28  Converts index of a matrix to (row, column). This does the same as the matlab funciton sub2ind,
29  converts subscript to index.
30 
31  sub:: subscript of entry in matrix
32  cols:: number of columns for matrix
33  rows:: number of rows for matrix
34  row:: output row of matrix
35  col:: output column of matrix
36 
37  */
38  return std::make_tuple<t_int, t_int>(std::floor((sub - (sub % cols)) / cols), sub % cols);
39 }
40 
41 t_real mod(const t_real &x, const t_real &y) {
42  /*
43  returns x % y, and warps circularly around y for negative values.
44  */
45  t_real r = std::fmod(x, y);
46  if (r < 0) r = y + r;
47  return r;
48 }
49 
50 Image<t_complex> convolution_operator(const Image<t_complex> &a, const Image<t_complex> &b) {
51  /*
52  returns the convolution of images a with images b
53  a:: vector a, which is shifted
54  b:: vector b, which is fixed
55  */
56 
57  // size of a image
58  t_int a_y = a.rows();
59  t_int a_x = a.cols();
60  // size of b image
61  t_int b_y = b.rows();
62  t_int b_x = b.cols();
63 
64  Image<t_complex> output = Image<t_complex>::Zero(a_y + b_y, a_x + b_x);
65 
66  for (t_int l = 0; l < b.cols(); ++l) {
67  for (t_int k = 0; k < b.rows(); ++k) {
68  output(k, l) = (a * b.block(k, l, a_y, a_x)).sum();
69  }
70  }
71 
72  return output;
73 }
74 
75 t_real calculate_l2_radius(const t_uint y_size, const t_real &sigma, const t_real &n_sigma,
76  const std::string distirbution) {
77  /*
78  Calculates the epsilon, the radius of the l2_ball in sopt
79  y:: vector for the l2 ball
80  */
81  if (distirbution == "chi^2") {
82  if (sigma == 0) {
83  return std::sqrt(2 * y_size + n_sigma * std::sqrt(4 * y_size));
84  }
85  return std::sqrt(2 * y_size + n_sigma * std::sqrt(4 * y_size)) * sigma;
86  }
87  if (distirbution == "chi") {
88  auto alpha =
89  1. / (8 * y_size) - 1. / (128 * y_size * y_size); // series expansion for gamma relation
90  auto mean = std::sqrt(2) * std::sqrt(y_size) *
91  (1 - alpha); // using Gamma(k+1/2)/Gamma(k) asymptotic formula
92  auto standard_deviation = std::sqrt(2 * y_size * alpha * (2 - alpha));
93  if (sigma == 0) {
94  return mean + n_sigma * standard_deviation;
95  }
96  return (mean + n_sigma * standard_deviation) * sigma;
97  }
98 
99  return 1.;
100 }
101 t_real SNR_to_standard_deviation(const Vector<t_complex> &y0, const t_real &SNR) {
102  /*
103  Returns value of noise rms given a measurement vector and signal to noise ratio
104  y0:: complex valued vector before noise added
105  SNR:: signal to noise ratio
106 
107  This calculation follows Carrillo et al. (2014), PURIFY a new approach to radio interferometric
108  imaging but we have assumed that rms noise is for the real and imaginary parts
109  */
110  return y0.stableNorm() / std::sqrt(2 * y0.size()) * std::pow(10.0, -(SNR / 20.0));
111 }
112 
113 Vector<t_complex> add_noise(const Vector<t_complex> &y0, const t_complex &mean,
114  const t_real &standard_deviation) {
115  /*
116  Adds complex valued Gaussian noise to vector
117  y0:: vector before noise
118  mean:: complex valued mean of noise
119  standard_deviation:: rms of noise
120  */
121  auto sample = [&mean, &standard_deviation](t_complex x) {
122  std::random_device rd_real;
123  std::random_device rd_imag;
124  std::mt19937_64 rng_real(rd_real());
125  std::mt19937_64 rng_imag(rd_imag());
126  static std::normal_distribution<t_real> normal_dist_real(std::real(mean), standard_deviation);
127  static std::normal_distribution<t_real> normal_dist_imag(std::imag(mean), standard_deviation);
128  t_complex I(0, 1.);
129  return normal_dist_real(rng_real) + I * normal_dist_imag(rng_imag);
130  };
131 
132  auto noise = Vector<t_complex>::Zero(y0.size()).unaryExpr(sample);
133 
134  return y0 + noise;
135 }
136 
137 bool file_exists(const std::string &name) {
138  /*
139  Checks if a file exists
140  name:: path of file checked for existance.
141 
142  returns true if exists and false if not exists
143  */
144  struct stat buffer;
145  return (stat(name.c_str(), &buffer) == 0);
146 }
147 
148 std::tuple<t_real, t_real, t_real> fit_fwhm(const Image<t_real> &psf, const t_int &size) {
149  /*
150  Find FWHM of point spread function, using least squares.
151 
152  psf:: point spread function, assumed to be normalised.
153 
154  The method assumes that you have sampled at least
155  3 pixels across the beam.
156  */
157 
158  t_int x0 = std::floor(psf.cols() * 0.5);
159  t_int y0 = std::floor(psf.rows() * 0.5);
160 
161  // finding patch
162  Image<t_real> patch =
163  psf.block(std::floor(y0 - size * 0.5) + 1, std::floor(x0 - size * 0.5) + 1, size, size);
164  PURIFY_LOW_LOG("Fitting to Patch");
165 
166  // finding values for least squares
167 
168  std::vector<t_tripletList> entries;
169  t_int total_entries = 0;
170 
171  for (t_int i = 0; i < patch.cols(); ++i) {
172  for (t_int j = 0; j < patch.rows(); ++j) {
173  entries.emplace_back(j, i, std::log(patch(j, i)));
174  total_entries++;
175  }
176  }
177  Matrix<t_real> A = Matrix<t_real>::Zero(total_entries, 4);
178  Vector<t_real> q = Vector<t_real>::Zero(total_entries);
179  // putting values into a vector and matrix for least squares
180  for (t_int i = 0; i < total_entries; ++i) {
181  t_real x = entries.at(i).col() - size * 0.5 + 0.5;
182  t_real y = entries.at(i).row() - size * 0.5 + 0.5;
183  A(i, 0) = static_cast<t_real>(x * x); // x^2
184  A(i, 1) = static_cast<t_real>(x * y); // x * y
185  A(i, 2) = static_cast<t_real>(y * y); // y^2
186  q(i) = std::real(entries.at(i).value());
187  }
188  // least squares fitting
189  const auto solution =
190  static_cast<Vector<t_real>>(A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(q));
191  t_real const a = -solution(0);
192  t_real const b = +solution(1) * 0.5;
193  t_real const c = -solution(2);
194  // parameters of Gaussian
195  t_real theta = std::atan2(b, std::sqrt(std::pow(2 * b, 2) + std::pow(c - a, 2)) +
196  (c - a) * 0.5); // some relatively complicated trig identity to
197  // go from tan(2theta) to tan(theta).
198  t_real t = 0.;
199  t_real s = 0.;
200  if (std::abs(b) < 1e-13) {
201  t = a;
202  s = c;
203  theta = 0;
204  } else {
205  t = (a + c - 2 * b / std::sin(2 * theta)) * 0.5;
206  s = (a + c + 2 * b / std::sin(2 * theta)) * 0.5;
207  }
208 
209  t_real const sigma_x = std::sqrt(1 / (2 * t));
210  t_real const sigma_y = std::sqrt(1 / (2 * s));
211 
212  std::tuple<t_real, t_real, t_real> fit_parameters;
213 
214  // fit for the beam rms, used for FWHM
215  std::get<0>(fit_parameters) = sigma_x;
216  std::get<1>(fit_parameters) = sigma_y;
217  std::get<2>(fit_parameters) = theta; // because 2*theta is periodic with pi.
218  return fit_parameters;
219 }
220 
221 t_real median(const Vector<t_real> &input) {
222  // Finds the median of a real vector x
223  auto size = input.size();
224  Vector<t_real> x(size);
225  std::copy(input.data(), input.data() + size, x.data());
226  std::sort(x.data(), x.data() + size);
227  if (std::floor(size / 2) - std::ceil(size / 2) == 0)
228  return 0.5 * (x(int(std::floor(size / 2) - 1)) + x(int(std::floor(size / 2))));
229  return x(int(std::ceil(size / 2)));
230 }
231 
232 t_real dynamic_range(const Image<t_complex> &model, const Image<t_complex> &residuals,
233  const t_real &operator_norm) {
234  /*
235  Returns value of noise rms given a measurement vector and signal to noise ratio
236  y0:: complex valued vector before noise added
237  SNR:: signal to noise ratio
238 
239  This calculation follows Carrillo et al. (2014), PURIFY a new approach to radio interferometric
240  imaging
241  */
242  return std::sqrt(model.size()) * (operator_norm * operator_norm) / residuals.matrix().norm() *
243  model.cwiseAbs().maxCoeff();
244 }
245 
246 Array<t_complex> init_weights(const Vector<t_real> &u, const Vector<t_real> &v,
247  const Vector<t_complex> &weights, const t_real &oversample_factor,
248  const std::string &weighting_type, const t_real &R,
249  const t_int &ftsizeu, const t_int &ftsizev) {
250  /*
251  Calculate the weights to be applied to the visibilities in the measurement operator.
252  It does none, whiten, natural, uniform, and robust.
253  */
254  if (weighting_type == "none") return weights.array() * 0 + 1.;
255  if (weighting_type == "natural" or weighting_type == "whiten") return weights;
256  Vector<t_complex> out_weights(weights.size());
257  if ((weighting_type == "uniform") or (weighting_type == "robust")) {
258  t_real scale =
259  1. / oversample_factor; // scale for fov, controlling the region of sidelobe supression
260  Matrix<t_complex> gridded_weights = Matrix<t_complex>::Zero(ftsizev, ftsizeu);
261  for (t_int i = 0; i < weights.size(); ++i) {
262  t_int q = utilities::mod(floor(u(i) * scale), ftsizeu);
263  t_int p = utilities::mod(floor(v(i) * scale), ftsizev);
264  gridded_weights(p, q) += 1 * 1; // I get better results assuming all the weights are the
265  // same. It looks like miriad does this as well.
266  }
267  t_complex const sum_weights = (weights.array() * weights.array()).sum();
268  t_complex const sum_grid_weights2 = (gridded_weights.array() * gridded_weights.array()).sum();
269  t_complex const robust_scale =
270  sum_weights / sum_grid_weights2 * 25. *
271  std::pow(10, -2 * R); // Following standard formula, a bit different from miriad.
272  gridded_weights = gridded_weights.array().sqrt();
273  for (t_int i = 0; i < weights.size(); ++i) {
274  t_int q = utilities::mod(floor(u(i) * scale), ftsizeu);
275  t_int p = utilities::mod(floor(v(i) * scale), ftsizev);
276  if (weighting_type == "uniform") out_weights(i) = weights(i) / gridded_weights(p, q);
277  if (weighting_type == "robust") {
278  out_weights(i) = weights(i) / std::sqrt(1. + robust_scale * gridded_weights(p, q) *
279  gridded_weights(p, q));
280  }
281  }
282  } else
283  throw std::runtime_error("Wrong weighting type, " + weighting_type + " not recognised.");
284  return out_weights;
285 }
286 
287 std::tuple<t_int, t_real> checkpoint_log(const std::string &diagnostic) {
288  // reads a log file and returns the latest parameters
289  if (!utilities::file_exists(diagnostic)) return std::make_tuple(0, 0);
290 
291  t_int iters = 0;
292  t_real gamma = 0;
293  std::ifstream log_file(diagnostic);
294  std::string entry;
295  std::string s;
296 
297  std::getline(log_file, s);
298 
299  while (log_file) {
300  if (!std::getline(log_file, s)) break;
301  std::istringstream ss(s);
302  std::getline(ss, entry, ' ');
303  iters = std::floor(std::stod(entry));
304  std::getline(ss, entry, ' ');
305  gamma = std::stod(entry);
306  }
307  log_file.close();
308  return std::make_tuple(iters, gamma);
309 }
310 
311 Matrix<t_complex> re_sample_ft_grid(const Matrix<t_complex> &input, const t_real &re_sample_ratio) {
312  /*
313  up samples image using by zero padding the fft
314 
315  input:: fft to be upsampled, with zero frequency at (0,0) of the matrix.
316 
317  */
318  if (re_sample_ratio == 1) return input;
319 
320  // sets up dimensions for old image
321  t_int old_x_centre_floor = std::floor(input.cols() * 0.5);
322  t_int old_y_centre_floor = std::floor(input.rows() * 0.5);
323  // need ceilling in case image is of odd dimension
324  t_int old_x_centre_ceil = std::ceil(input.cols() * 0.5);
325  t_int old_y_centre_ceil = std::ceil(input.rows() * 0.5);
326 
327  // sets up dimensions for new image
328  t_int new_x = std::floor(input.cols() * re_sample_ratio);
329  t_int new_y = std::floor(input.rows() * re_sample_ratio);
330 
331  t_int new_x_centre_floor = std::floor(new_x * 0.5);
332  t_int new_y_centre_floor = std::floor(new_y * 0.5);
333  // need ceilling in case image is of odd dimension
334  t_int new_x_centre_ceil = std::ceil(new_x * 0.5);
335  t_int new_y_centre_ceil = std::ceil(new_y * 0.5);
336 
337  Matrix<t_complex> output = Matrix<t_complex>::Zero(new_y, new_x);
338 
339  t_int box_dim_x;
340  t_int box_dim_y;
341 
342  // now have to move each quadrant into new grid
343  box_dim_x = std::min(old_x_centre_ceil, new_x_centre_ceil);
344  box_dim_y = std::min(old_y_centre_ceil, new_y_centre_ceil);
345  //(0, 0)
346  output.bottomLeftCorner(box_dim_y, box_dim_x) = input.bottomLeftCorner(box_dim_y, box_dim_x);
347 
348  box_dim_x = std::min(old_x_centre_ceil, new_x_centre_ceil);
349  box_dim_y = std::min(old_y_centre_floor, new_y_centre_floor);
350  //(y0, 0)
351  output.topLeftCorner(box_dim_y, box_dim_x) = input.topLeftCorner(box_dim_y, box_dim_x);
352 
353  box_dim_x = std::min(old_x_centre_floor, new_x_centre_floor);
354  box_dim_y = std::min(old_y_centre_ceil, new_y_centre_ceil);
355  //(0, x0)
356  output.bottomRightCorner(box_dim_y, box_dim_x) = input.bottomRightCorner(box_dim_y, box_dim_x);
357 
358  box_dim_x = std::min(old_x_centre_floor, new_x_centre_floor);
359  box_dim_y = std::min(old_y_centre_floor, new_y_centre_floor);
360  //(y0, x0)
361  output.topRightCorner(box_dim_y, box_dim_x) = input.topRightCorner(box_dim_y, box_dim_x);
362 
363  return output;
364 }
365 Matrix<t_complex> re_sample_image(const Matrix<t_complex> &input, const t_real &re_sample_ratio) {
366  const auto ft_plan = operators::fftw_plan::estimate;
367  auto fft =
368  purify::operators::init_FFT_2d<Vector<t_complex>>(input.rows(), input.cols(), 1., ft_plan);
369  Vector<t_complex> ft_grid = Vector<t_complex>::Zero(input.size());
370  std::get<0>(fft)(ft_grid, Vector<t_complex>::Map(input.data(), input.size()));
371  Matrix<t_complex> const new_ft_grid = utilities::re_sample_ft_grid(
372  Matrix<t_complex>::Map(ft_grid.data(), input.rows(), input.cols()), re_sample_ratio);
373  Vector<t_complex> output = Vector<t_complex>::Zero(input.size());
374  fft = purify::operators::init_FFT_2d<Vector<t_complex>>(new_ft_grid.rows(), new_ft_grid.cols(),
375  1., ft_plan);
376  std::get<1>(fft)(output, Vector<t_complex>::Map(new_ft_grid.data(), new_ft_grid.size()));
377  return Matrix<t_complex>::Map(output.data(), new_ft_grid.rows(), new_ft_grid.cols()) *
378  re_sample_ratio;
379 }
380 } // namespace utilities
381 } // namespace purify
#define PURIFY_LOW_LOG(...)
Low priority message.
Definition: logging.h:207
const std::vector< t_real > u
data for u coordinate
Definition: operators.cc:18
const std::vector< t_real > v
data for v coordinate
Definition: operators.cc:20
const t_real c
speed of light in vacuum
Definition: types.h:72
Array< t_complex > init_weights(const Vector< t_real > &u, const Vector< t_real > &v, const Vector< t_complex > &weights, const t_real &oversample_factor, const std::string &weighting_type, const t_real &R, const t_int &ftsizeu, const t_int &ftsizev)
Calculate weightings.
Definition: utilities.cc:246
t_real median(const Vector< t_real > &input)
Return median of real vector.
Definition: utilities.cc:221
std::tuple< t_real, t_real, t_real > fit_fwhm(const Image< t_real > &psf, const t_int &size)
Method to fit Gaussian to PSF.
Definition: utilities.cc:148
t_real SNR_to_standard_deviation(const Vector< t_complex > &y0, const t_real &SNR)
Converts SNR to RMS noise.
Definition: utilities.cc:101
K::Scalar mean(const K x)
Calculate mean of vector.
Definition: utilities.h:21
Vector< t_complex > add_noise(const Vector< t_complex > &y0, const t_complex &mean, const t_real &standard_deviation)
Add guassian noise to vector.
Definition: utilities.cc:113
t_int sub2ind(const t_int &row, const t_int &col, const t_int &rows, const t_int &cols)
Converts from subscript to index for matrix.
Definition: utilities.cc:12
std::tuple< t_int, t_real > checkpoint_log(const std::string &diagnostic)
Reads a diagnostic file and updates parameters.
Definition: utilities.cc:287
Matrix< t_complex > re_sample_ft_grid(const Matrix< t_complex > &input, const t_real &re_sample_ratio)
zero pads ft grid for image up sampling and downsampling
Definition: utilities.cc:311
Matrix< t_complex > re_sample_image(const Matrix< t_complex > &input, const t_real &re_sample_ratio)
resamples image size
Definition: utilities.cc:365
bool file_exists(const std::string &name)
Test to see if file exists.
Definition: utilities.cc:137
t_real dynamic_range(const Image< t_complex > &model, const Image< t_complex > &residuals, const t_real &operator_norm)
Calculate the dynamic range between the model and residuals.
Definition: utilities.cc:232
t_real mod(const t_real &x, const t_real &y)
Mod function modified to wrap circularly for negative numbers.
Definition: utilities.cc:41
t_real standard_deviation(const K x)
Calculates the standard deviation of a vector.
Definition: utilities.h:34
Image< t_complex > convolution_operator(const Image< t_complex > &a, const Image< t_complex > &b)
Calculates the convolution between two images.
Definition: utilities.cc:50
t_real calculate_l2_radius(const t_uint y_size, const t_real &sigma, const t_real &n_sigma, const std::string distirbution)
A function that calculates the l2 ball radius for sopt.
Definition: utilities.cc:75
std::tuple< t_int, t_int > ind2sub(const t_int &sub, const t_int &cols, const t_int &rows)
Converts from index to subscript for matrix.
Definition: utilities.cc:26