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
|
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef RADLER_ALGORITHMS_SUB_MINOR_LOOP_H_
#define RADLER_ALGORITHMS_SUB_MINOR_LOOP_H_
#include <cstring>
#include <vector>
#include <aocommon/image.h>
#include <aocommon/logger.h>
#include <aocommon/optionalnumber.h>
#include "component_list.h"
#include "image_set.h"
namespace radler::algorithms {
/**
* In multi-scale, a subminor optimized loop looks like this:
*
* IterateAndMakeModel():
* - Make a set S with positions of all the components larger than 'threshold',
* which are also in the mask
* - Find the largest component in S
* Loop {
* - Measure the largest component per frequency (from S)
* - Store the model component in S
* - Subtract this component multiplied with the twice convolved PSF and minor
* loop gain from all components in S (per individual image)
* - Find the new largest component in S
* }
*
* CorrectResidualDirty():
* For each individual image {
* - Put the model components from S onto a full image (using
* GetFullIndividualModel())
* - Convolve the model with the SingleConvolvedPSF
* - Subtract the convolved model from the residual
* }
*
* Finalization:
* - Put the model components from S onto a full image (using
* GetFullIndividualModel())
* - Convolve the model image with the scale kernel
* - Add the model components to the full model
*
* A subminor loop has some correspondance with the so-called Clark
* optimization. However, this implementation has some differences, e.g. by
* collecting a list of threshold components prior of entering the subminor
* loop.
*/
class SubMinorModel {
public:
SubMinorModel(size_t width, size_t /*height*/) : _width(width) {}
SubMinorModel(SubMinorModel&&) = default;
SubMinorModel& operator=(SubMinorModel&&) = default;
void AddPosition(size_t x, size_t y) {
_positions.push_back(std::make_pair(x, y));
}
/**
* Return number of selected pixels.
*/
size_t size() const { return _positions.size(); }
void MakeSets(const ImageSet& residual_set);
void MakeRmsFactorImage(aocommon::Image& rms_factor_image);
ImageSet& Residual() { return *_residual; }
const ImageSet& Residual() const { return *_residual; }
ImageSet& Model() { return *_model; }
const ImageSet& Model() const { return *_model; }
size_t X(size_t index) const { return _positions[index].first; }
size_t Y(size_t index) const { return _positions[index].second; }
size_t FullIndex(size_t index) const { return X(index) + Y(index) * _width; }
template <bool AllowNegatives>
size_t GetMaxComponent(aocommon::Image& scratch, float& max_value) const;
size_t GetMaxComponent(aocommon::Image& scratch, float& max_value,
bool allowNegatives) const {
if (allowNegatives)
return GetMaxComponent<true>(scratch, max_value);
else
return GetMaxComponent<false>(scratch, max_value);
}
private:
std::vector<std::pair<size_t, size_t>> _positions;
std::unique_ptr<ImageSet> _residual, _model;
aocommon::Image _rmsFactorImage;
size_t _width;
};
class SubMinorLoop {
public:
SubMinorLoop(size_t width, size_t height, size_t padded_width,
size_t padded_height, aocommon::LogReceiver& log_receiver)
: _width(width),
_height(height),
_paddedWidth(padded_width),
_paddedHeight(padded_height),
_threshold(0.0),
_consideredPixelThreshold(0.0),
_gain(0.0),
_horizontalBorder(0),
_verticalBorder(0),
_currentIteration(0),
_maxIterations(0),
_allowNegativeComponents(true),
_stopOnNegativeComponent(false),
_mask(nullptr),
_parentAlgorithm(nullptr),
_subMinorModel(width, height),
_fluxCleaned(0.0),
_logReceiver(log_receiver) {}
SubMinorLoop(SubMinorLoop&&) = default;
/**
* @param threshold The threshold to which this subminor run should clean
* @param considered_pixel_threshold The threshold that is used to determine
* whether a pixel is considered. Typically, this is similar to threshold, but
* it can be set lower if it is important that all peak values are below the
* threshold, as otherwise some pixels might not be considered but get
* increased by the cleaning, thereby stay above the threshold. This is
* important for making multi-scale clean efficient near a stopping threshold.
*/
void SetThreshold(float threshold, float considered_pixel_threshold) {
_threshold = threshold;
_consideredPixelThreshold = considered_pixel_threshold;
}
void SetIterationInfo(size_t current_iteration, size_t max_iterations) {
_currentIteration = current_iteration;
_maxIterations = max_iterations;
}
void SetGain(float minor_loop_gain) { _gain = minor_loop_gain; }
void SetAllowNegativeComponents(bool allow_negative_components) {
_allowNegativeComponents = allow_negative_components;
}
void SetStopOnNegativeComponent(bool stop_on_negative_component) {
_stopOnNegativeComponent = stop_on_negative_component;
}
void SetParentAlgorithm(DeconvolutionAlgorithm* parent_algorithm) {
_parentAlgorithm = parent_algorithm;
}
void SetCleanBorders(size_t horizontal_border, size_t vertical_border) {
_horizontalBorder = horizontal_border;
_verticalBorder = vertical_border;
}
void SetMask(const bool* mask) { _mask = mask; }
void SetRmsFactorImage(const aocommon::Image& image) {
_rmsFactorImage = image;
}
void SetDivergenceLimit(float divergence_limit) {
_divergenceLimit = divergence_limit;
}
size_t CurrentIteration() const { return _currentIteration; }
float FluxCleaned() const { return _fluxCleaned; }
/**
* @returns a pair for which the first value specifies whether divergence has
* taken place, and the second value is the peak on which the algorithm
* finished, if a peak exists.
*/
std::pair<bool, aocommon::OptionalNumber<float>> Run(
ImageSet& convolvedResidual,
const std::vector<aocommon::Image>& twiceConvolvedPsfs);
/**
* The produced model is convolved with the given psf, and the result is
* subtracted from the given residual image. To be called after Run(). After
* this method, the residual will hold the result of the subminor loop run.
* @p scratch_a and @p scratch_b need to be able to store the full padded
* image (_paddedWidth x _paddedHeight). @p scratch_c only needs to
* store the trimmed size (_width x _height).
*/
void CorrectResidualDirty(float* scratch_a, float* scratch_b,
float* scratch_c, size_t image_index,
float* residual,
const float* single_convolved_psf) const;
void GetFullIndividualModel(size_t image_index,
float* individualModelImg) const;
void UpdateAutoMask(bool* mask) const;
void UpdateComponentList(ComponentList& list, size_t scale_index) const;
private:
void findPeakPositions(ImageSet& convolvedResidual);
size_t _width, _height, _paddedWidth, _paddedHeight;
float _threshold, _consideredPixelThreshold, _gain;
size_t _horizontalBorder, _verticalBorder;
size_t _currentIteration, _maxIterations;
bool _allowNegativeComponents, _stopOnNegativeComponent;
const bool* _mask;
/**
* A value of 0 means that divergence checking is disabled.
* @see Settings::divergence_limit for more info.
*/
float _divergenceLimit = 0.0f;
/**
* The parent algorithm is used to perform spectral fitting.
*/
DeconvolutionAlgorithm* _parentAlgorithm;
SubMinorModel _subMinorModel;
float _fluxCleaned;
aocommon::Image _rmsFactorImage;
aocommon::LogReceiver& _logReceiver;
};
} // namespace radler::algorithms
#endif // RADLER_ALGORITHMS_SUB_MINOR_LOOP_H_
|