File: subpixelrenderer.cpp

package info (click to toggle)
wsclean 3.7-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 10,968 kB
  • sloc: cpp: 85,742; python: 3,526; sh: 245; makefile: 21
file content (302 lines) | stat: -rw-r--r-- 12,526 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
#include "subpixelrenderer.h"

#include <cmath>
#include <vector>

#include <aocommon/lane.h>
#include <aocommon/logger.h>
#include <aocommon/uvector.h>
#include <aocommon/threadpool.h>

#include <schaapcommon/math/drawgaussian.h>
#include <schaapcommon/fitters/polynomialfitter.h>

#include "../model/bbsmodel.h"
#include "../model/modelsource.h"
#include "../structures/resources.h"
#include "fourierdomainrenderer.h"

using aocommon::Image;

namespace wsclean::math {
namespace {

struct RenderingInfo {
  double central_frequency;
  double start_frequency;
  double end_frequency;
  const aocommon::CoordinateSystem& coordinate_system;
  std::vector<std::vector<Image>> images;
};

aocommon::UVector<float> MakeSincKernel(double value, size_t size) {
  aocommon::UVector<float> kernel(size + 1);
  const int mid = kernel.size() / 2;
  const double fraction = value - std::floor(value);
  for (size_t i = 0; i != kernel.size(); ++i) {
    const double value = (int(i) - mid - fraction) * M_PI;
    kernel[i] = (value == 0) ? 1.0 : std::sin(value) / value;
  }
  return kernel;
}

void MakeWindowedKernel(double value, size_t n,
                        aocommon::UVector<float>& kernel) {
  const int midH = n / 2;
  const float fraction = value - std::floor(value);
  for (size_t i = 0; i != n; ++i) {
    const float xi = (int(i) - midH - fraction) * M_PI;
    // The Hann window is cos^2 (xi/n).
    const float hann_term = std::cos(xi / n);
    kernel[i] = (xi == 0.0) ? 1.0 : hann_term * hann_term * std::sin(xi) / xi;
  }
}

// This function reads the spectral terms (including Stokes I flux) from the
// component model data, and, if required, the terms are converted to those of
// the desired (ordinary) polynomial.
std::vector<float> GetSpectralTerms(const ModelComponent& component,
                                    const RenderingInfo& settings) {
  if (!component.HasPowerLawSED())
    throw std::runtime_error(
        "Spectral term imaging requires the model to specify functional terms");
  const PowerLawSED& sed = static_cast<const PowerLawSED&>(component.SED());
  double component_reference_frequency = 0.0;
  double component_brightness[4] = {0.0, 0.0, 0.0, 0.0};
  std::vector<double> component_terms;
  sed.GetData(component_reference_frequency, component_brightness,
              component_terms);

  // Combine Stokes I flux with the higher-order spectral terms.
  const float stokes_i = component_brightness[0];
  std::vector<float> spectral_terms = {stokes_i};
  spectral_terms.insert(spectral_terms.end(), component_terms.begin(),
                        component_terms.end());

  if (sed.IsLogarithmic()) {
    std::vector<float> polynomial_terms(spectral_terms.size(), 0.0);
    schaapcommon::fitters::PowerLawToPolynomialCoefficients(
        polynomial_terms, spectral_terms, component_reference_frequency,
        settings.central_frequency, settings.start_frequency,
        settings.end_frequency);

    spectral_terms = polynomial_terms;
  } else if (component_reference_frequency != settings.central_frequency) {
    schaapcommon::fitters::ShiftPolynomialReferenceFrequency(
        spectral_terms, component_reference_frequency,
        settings.central_frequency);
  }

  return spectral_terms;
}

// This function waits for sources to be placed in the source lane, and renders
// these to the image. It runs until the lane receives a write_end() call.
void RenderSourceTasks(aocommon::Lane<ModelSource>& source_lane,
                       std::vector<Image>& images, size_t window_size,
                       size_t n_terms, const RenderingInfo& settings) {
  const double pixel_size = settings.coordinate_system.dl;
  FourierDomainRenderer fd_renderer(window_size, pixel_size);
  SubPixelRenderer renderer(window_size);
  ModelSource source;
  while (source_lane.read(source)) {
    for (const ModelComponent& component : source) {
      std::vector<float> spectral_terms = GetSpectralTerms(component, settings);
      if (n_terms < spectral_terms.size()) {
        aocommon::Logger::Warn << "Consider increasing the number of spectral "
                                  "terms set with -draw-spectral-terms (" +
                                      std::to_string(spectral_terms.size()) +
                                      " terms available, but only " +
                                      std::to_string(n_terms) + " requested)";
      }
      for (size_t image_index = 0; image_index < n_terms; ++image_index) {
        const float term = spectral_terms[image_index];
        Image& image = images[image_index];
        if (component.Type() == ModelComponent::GaussianSource) {
          bool is_small_gaussian = component.MinorAxis() < 50.0 * pixel_size;
          if (is_small_gaussian) {
            double l, m;
            aocommon::ImageCoordinates::RaDecToLM<double>(
                component.PosRA(), component.PosDec(),
                settings.coordinate_system.ra, settings.coordinate_system.dec,
                l, m);
            l += settings.coordinate_system.l_shift;
            m += settings.coordinate_system.m_shift;
            float x, y;
            aocommon::ImageCoordinates::LMToXYfloat<float>(
                l, m, settings.coordinate_system.dl,
                settings.coordinate_system.dm, settings.coordinate_system.width,
                settings.coordinate_system.height, x, y);

            fd_renderer.RenderModelComponent(
                image.Data(), component, settings.coordinate_system.width,
                settings.coordinate_system.height, term, x, y);
          } else {
            const aocommon::CoordinateSystem& cs = settings.coordinate_system;
            const schaapcommon::math::Ellipse shape(component.MajorAxis(),
                                                    component.MinorAxis(),
                                                    component.PositionAngle());
            DrawGaussianToLm(image.Data(), cs.width, cs.height, cs.ra, cs.dec,
                             cs.dl, cs.dm, cs.l_shift, cs.m_shift,
                             component.PosRA(), component.PosDec(), shape,
                             term);
          }
        } else {
          double l, m;
          aocommon::ImageCoordinates::RaDecToLM<double>(
              component.PosRA(), component.PosDec(),
              settings.coordinate_system.ra, settings.coordinate_system.dec, l,
              m);
          l += settings.coordinate_system.l_shift;
          m += settings.coordinate_system.m_shift;
          float x, y;
          aocommon::ImageCoordinates::LMToXYfloat<float>(
              l, m, settings.coordinate_system.dl,
              settings.coordinate_system.dm, settings.coordinate_system.width,
              settings.coordinate_system.height, x, y);
          if (window_size)
            renderer.RenderWindowedSource(
                image.Data(), settings.coordinate_system.width,
                settings.coordinate_system.height, term, x, y);
          else
            renderer.RenderSource(
                image.Data(), settings.coordinate_system.width,
                settings.coordinate_system.height, term, x, y);
        }
      }
    }
  }
}

}  // namespace

void SubPixelRenderer::RenderSource(float* image, size_t width, size_t height,
                                    float brightness, double x, double y) {
  const aocommon::UVector<float> x_sinc = MakeSincKernel(x, width);
  const aocommon::UVector<float> y_sinc = MakeSincKernel(y, height);

  const int mid_x = x_sinc.size() / 2;
  const int mid_y = y_sinc.size() / 2;
  const int x_offset = std::floor(x) - mid_x;
  const int y_offset = std::floor(y) - mid_y;
  const size_t start_x = std::max<int>(x_offset, 0);
  const size_t start_y = std::max<int>(y_offset, 0);
  const size_t end_x = std::min<size_t>(x_offset + x_sinc.size(), width);
  const size_t end_y = std::min<size_t>(y_offset + y_sinc.size(), height);

  for (size_t yi = start_y; yi != end_y; ++yi) {
    float* ptr = &image[yi * width];
    const double y_value = brightness * y_sinc[yi - y_offset];
    for (size_t xi = start_x; xi != end_x; ++xi) {
      ptr[xi] += y_value * x_sinc[xi - x_offset];
    }
  }
}

void SubPixelRenderer::RenderWindowedSource(float* image, size_t width,
                                            size_t height, float flux, float x,
                                            float y) {
  const size_t n = x_kernel_.size();

  MakeWindowedKernel(x, n, x_kernel_);
  MakeWindowedKernel(y, n, y_kernel_);

  const int mid_x = n / 2;
  const int mid_y = n / 2;
  const int x_offset = std::floor(x) - mid_x;
  const int y_offset = std::floor(y) - mid_y;
  const size_t start_x = std::max<int>(x_offset, 0);
  const size_t start_y = std::max<int>(y_offset, 0);
  const size_t end_x =
      std::max<int>(std::min<int>(x_offset + int(n), int(width)), int(start_x));
  const size_t end_y = std::max<int>(
      std::min<int>(y_offset + int(n), int(height)), int(start_y));
  for (size_t yi = start_y; yi != end_y; ++yi) {
    float* ptr = &image[yi * width];
    const float y_corrected_flux = flux * y_kernel_[yi - y_offset];
    for (size_t xi = start_x; xi != end_x; ++xi) {
      ptr[xi] += y_corrected_flux * x_kernel_[xi - x_offset];
    }
  }
}

std::vector<aocommon::Image> RenderSubPixelModel(
    const std::string& model_filename,
    const aocommon::CoordinateSystem& coordinate_system, double frequency,
    double bandwidth, size_t window_size, size_t n_terms, double mem_fraction,
    double mem_limit) {
  aocommon::Logger::Info << "Rendering sources...\n";
  aocommon::ThreadPool& pool = aocommon::ThreadPool::GetInstance();
  // Each thread will get their own list of images, to prevent having to
  // synchronize. These lists consist of the Stokes I image and images for each
  // of the higher order spectral terms, as requested.
  std::vector<std::vector<Image>> images;

  // Limit the number of threads based on the available memory.
  const size_t thread_limit =
      std::max<size_t>(1, GetAvailableMemory(0.5 * mem_fraction, mem_limit) /
                              (n_terms * coordinate_system.width *
                               coordinate_system.height * sizeof(float)));
  const size_t n_threads = std::min(pool.NThreads(), thread_limit);
  if (n_threads < pool.NThreads()) {
    aocommon::Logger::Warn << "Rendering sky model with fewer threads ("
                           << n_threads << ") than requested ("
                           << pool.NThreads() << ").";
  }

  // Thread pool is only used for renderering, hence, it's safe to resize.
  pool.SetNThreads(n_threads);

  images.resize(n_threads);
  const RenderingInfo settings(frequency, frequency - bandwidth * 0.5,
                               frequency + bandwidth * 0.5, coordinate_system,
                               images);
  aocommon::Lane<ModelSource> source_lane(n_threads);
  for (size_t i = 0; i != n_threads; ++i) {
    for (size_t t = 0; t != n_terms; ++t) {
      images[i].emplace_back(coordinate_system.width, coordinate_system.height,
                             0.0f);
    }
  }

  pool.StartParallelExecution([&](size_t thread_index) {
    RenderSourceTasks(source_lane, images[thread_index], window_size, n_terms,
                      settings);
  });

  // We use the "streaming" reading function from BBSModel. This allows using
  // very large files without keeping it in memory all the time. The streaming
  // read will, after reading a source, call the provided function. That
  // function will place the source in the queue, which will be rendered by the
  // threads running in the thread pool.
  auto process_function = [&source_lane](const ModelSource& s) {
    source_lane.write(s);
  };

  std::exception_ptr exception;
  try {
    BBSModel::Read(model_filename, process_function);
  } catch (...) {
    // In case of an exception (e.g. file not found), the threads still need to
    // receive a signal to end.
    exception = std::current_exception();
  }

  aocommon::Logger::Info << "Finishing...\n";
  source_lane.write_end();
  pool.FinishParallelExecution();

  if (exception) rethrow_exception(exception);

  // Add all images together
  for (size_t thread_index = 1; thread_index != images.size(); ++thread_index) {
    for (size_t image_index = 0; image_index < n_terms; ++image_index) {
      images[0][image_index] += images[thread_index][image_index];
    }
  }

  std::vector<aocommon::Image> result(std::move(images.front()));
  return result;
}

}  // namespace wsclean::math