File: primarybeam.cpp

package info (click to toggle)
wsclean 3.6-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 16,296 kB
  • sloc: cpp: 129,246; python: 22,066; sh: 360; ansic: 230; makefile: 185
file content (732 lines) | stat: -rw-r--r-- 31,483 bytes parent folder | download | duplicates (2)
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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
#include "primarybeam.h"
#include "../msproviders/msreaders/msreader.h"

#include "../main/settings.h"

#include "../structures/imageweights.h"

#include "../msproviders/msdatadescription.h"

#include "../io/findmwacoefffile.h"

#include <aocommon/image.h>
#include <aocommon/logger.h>
#include <aocommon/multibanddata.h>

#include <schaapcommon/facets/facetimage.h>

#include <boost/filesystem/operations.hpp>
#include <boost/algorithm/string/case_conv.hpp>

#include <optional>
#include <stdexcept>

#ifdef HAVE_EVERYBEAM
#include <EveryBeam/aterms/atermconfig.h>
#include <EveryBeam/griddedresponse/griddedresponse.h>

using everybeam::ATermSettings;
using everybeam::aterms::ATermConfig;
#endif

using aocommon::CoordinateSystem;
using aocommon::Image;
using aocommon::Logger;
using aocommon::Polarization;
using aocommon::PolarizationEnum;

using schaapcommon::reordering::MSSelection;

namespace wsclean {

namespace {

/// Returns a fitswriter initialized for the given coordinates and entry.
aocommon::FitsWriter MakeWriter(const CoordinateSystem& coordinates,
                                const ImagingTableEntry& entry) {
  aocommon::FitsWriter writer;
  writer.SetImageDimensions(coordinates.width, coordinates.height,
                            coordinates.ra, coordinates.dec, coordinates.dl,
                            coordinates.dm);
  writer.SetPhaseCentreShift(coordinates.l_shift, coordinates.m_shift);
  writer.SetFrequency(entry.CentralFrequency(),
                      entry.bandEndFrequency - entry.bandStartFrequency);
  return writer;
}

std::string BeamFilename(const Settings& settings,
                         const ImageFilename& image_name,
                         std::optional<size_t> element_index) {
  const std::string prefix = image_name.GetBeamPrefix(settings);
  return element_index ? prefix + "-" + std::to_string(*element_index) + ".fits"
                       : prefix + ".fits";
}

/**
 * @param element_index when given, the index is used in the filename (e.g.
 * "wsclean-beam-3.fits"). Otherwise, a filename without index is used (e.g.
 * "wsclean-beam.fits").
 */
void WriteBeamElement(const ImageFilename& image_name, const Image& beam_image,
                      const Settings& settings,
                      std::optional<size_t> element_index,
                      const aocommon::FitsWriter& writer) {
  const std::string filename =
      BeamFilename(settings, image_name, element_index);
  writer.Write(filename, beam_image.Data());
}

aocommon::Image Load(const Settings& settings, const ImageFilename& image_name,
                     std::optional<size_t> element_index) {
  const std::string filename =
      BeamFilename(settings, image_name, element_index);
  if (boost::filesystem::exists(filename)) {
    aocommon::FitsReader reader(filename);
    Image image(reader.ImageWidth(), reader.ImageHeight());
    reader.Read(image.Data());
    return image;
  } else {
    return aocommon::Image();
  }
}

#ifdef HAVE_EVERYBEAM
void WriteBeamImages(const ImageFilename& image_name,
                     std::vector<aocommon::HMC4x4>&& beam,
                     const Settings& settings, const ImagingTableEntry& entry,
                     const CoordinateSystem& coordinates,
                     size_t undersampling_factor) {
  using everybeam::griddedresponse::GriddedResponse;
  const bool use_squared_beam = settings.UseFacetCorrections();
  const aocommon::FitsWriter writer = MakeWriter(coordinates, entry);
  Image upsampled(coordinates.width, coordinates.height);
  if (use_squared_beam) {
    for (aocommon::HMC4x4& pixel : beam) {
      if (pixel.Invert()) {
        const double stokes_i_sqrt = std::sqrt(
            0.5 * (pixel.Data(0) + 2.0 * pixel.Data(9) + pixel.Data(15)));
        pixel.Data(0) = stokes_i_sqrt == 0.0 ? 0.0 : 1.0 / stokes_i_sqrt;
      } else {
        pixel.Data(0) = 0.0;
      }
    }
    GriddedResponse::UpsampleCorrection(upsampled.Data(), 0, coordinates.width,
                                        coordinates.height, beam,
                                        undersampling_factor);
    WriteBeamElement(image_name, upsampled, settings, {}, writer);
  } else {
    std::vector<size_t> required_elements;
    const bool pseudo_correction = settings.polarizations.size() == 1 &&
                                   (entry.polarization == Polarization::RR ||
                                    entry.polarization == Polarization::LL);
    const bool stokes_i_correction =
        settings.polarizations.size() == 1 &&
        entry.polarization == Polarization::StokesI;
    const bool diagonal_correction =
        settings.polarizations ==
        std::set{aocommon::Polarization::XX, aocommon::Polarization::YY};
    if (pseudo_correction || stokes_i_correction) {
      // Require: m_00, m_03, m_30 and m_33 ; see aocommon::HMC4x4::Data()
      // m_03 is complex conjugate of m_30. The imaginary value is not
      // necessary for Stokes I correction as it cancels out.
      required_elements = {0, 9, 15};
    } else if (diagonal_correction) {
      // In diagonal (=xx,yy) correction, a 2x2 matrix with the xx-to-xx,
      // xx-to-yy, yy-to-xx and yy-to-yy values needs to be inverted. This
      // matrix inversion is affected (I think) by the imaginary value of
      // the off-diagonal, so we need image 10 too.
      required_elements = {0, 9, 10, 15};
    } else {
      required_elements = {0, 1, 2,  3,  4,  5,  6,  7,
                           8, 9, 10, 11, 12, 13, 14, 15};
    }
    for (size_t element : required_elements) {
      Logger::Debug << "Upsampling beam element " << element << "...\n";
      GriddedResponse::UpsampleCorrection(upsampled.Data(), element,
                                          coordinates.width, coordinates.height,
                                          beam, undersampling_factor);
      WriteBeamElement(image_name, upsampled, settings, element, writer);
    }
  }
}
#endif

void ApplyFacetCorrections(const ImageFilename& image_name,
                           const Settings& settings,
                           const CoordinateSystem& coordinates,
                           const ImagingTable::Group& group,
                           const OutputChannelInfo& channel_info) {
  schaapcommon::facets::FacetImage facet_image(coordinates.width,
                                               coordinates.height, 1);

  // group.front() can be used, because the central frequency and start/end
  // frequency are equal inside a FacetGroup
  const aocommon::FitsWriter writer = MakeWriter(coordinates, *group.front());

  // When facet corrections are applied, only a scalar correction is left to
  // be applied on the images.
  Image beam_image = Load(settings, image_name, {});
  if (!beam_image.Empty()) {
    std::vector<float*> image_pointer = {beam_image.Data()};
    for (const std::shared_ptr<ImagingTableEntry>& entry : group) {
      // The visibilities are weighted by the beam and h5 facet solutions
      // when gridding, and the visibilities themselves are "apparent"
      // causing the images to be weighted by the square of those gains.
      // The images are then per facet fully Mueller corrected to make them
      // flat gain (=true instrinsic flux), after which they are again divided
      // by the sqrt of the Stokes I gain (a scalar correction) to make them
      // approximately flat noise for deconvolution. What's left to do here is
      // therefore to take out that sqrt of Stokes I gain. This is done in
      // two steps: an estimate of the average squared beam is corrected as a
      // smooth image correction, whereas the residual facet solution gains
      // are facet-based. In case the facet gains are near unity, this would
      // result in a smooth image. Otherwise, the beam and solution
      // contributions aren't easily separable (as they are averages of
      // squares), so we just use (full_correction / beam_correction) as
      // factor. The final image needs to be divided by this factor. However,
      // since we are scaling the beam images, we need to apply the inverse of
      // that.
      const double beam_factor =
          channel_info.averageBeamFacetCorrection[entry->facetIndex]
              .GetStokesIValue();
      const double full_factor =
          channel_info.averageFacetCorrection[entry->facetIndex]
              .GetStokesIValue();
      const double correction =
          beam_factor == 0.0 ? full_factor : full_factor / beam_factor;
      facet_image.SetFacet(*entry->facet, true);
      facet_image.MultiplyImageInsideFacet(image_pointer,
                                           std::sqrt(correction));
    }

    WriteBeamElement(image_name, beam_image, settings, {}, writer);
  }
}

#ifdef HAVE_EVERYBEAM
std::unique_ptr<everybeam::telescope::Telescope> PrepareEveryBeam(
    SynchronizedMS& ms, const Settings& settings,
    everybeam::TelescopeType telescope_type) {
  // Pass the settings to EveryBeam::Options struct
  const bool frequency_interpolation = true;
  const bool use_channel_frequency = true;
  const std::string element_response_model = settings.beamModel;

  const std::string coefficients_path =
      (telescope_type == everybeam::TelescopeType::kMWATelescope)
          ? wsclean::mwa::FindCoeffFile(settings.mwaPath)
          : "";

  ATermSettings aterm_settings;
  aterm_settings.coeff_path = coefficients_path;
  aterm_settings.data_column_name = settings.dataColumnName;
  const everybeam::Options options = ATermConfig::ConvertToEBOptions(
      *ms, aterm_settings, frequency_interpolation,
      settings.beamNormalisationMode, use_channel_frequency,
      element_response_model, settings.beamMode);

  // Make telescope
  return everybeam::Load(ms.MS(), options);
}

#endif  // HAVE_EVERYBEAM

}  // namespace

/**
 * Returns the intervals to which a beam is calculated over.
 * The returned vector holds for each interval the start and
 * end row indices, as well as the central time of the interval.
 */
std::vector<BeamInterval> GetBeamIntervals(MSProvider& ms_provider,
                                           double seconds_before_beam_update) {
  double start_time = 0.0;
  double previous_time = 0.0;
  std::unique_ptr<MSReader> ms_reader = ms_provider.MakeReader();
  size_t row = 0;
  size_t start_row = 0;
  std::vector<BeamInterval> result;
  if (ms_reader->CurrentRowAvailable()) {
    MSProvider::MetaData meta;
    ms_reader->ReadMeta(meta);
    start_time = meta.time;
    previous_time = meta.time;
    ms_reader->NextInputRow();
    ++row;
    while (ms_reader->CurrentRowAvailable()) {
      ms_reader->ReadMeta(meta);
      if (std::abs(meta.time - start_time) > seconds_before_beam_update) {
        result.emplace_back(BeamInterval{start_row, row - 1,
                                         (start_time + previous_time) * 0.5});
        start_row = row;
        start_time = meta.time;
      }
      previous_time = meta.time;
      ms_reader->NextInputRow();
      ++row;
    }
    if (row - 1 != start_row)
      result.emplace_back(
          BeamInterval{start_row, row - 1, (start_time + previous_time) * 0.5});
  }

  return result;
}

PrimaryBeam::PrimaryBeam(const Settings& settings)
    : settings_(settings),
      phase_centre_ra_(0.0),
      phase_centre_dec_(0.0),
      l_shift_(0.0),
      m_shift_(0.0),
      undersample_(computeUndersamplingFactor(settings)),
      seconds_before_beam_update_(settings.primaryBeamUpdateTime)
#ifdef HAVE_EVERYBEAM
      ,
      beam_mode_(everybeam::ParseBeamMode(settings.beamMode)),
      beam_normalisation_mode_(
          everybeam::ParseBeamNormalisationMode(settings.beamNormalisationMode))
#endif
{
}

PrimaryBeam::~PrimaryBeam() = default;

void PrimaryBeam::AddMS(std::unique_ptr<MSDataDescription> description) {
  ms_list_.emplace_back(std::move(description));
}

void PrimaryBeam::CorrectBeamForFacetGain(
    const ImageFilename& image_name, const ImagingTable::Group& group,
    const OutputChannelInfo& channel_info) {
  const CoordinateSystem coordinates{settings_.trimmedImageWidth,
                                     settings_.trimmedImageHeight,
                                     phase_centre_ra_,
                                     phase_centre_dec_,
                                     settings_.pixelScaleX,
                                     settings_.pixelScaleY,
                                     l_shift_,
                                     m_shift_};
  ApplyFacetCorrections(image_name, settings_, coordinates, group,
                        channel_info);
}

void PrimaryBeam::CorrectImages(aocommon::FitsWriter& writer,
                                const ImageFilename& image_name,
                                const std::string& filename_kind) {
  if (settings_.polarizations.size() == 1 || filename_kind == "psf") {
    const PrimaryBeamImageSet beam_images = LoadStokesI(image_name);
    PolarizationEnum pol = *settings_.polarizations.begin();

    const bool pseudo_correction =
        settings_.polarizations.size() == 1 &&
        (pol == Polarization::RR || pol == Polarization::LL);
    if (pseudo_correction)
      Logger::Warn
          << "Warning: not all polarizations are available for full beam "
             "correction, performing pseudo-Stokes I beam correction.\n";
    if (pol == Polarization::StokesI || pseudo_correction) {
      ImageFilename stokes_i_name(image_name);
      stokes_i_name.SetPolarization(pol);
      std::string prefix;
      if (filename_kind == "psf")
        prefix = stokes_i_name.GetPSFPrefix(settings_);
      else
        prefix = stokes_i_name.GetPrefix(settings_);
      aocommon::FitsReader reader(prefix + "-" + filename_kind + ".fits");
      Image image(reader.ImageWidth(), reader.ImageHeight());
      reader.Read(image.Data());
      beam_images.ApplyStokesI(image.Data(), settings_.primaryBeamLimit);
      writer.Write(prefix + "-" + filename_kind + "-pb.fits", image.Data());
    } else {
      throw std::runtime_error(
          "Primary beam correction is requested, but this is not supported "
          "when imaging a single polarization that is not Stokes I. Either "
          "image all four polarizations or turn off beam correction.");
    }
  } else if (settings_.polarizations ==
             std::set<aocommon::PolarizationEnum>{aocommon::Polarization::XX,
                                                  aocommon::Polarization::YY}) {
    const PrimaryBeamImageSet beam_images = LoadDiagonal(image_name);
    Image images[2];
    std::unique_ptr<aocommon::FitsReader> reader;
    for (size_t pol_index = 0; pol_index != 2; ++pol_index) {
      const aocommon::PolarizationEnum pol = (pol_index == 0)
                                                 ? aocommon::Polarization::XX
                                                 : aocommon::Polarization::YY;
      ImageFilename name(image_name);
      name.SetPolarization(pol);
      reader = std::make_unique<aocommon::FitsReader>(
          name.GetPrefix(settings_) + "-" + filename_kind + ".fits");
      images[pol_index] = Image(reader->ImageWidth(), reader->ImageHeight());
      reader->Read(images[pol_index].Data());
    }

    float* image_ptrs[2] = {images[0].Data(), images[1].Data()};
    beam_images.ApplyDiagonal(image_ptrs, settings_.primaryBeamLimit);

    for (size_t pol_index = 0; pol_index != 2; ++pol_index) {
      const aocommon::PolarizationEnum pol = (pol_index == 0)
                                                 ? aocommon::Polarization::XX
                                                 : aocommon::Polarization::YY;
      ImageFilename name(image_name);
      name.SetPolarization(pol);
      writer.SetPolarization(pol);
      writer.Write(name.GetPrefix(settings_) + "-" + filename_kind + "-pb.fits",
                   images[pol_index].Data());
    }
  } else if (aocommon::Polarization::HasFullStokesPolarization(
                 settings_.polarizations)) {
    const PrimaryBeamImageSet beam_images = LoadFull(image_name);
    Image images[4];
    std::unique_ptr<aocommon::FitsReader> reader;
    for (size_t pol_index = 0; pol_index != 4; ++pol_index) {
      aocommon::PolarizationEnum pol =
          aocommon::Polarization::IndexToStokes(pol_index);
      ImageFilename name(image_name);
      name.SetPolarization(pol);
      reader = std::make_unique<aocommon::FitsReader>(
          name.GetPrefix(settings_) + "-" + filename_kind + ".fits");
      images[pol_index] = Image(reader->ImageWidth(), reader->ImageHeight());
      reader->Read(images[pol_index].Data());
    }

    float* image_ptrs[4] = {images[0].Data(), images[1].Data(),
                            images[2].Data(), images[3].Data()};
    beam_images.ApplyFullStokes(image_ptrs, settings_.primaryBeamLimit);
    for (size_t pol_index = 0; pol_index != 4; ++pol_index) {
      aocommon::PolarizationEnum pol =
          aocommon::Polarization::IndexToStokes(pol_index);
      ImageFilename name(image_name);
      name.SetPolarization(pol);
      writer.SetPolarization(pol);
      writer.Write(name.GetPrefix(settings_) + "-" + filename_kind + "-pb.fits",
                   images[pol_index].Data());
    }
  } else {
    throw std::runtime_error(
        "Primary beam correction can only be performed on Stokes I, "
        "polarizations (XX,YY) or when "
        "imaging all four polarizations.");
  }
}

PrimaryBeamImageSet PrimaryBeam::Load(const ImageFilename& image_name,
                                      const std::set<size_t>& elements) {
  assert(!elements.empty());
  // This function will be called for Stokes I, diagonal or Full Jones
  // correction, so we can assume that the first element is always required:
  assert(*elements.begin() == 0);
  const bool use_squared_beam = settings_.UseFacetCorrections();
  if (use_squared_beam || settings_.gridderType == GridderType::IDG) {
    PrimaryBeamImageSet beam_images;
    // IDG and facet-based imaging produce only a Stokes I beam, and images
    // have already been corrected for the rest. Currently we just load that
    // beam into the 4 diagonal entries. This is a bit wasteful so might
    // require a better strategy for big images.
    ImageFilename pol_name(image_name);
    pol_name.SetPolarization(aocommon::Polarization::StokesI);
    aocommon::FitsReader reader(pol_name.GetBeamPrefix(settings_) + ".fits");
    beam_images[0] =
        Image(settings_.trimmedImageWidth, settings_.trimmedImageHeight);
    reader.Read(beam_images[0].Data());

    // Copy image zero to images on the diagonal (see aocommon::HMC4x4)
    const std::array<size_t, 3> diagonal_entries = {3, 8, 15};
    for (size_t element : diagonal_entries) {
      if (elements.count(element)) beam_images[element] = beam_images[0];
    }
    return beam_images;
  } else {
    PrimaryBeamImageSet beam_images;
    for (size_t element = 0; element != beam_images.NImages(); ++element) {
      if (elements.count(element)) {
        beam_images[element] = wsclean::Load(settings_, image_name, element);
      }
    }
    return beam_images;
  }
}

void PrimaryBeam::MakeUnitary(const ImagingTableEntry& entry,
                              const ImageFilename& image_name,
                              const Settings& settings) {
  const size_t width = settings.trimmedImageWidth;
  const size_t height = settings.trimmedImageHeight;
  const aocommon::CoordinateSystem coordinates{width,
                                               height,
                                               phase_centre_ra_,
                                               phase_centre_dec_,
                                               settings_.pixelScaleX,
                                               settings_.pixelScaleY,
                                               l_shift_,
                                               m_shift_};
  const aocommon::FitsWriter writer = MakeWriter(coordinates, entry);
  const Image image(width, height, 1.0f);
  Logger::Debug << "Writing unitary beam...\n";
  WriteBeamElement(image_name, image, settings, {}, writer);
}

#ifndef HAVE_EVERYBEAM
void PrimaryBeam::MakeOrReuse(const ImageFilename& image_name,
                              const ImagingTableEntry& entry,
                              std::shared_ptr<ImageWeights> image_weights,
                              size_t field_id) {
  throw std::runtime_error(
      "PrimaryBeam correction requested, but the software has been compiled "
      "without EveryBeam. Recompile your software and make sure that "
      "cmake finds the EveryBeam library.");
}
#else
void PrimaryBeam::MakeOrReuse(const ImageFilename& image_name,
                              const ImagingTableEntry& entry,
                              std::shared_ptr<ImageWeights> image_weights,
                              size_t field_id) {
  bool use_existing_beam = false;
  if (settings_.reusePrimaryBeam) {
    ImageFilename first_pol_name(image_name);
    first_pol_name.SetPolarization(image_name.GetPolarization());
    first_pol_name.SetIsImaginary(false);
    std::string f(first_pol_name.GetBeamPrefix(settings_) + "-0.fits");
    if (boost::filesystem::exists(f)) {
      aocommon::FitsReader reader(f);
      if (reader.ImageWidth() == settings_.trimmedImageWidth &&
          reader.ImageHeight() == settings_.trimmedImageHeight) {
        use_existing_beam = true;
        Logger::Info << "File '" << f
                     << "' exists on disk -- reusing files for primary beam.\n";
      } else {
        Logger::Info << "File '" << f
                     << "' exists on disk but has different dimensions. Beam "
                        "will be recreated.\n";
      }
    } else {
      Logger::Info << "Primary beam not yet available (file '" << f
                   << "' does not exist). Beam will be created.\n";
    }
  }
  if (!use_existing_beam) {
    Logger::Info << " == Constructing primary beam ==\n";
    MakeImage(image_name, entry, image_weights, field_id);
  }
}

void PrimaryBeam::MakeImage(const ImageFilename& image_name,
                            const ImagingTableEntry& entry,
                            std::shared_ptr<ImageWeights> image_weights,
                            size_t field_id) {
  const size_t width(settings_.trimmedImageWidth);
  const size_t height(settings_.trimmedImageHeight);

  std::vector<std::unique_ptr<MSProvider>> providers;
  for (size_t i = 0; i != ms_list_.size(); ++i) {
    providers.emplace_back(ms_list_[i]->GetProvider());
    ms_providers_.push_back(
        MSProviderInfo(providers.back().get(), &ms_list_[i]->Selection(), i));
  }

  aocommon::CoordinateSystem coordinates{width,
                                         height,
                                         phase_centre_ra_,
                                         phase_centre_dec_,
                                         settings_.pixelScaleX,
                                         settings_.pixelScaleY,
                                         l_shift_,
                                         m_shift_};

  std::vector<aocommon::HMC4x4> result;
  double ms_weight_sum = 0;
  for (const MSProviderInfo& ms_provider_info : ms_providers_) {
    const MSSelection& selection = *ms_provider_info.selection;
    double central_frequency;
    {
      aocommon::BandData band(ms_provider_info.provider->Band(),
                              selection.ChannelRangeStart(),
                              selection.ChannelRangeEnd());
      central_frequency = band.CentreFrequency();
    }

    std::vector<aocommon::HMC4x4> ms_beam;
    const double ms_weight =
        MakeBeamForMS(ms_beam, *ms_provider_info.provider, selection,
                      *image_weights, coordinates, central_frequency, field_id);
    if (result.empty()) {
      result = std::move(ms_beam);
      if (ms_weight > 0.0) {
        for (aocommon::HMC4x4& m : result) {
          m *= ms_weight;
        }
        ms_weight_sum += ms_weight;
      } else {
        // In case of a zero weight beam, the beam might contain NaNs or be
        // otherwise bad, so explicitly set it to zero.
        result.assign(result.size(), aocommon::HMC4x4::Zero());
      }
    } else if (ms_weight > 0.0) {
      assert(ms_beam.size() == result.size());
      for (size_t i = 0; i != result.size(); ++i) {
        result[i] += ms_beam[i] * ms_weight;
      }
      ms_weight_sum += ms_weight;
    }
  }

  // Apply MS weights
  for (size_t i = 0; i != result.size(); ++i) {
    result[i] /= ms_weight_sum;
  }

  WriteBeamImages(image_name, std::move(result), settings_, entry, coordinates,
                  undersample_);
}

double PrimaryBeam::MakeBeamForMS(
    std::vector<aocommon::HMC4x4>& result, MSProvider& ms_provider,
    const MSSelection& selection, const ImageWeights& image_weights,
    const aocommon::CoordinateSystem& coordinateSystem,
    double central_frequency, size_t field_id) {
  Logger::Debug << "Counting timesteps...\n";
  const std::vector<BeamInterval> intervals =
      GetBeamIntervals(ms_provider, seconds_before_beam_update_);
  Logger::Debug << "Dividing MS in " << intervals.size() << " intervals.\n";

  SynchronizedMS ms = ms_provider.MS();
  const everybeam::TelescopeType telescope_type =
      everybeam::GetTelescopeType(*ms);
  std::unique_ptr<everybeam::telescope::Telescope> telescope =
      PrepareEveryBeam(ms, settings_, telescope_type);

  casacore::MEpoch::ScalarColumn time_column(
      *ms, ms->columnName(casacore::MSMainEnums::TIME));
  std::size_t n_baselines =
      telescope->GetNrStations() * (telescope->GetNrStations() + 1) / 2;

  std::unique_ptr<everybeam::griddedresponse::GriddedResponse> grid_response =
      telescope->GetGriddedResponse(coordinateSystem);

  // Time array and baseline weights only relevant for LOFAR, MWA and SKA.
  // MWA beam needs scrutiny, this telescope might be amenable to a
  // more efficient implementation
  double ms_weight = 0;
  switch (telescope_type) {
    // These are the telescopes that require time information OR are
    // heterogenous (not all antennas have the same response)
    case everybeam::TelescopeType::kLofarTelescope:
    case everybeam::TelescopeType::kAARTFAAC:
    case everybeam::TelescopeType::kMWATelescope:
    case everybeam::TelescopeType::kOSKARTelescope:
    case everybeam::TelescopeType::kSkaMidTelescope:
    case everybeam::TelescopeType::kOvroLwaTelescope: {
      std::vector<double> baseline_weights(n_baselines * intervals.size(), 0.0);
      std::vector<double> time_array(intervals.size(), 0.0);
      // Loop over the intervalCounts
      std::unique_ptr<MSReader> reader = ms_provider.MakeReader();
      size_t current_row = 0;

      for (size_t interval_index = 0; interval_index != intervals.size();
           ++interval_index) {
        const BeamInterval& interval = intervals[interval_index];

        // Skip to the start row
        while (current_row < interval.start_row) {
          reader->NextInputRow();
          ++current_row;
        }

        // Set value in time array
        time_array[interval_index] = interval.central_time;

        WeightMatrix weights(telescope->GetNrStations());
        CalculateStationWeights(image_weights, weights, ms, *reader, selection,
                                current_row, interval.end_row);

        // Get the baseline weights from the baseline_weight matrix
        aocommon::UVector<double> interval_weights =
            weights.GetBaselineWeights();
        std::copy(interval_weights.begin(), interval_weights.end(),
                  baseline_weights.begin() + n_baselines * interval_index);
      }
      // Compute MS weight
      ms_weight = std::accumulate(baseline_weights.begin(),
                                  baseline_weights.end(), 0.0);
      const bool use_squared_beam = settings_.UseFacetCorrections();
      result = grid_response->UndersampledIntegratedCorrection(
          beam_mode_, time_array, central_frequency, field_id, undersample_,
          baseline_weights, use_squared_beam);
    } break;
    // Using 'default:' gives compatibility with different EveryBeam versions
    default: {
      if (telescope_type == everybeam::TelescopeType::kATCATelescope ||
          telescope_type == everybeam::TelescopeType::kGMRTTelescope) {
        Logger::Warn << "Warning: ATCA and GMRT primary beam corrections have "
                        "not yet been tested extensively!\n";
      }
      // The dish response is time independent, so leaving zero is fine:
      std::vector<double> time_array(1, 0);
      // A weight of 1 is used for these time independent telescopes
      ms_weight = 1.0;
      // baseline weights have no effect on homogeneous arrays, so leave at 1
      std::vector<double> baseline_weights(n_baselines, 1);
      if (settings_.fieldIds[0] == MSSelection::kAllFields) {
        Logger::Warn
            << "Warning: primary beam correction together with '-fields "
               "ALL' is not properly supported\n";
        Logger::Warn << "       : The beam will be calculated only for the "
                        "first field!\n";
      }
      result = grid_response->UndersampledIntegratedCorrection(
          beam_mode_, time_array, central_frequency, field_id, undersample_,
          baseline_weights, false);
    } break;
    case everybeam::TelescopeType::kUnknownTelescope:
      throw std::runtime_error(
          "Unknown telescope type! If your telescope is supposed to be "
          "supported, try upgrading EveryBeam.");
  }

  return ms_weight;
}

void PrimaryBeam::CalculateStationWeights(const ImageWeights& imageWeights,
                                          WeightMatrix& baselineWeights,
                                          SynchronizedMS& ms,
                                          MSReader& ms_reader,
                                          const MSSelection& selection,
                                          size_t& current_row, size_t end_row) {
  casacore::MSAntenna antenna_table(ms->antenna());
  aocommon::UVector<double> per_antenna_weights(antenna_table.nrow(), 0.0);

  aocommon::MultiBandData multi_band(ms->spectralWindow(),
                                     ms->dataDescription());
  const size_t n_channels =
      selection.ChannelRangeEnd() - selection.ChannelRangeStart();
  const size_t n_polarizations = ms_reader.NPolarizations();
  aocommon::UVector<float> weight_array(n_channels * n_polarizations);
  const aocommon::BandData band = multi_band[ms_reader.DataDescId()];
  while (ms_reader.CurrentRowAvailable() && current_row <= end_row) {
    MSProvider::MetaData meta_data;
    ms_reader.ReadMeta(meta_data);
    ms_reader.ReadWeights(weight_array.data());

    for (size_t ch = 0; ch != n_channels; ++ch) {
      const double u = meta_data.uInM / band.ChannelWavelength(ch);
      const double v = meta_data.vInM / band.ChannelWavelength(ch);
      const double iw = imageWeights.GetWeight(u, v);
      const double w = weight_array[ch * n_polarizations] * iw;
      baselineWeights.Value(meta_data.antenna1, meta_data.antenna2) += w;
    }
    ms_reader.NextInputRow();
    ++current_row;
  }
}

#endif  // HAVE_EVERYBEAM

size_t PrimaryBeam::computeUndersamplingFactor(const Settings& settings) {
  return std::max(
      std::min(settings.trimmedImageWidth / settings.primaryBeamGridSize,
               settings.trimmedImageHeight / settings.primaryBeamGridSize),
      (size_t)1);
}

}  // namespace wsclean