Skip to content
Snippets Groups Projects
Select Git revision
  • 6e39c8dd30f1940e4a677fdeb9abb4d162ab8c61
  • main default protected
  • pixel-fitter
  • remove-pybind11
  • multiscale_cuda
  • profiling
  • wavelet-deconvolution
  • ast-1102-nanobind
  • temp-fix-for-local-rms-multiscale-crash
  • ast-943-use-central-frequency-only
  • add-sphinx
  • test-radler-as-static
12 results

image_set.h

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    image_set.h 11.69 KiB
    // Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
    // SPDX-License-Identifier: GPL-3.0-or-later
    
    #ifndef RADLER_IMAGE_SET_H_
    #define RADLER_IMAGE_SET_H_
    
    #include <map>
    #include <memory>
    #include <vector>
    
    #include <aocommon/image.h>
    #include <aocommon/uvector.h>
    
    #include <schaapcommon/fitters/spectralfitter.h>
    
    #include "deconvolution_table.h"
    
    namespace radler {
    
    class ImageSet {
     public:
      ImageSet(const DeconvolutionTable& table, bool squared_joins,
               const std::set<aocommon::PolarizationEnum>& linked_polarizations,
               size_t width, size_t height);
    
      /**
       * Constructs an ImageSet with the same settings as an existing ImageSet
       * object, but a different image size.
       *
       * @param image_set An existing ImageSet.
       * @param width The image width for the new ImageSet.
       * @param height The image height for the new ImageSet.
       */
      ImageSet(const ImageSet& image_set, size_t width, size_t height);
    
      ImageSet(const ImageSet&) = delete;
      ImageSet& operator=(const ImageSet&) = delete;
    
      aocommon::Image Release(size_t imageIndex) {
        return std::move(_images[imageIndex]);
      }
    
      void SetImage(size_t imageIndex, aocommon::Image&& data) {
        assert(data.Width() == Width() && data.Height() == Height());
        _images[imageIndex] = std::move(data);
      }
    
      /**
       * Replace the images of this ImageSet. The images may be of a different size.
       * Both ImageSets are expected to be for the same deconvolution configuration:
       * besides the images and their dimension, no fields are changed.
       */
      void SetImages(ImageSet&& source);
    
      /**
       * @param use_residual_images: True: Load residual images. False: Load model
       * images.
       */
      void LoadAndAverage(bool use_residual_images);
    
      std::vector<aocommon::Image> LoadAndAveragePSFs();
    
      void InterpolateAndStoreModel(
          const schaapcommon::fitters::SpectralFitter& fitter, size_t threadCount);
    
      void AssignAndStoreResidual();
    
      /**
       * This function will calculate the integration over all images, squaring
       * images that are in the same squared-image group. For example, with
       * a squared group of [I, Q, ..] and another group [I2, Q2, ...], this
       * will calculate:
       *
       * sqrt(I^2 + Q^2 + ..) + sqrt(I2^2 + Q2^2 ..) + ..
       * ----------------------------------------------
       *           1          +           1          + ..
       *
       * If the 'squared groups' are of size 1, the average of the groups will be
       * returned (i.e., without square-rooting the square).
       *
       * If the squared joining option is set in the provided wsclean settings, the
       * behaviour of this method changes. In that case, it will return the square
       * root of the average squared value:
       *
       *       I^2 + Q^2 + ..  +  I2^2 + Q2^2 ..  + ..
       * sqrt( --------------------------------------- )
       *            1          +        1         + ..
       *
       * These formulae are such that the values will have normal flux values.
       * @param dest Pre-allocated output array that will be filled with the
       * integrated image.
       * @param scratch Pre-allocated scratch space, same size as image.
       */
      void GetSquareIntegrated(aocommon::Image& dest,
                               aocommon::Image& scratch) const {
        if (_squareJoinedChannels)
          getSquareIntegratedWithSquaredChannels(dest);
        else
          getSquareIntegratedWithNormalChannels(dest, scratch);
      }
    
      /**
       * This function will calculate the 'linear' integration over all images,
       * unless joined channels are requested to be squared. The method will return
       * the weighted average of all images. Normally,
       * @ref GetSquareIntegrated
       * should be used for peak finding, but in case negative values should remain
       * negative, such as with multiscale (otherwise a sidelobe will be fitted with
       * large scales), this function can be used.
       * @param dest Pre-allocated output array that will be filled with the average
       * values.
       */
      void GetLinearIntegrated(aocommon::Image& dest) const {
        if (_squareJoinedChannels)
          getSquareIntegratedWithSquaredChannels(dest);
        else
          getLinearIntegratedWithNormalChannels(dest);
      }
    
      void GetIntegratedPSF(aocommon::Image& dest,
                            const std::vector<aocommon::Image>& psfs);
    
      size_t NOriginalChannels() const {
        return _deconvolutionTable.OriginalGroups().size();
      }
    
      size_t PSFCount() const { return NDeconvolutionChannels(); }
    
      size_t NDeconvolutionChannels() const {
        return _deconvolutionTable.DeconvolutionGroups().size();
      }
    
      ImageSet& operator=(float val) {
        for (aocommon::Image& image : _images) image = val;
        return *this;
      }
    
      /**
       * Exposes image data.
       *
       * ImageSet only exposes a non-const pointer to the image data. When exposing
       * non-const reference to the images themselves, the user could change the
       * image size and violate the invariant that all images have equal sizes.
       * @param index An image index.
       * @return A non-const pointer to the data area for the image.
       */
      float* Data(size_t index) { return _images[index].Data(); }
    
      /**
       * Exposes the images in the image set.
       *
       * Creating a non-const version of this operator is not desirable. See Data().
       *
       * @param index An image index.
       * @return A const reference to the image with the given index.
       */
      const aocommon::Image& operator[](size_t index) const {
        return _images[index];
      }
    
      size_t size() const { return _images.size(); }
    
      size_t PSFIndex(size_t imageIndex) const {
        return _imageIndexToPSFIndex[imageIndex];
      }
    
      const DeconvolutionTable& Table() const { return _deconvolutionTable; }
    
      std::unique_ptr<ImageSet> Trim(size_t x1, size_t y1, size_t x2, size_t y2,
                                     size_t oldWidth) const {
        auto p = std::make_unique<ImageSet>(*this, x2 - x1, y2 - y1);
        for (size_t i = 0; i != _images.size(); ++i) {
          copySmallerPart(_images[i], p->_images[i], x1, y1, x2, y2, oldWidth);
        }
        return p;
      }
    
      /**
       * Like Trim(), but only copies values that are in the mask. All other values
       * are set to zero.
       * @param mask A mask of size (x2-x1) x (y2-y1)
       */
      std::unique_ptr<ImageSet> TrimMasked(size_t x1, size_t y1, size_t x2,
                                           size_t y2, size_t oldWidth,
                                           const bool* mask) const {
        std::unique_ptr<ImageSet> p = Trim(x1, y1, x2, y2, oldWidth);
        for (aocommon::Image& image : p->_images) {
          for (size_t pixel = 0; pixel != image.Size(); ++pixel) {
            if (!mask[pixel]) image[pixel] = 0.0;
          }
        }
        return p;
      }
    
      void CopyMasked(const ImageSet& fromImageSet, size_t toX, size_t toY,
                      const bool* fromMask) {
        for (size_t i = 0; i != _images.size(); ++i) {
          aocommon::Image::CopyMasked(
              _images[i].Data(), toX, toY, _images[i].Width(),
              fromImageSet._images[i].Data(), fromImageSet._images[i].Width(),
              fromImageSet._images[i].Height(), fromMask);
        }
      }
    
      /**
       * Place all images in @c from onto the images in this ImageSet at a
       * given position. The dimensions of @c from can be smaller or equal
       * to ones in this.
       */
      void AddSubImage(const ImageSet& from, size_t toX, size_t toY) {
        for (size_t i = 0; i != _images.size(); ++i) {
          aocommon::Image::AddSubImage(_images[i].Data(), toX, toY,
                                       _images[i].Width(), from._images[i].Data(),
                                       from._images[i].Width(),
                                       from._images[i].Height());
        }
      }
    
      ImageSet& operator*=(float factor) {
        for (aocommon::Image& image : _images) image *= factor;
        return *this;
      }
    
      ImageSet& operator+=(const ImageSet& other) {
        for (size_t i = 0; i != size(); ++i) _images[i] += other._images[i];
        return *this;
      }
    
      void FactorAdd(ImageSet& rhs, double factor) {
        for (size_t i = 0; i != size(); ++i)
          _images[i].AddWithFactor(rhs._images[i], factor);
      }
    
      bool SquareJoinedChannels() const { return _squareJoinedChannels; }
    
      const std::set<aocommon::PolarizationEnum>& LinkedPolarizations() const {
        return _linkedPolarizations;
      }
    
      size_t Width() const { return _images.front().Width(); }
    
      size_t Height() const { return _images.front().Height(); }
    
      static void CalculateDeconvolutionFrequencies(
          const DeconvolutionTable& groupTable,
          aocommon::UVector<double>& frequencies,
          aocommon::UVector<float>& weights);
    
     private:
      void initializeIndices();
    
      void initializePolFactor() {
        const DeconvolutionTable::Group& firstChannelGroup =
            _deconvolutionTable.OriginalGroups().front();
        std::set<aocommon::PolarizationEnum> pols;
        bool all_stokes_without_i = true;
        for (const DeconvolutionTableEntry* entry : firstChannelGroup) {
          if (_linkedPolarizations.empty() ||
              _linkedPolarizations.count(entry->polarization) != 0) {
            if (!aocommon::Polarization::IsStokes(entry->polarization) ||
                entry->polarization == aocommon::Polarization::StokesI)
              all_stokes_without_i = false;
            pols.insert(entry->polarization);
          }
        }
        const bool isDual =
            pols.size() == 2 && aocommon::Polarization::HasDualPolarization(pols);
        const bool isFull =
            pols.size() == 4 &&
            (aocommon::Polarization::HasFullLinearPolarization(pols) ||
             aocommon::Polarization::HasFullCircularPolarization(pols));
        if (all_stokes_without_i)
          _polarizationNormalizationFactor = 1.0 / pols.size();
        else if (isDual || isFull)
          _polarizationNormalizationFactor = 0.5;
        else
          _polarizationNormalizationFactor = 1.0;
      }
    
      static void copySmallerPart(const aocommon::Image& input,
                                  aocommon::Image& output, size_t x1, size_t y1,
                                  size_t x2, size_t y2, size_t oldWidth) {
        size_t newWidth = x2 - x1;
        for (size_t y = y1; y != y2; ++y) {
          const float* oldPtr = &input[y * oldWidth];
          float* newPtr = &output[(y - y1) * newWidth];
          for (size_t x = x1; x != x2; ++x) {
            newPtr[x - x1] = oldPtr[x];
          }
        }
      }
    
      static void copyToLarger(aocommon::Image& to, size_t toX, size_t toY,
                               size_t toWidth, const aocommon::Image& from,
                               size_t fromWidth, size_t fromHeight) {
        for (size_t y = 0; y != fromHeight; ++y) {
          std::copy(from.Data() + y * fromWidth, from.Data() + (y + 1) * fromWidth,
                    to.Data() + toX + (toY + y) * toWidth);
        }
      }
    
      static void copyToLarger(aocommon::Image& to, size_t toX, size_t toY,
                               size_t toWidth, const aocommon::Image& from,
                               size_t fromWidth, size_t fromHeight,
                               const bool* fromMask) {
        for (size_t y = 0; y != fromHeight; ++y) {
          for (size_t x = 0; x != fromWidth; ++x) {
            if (fromMask[y * fromWidth + x])
              to[toX + (toY + y) * toWidth + x] = from[y * fromWidth + x];
          }
        }
      }
    
      void getSquareIntegratedWithNormalChannels(aocommon::Image& dest,
                                                 aocommon::Image& scratch) const;
    
      void getSquareIntegratedWithSquaredChannels(aocommon::Image& dest) const;
    
      void getLinearIntegratedWithNormalChannels(aocommon::Image& dest) const;
    
      const aocommon::Image& entryToImage(
          const DeconvolutionTableEntry& entry) const {
        return _images[_entryIndexToImageIndex[entry.index]];
      }
    
      std::vector<aocommon::Image> _images;
      // Weight of each deconvolution channels
      aocommon::UVector<float> _weights;
      bool _squareJoinedChannels;
      const DeconvolutionTable& _deconvolutionTable;
      std::vector<size_t> _entryIndexToImageIndex;
      aocommon::UVector<size_t> _imageIndexToPSFIndex;
      float _polarizationNormalizationFactor;
      std::set<aocommon::PolarizationEnum> _linkedPolarizations;
    };
    }  // namespace radler
    #endif  // RADLER_IMAGE_SET_H_