Mantid
Loading...
Searching...
No Matches
GetDetectorOffsets.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
8#include "MantidAPI/Axis.h"
18
19namespace Mantid::Algorithms {
20
21// Register the class into the algorithm factory
22DECLARE_ALGORITHM(GetDetectorOffsets)
23
24using namespace Kernel;
26using namespace Algorithms::PeakParameterHelper;
27using namespace API;
28using std::size_t;
29using namespace DataObjects;
30
31//-----------------------------------------------------------------------------------------
35
36 declareProperty(std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input),
37
38 "A 2D workspace with X values of d-spacing");
39
40 auto mustBePositive = std::make_shared<BoundedValidator<double>>();
41 mustBePositive->setLower(0);
42
43 declareProperty("Step", 0.001, mustBePositive, "Step size used to bin d-spacing data");
44 declareProperty("DReference", 2.0, mustBePositive, "Center of reference peak in d-space");
45 declareProperty("XMin", 0.0, "Minimum of CrossCorrelation data to search for peak, usually negative");
46 declareProperty("XMax", 0.0, "Maximum of CrossCorrelation data to search for peak, usually positive");
47
48 declareProperty(std::make_unique<FileProperty>("GroupingFileName", "", FileProperty::OptionalSave, ".cal"),
49 "Optional: The name of the output CalFile to save the "
50 "generated OffsetsWorkspace.");
51 declareProperty(std::make_unique<WorkspaceProperty<OffsetsWorkspace>>("OutputWorkspace", "", Direction::Output),
52 "An output workspace containing the offsets.");
53
54 // Mantid's python API _requires_ a non empty-string name for any Output workspace, even when 'PropertyMode::Optional'
55 // is specified.
56 declareProperty(std::make_unique<WorkspaceProperty<MaskWorkspace>>("MaskWorkspace", "_empty_", Direction::Output,
58 "Mask workspace (optional input / output workspace):"
59 " when specified, if the workspace already exists, any incoming masked detectors will be combined"
60 " with any additional outgoing masked detectors detected by the algorithm");
61
62 // Only keep peaks
63 declareProperty("PeakFunction", "Gaussian",
64 std::make_shared<StringListValidator>(FunctionFactory::Instance().getFunctionNames<IPeakFunction>()),
65 "The function type for fitting the peaks.");
66 declareProperty(std::make_unique<Kernel::PropertyWithValue<bool>>("EstimateFWHM", false),
67 "Whether to esimate FWHM of peak function when estimating fit parameters");
68 declareProperty("MaxOffset", 1.0, "Maximum absolute value of offsets; default is 1");
69
70 /* Signed mode calculates offset in number of bins */
71 std::vector<std::string> modes{"Relative", "Absolute", "Signed"};
72
73 declareProperty("OffsetMode", "Relative", std::make_shared<StringListValidator>(modes),
74 "Whether to calculate a relative, absolute, or signed offset");
75 declareProperty("DIdeal", 2.0, mustBePositive,
76 "The known peak centre value from the NIST standard "
77 "information, this is only used in Absolute OffsetMode.");
78}
79
80std::map<std::string, std::string> GetDetectorOffsets::validateInputs() {
81 std::map<std::string, std::string> result;
82
83 MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
84 if (!inputWS) {
85 result["InputWorkspace"] = "The InputWorkspace must be a MatrixWorkspace.";
86 return result;
87 }
88
89 const auto unit = inputWS->getAxis(0)->unit()->caption();
90 if (unit != "Bins of Shift" && unit != "d-Spacing") {
91 const auto unitErrorMsg = "GetDetectorOffsets only supports input workspaces with units 'Bins of Shift' or "
92 "'d-Spacing', your unit was : " +
93 unit;
94 result["InputWorkspace"] = unitErrorMsg;
95 return result;
96 }
97
98 if (MaskWorkspace_const_sptr maskWS = getProperty("MaskWorkspace")) {
99 // detectors which are monitors are not included in the mask
100 if (maskWS->getInstrument()->getNumberDetectors(true) != inputWS->getInstrument()->getNumberDetectors(true)) {
101 result["MaskWorkspace"] = "incoming mask workspace must have the same instrument as the input workspace";
102 } else if (maskWS->getNumberHistograms() != inputWS->getInstrument()->getNumberDetectors(true)) {
103 result["MaskWorkspace"] = "incoming mask workspace must have one spectrum per detector";
104 }
105 }
106 return result;
107}
108//-----------------------------------------------------------------------------------------
115 inputW = getProperty("InputWorkspace");
116 m_Xmin = getProperty("XMin");
117 m_Xmax = getProperty("XMax");
118 m_maxOffset = getProperty("MaxOffset");
119 if (m_Xmin >= m_Xmax)
120 throw std::runtime_error("Must specify m_Xmin<m_Xmax");
121 m_dreference = getProperty("DReference");
122 m_step = getProperty("Step");
123 m_estimateFWHM = getProperty("EstimateFWHM");
124
125 std::string mode_str = getProperty("OffsetMode");
126
127 if (mode_str == "Absolute") {
129 }
130
131 else if (mode_str == "Relative") {
133 }
134
135 else if (mode_str == "Signed") {
137 }
138
139 m_dideal = getProperty("DIdeal");
140
141 int64_t nspec = static_cast<int64_t>(inputW->getNumberHistograms());
142
143 // Create the output OffsetsWorkspace and initialize it to zero.
144 auto outputW = std::make_shared<OffsetsWorkspace>(inputW->getInstrument());
145 size_t N_d = outputW->getNumberHistograms();
146 for (size_t di = 0; di < N_d; ++di)
147 outputW->mutableY(di) = 0.0; // calls detail::FixedLengthVector::assign
148
149 // Use the incoming mask workspace, or start a new one if the workspace does not exist.
150 MaskWorkspace_sptr maskWS;
151 if (!isDefault("MaskWorkspace")) {
152 maskWS = getProperty("MaskWorkspace");
153 }
154 if (!maskWS) {
155 g_log.debug() << "[GetDetectorOffsets]: CREATING new MaskWorkspace.\n";
156 // A new mask is completely cleared at creation.
157 maskWS = std::make_shared<MaskWorkspace>(inputW->getInstrument());
158 } else {
159 g_log.debug() << "[GetDetectorOffsets]: Using EXISTING MaskWorkspace.\n";
160 }
161 // Include any incoming masked detector flags in the mask-workspace values.
162 maskWS->combineFromDetectorMasks(inputW->detectorInfo());
163
164 // Fit all the spectra with a gaussian
165 Progress prog(this, 0.0, 1.0, nspec);
167 for (int64_t wi = 0; wi < nspec; ++wi) {
169 // Get the list of detectors in this pixel
170 const auto &dets = inputW->getSpectrum(static_cast<size_t>(wi)).getDetectorIDs();
171
172 // If the entire spectrum is already masked, there's nothing to do.
173 if (maskWS->isMasked(dets))
174 continue;
175
176 // Fit the peak
177 double offset = fitSpectra(static_cast<size_t>(wi));
178 bool spectrumIsMasked = false;
179 if (std::abs(offset) > m_maxOffset) {
180 g_log.debug() << "[GetDetectorOffsets]: fit failure: offset: " << std::abs(offset)
181 << " is greater than maximum allowed: " << m_maxOffset << ".\n";
182 spectrumIsMasked = true;
183 }
184
185 // Most of the exec time is in FitSpectra, so this critical block should not
186 // be a problem.
187 PARALLEL_CRITICAL(GetDetectorOffsets_setValue) {
188 // Use the same offset for all detectors from this pixel
189 for (const auto &det : dets) {
190 if (spectrumIsMasked) {
191 maskWS->setMasked(det, true);
192 continue;
193 }
194 // Warning: individual detectors in a spectrum may be masked.
195 if (!maskWS->isMasked(det))
196 outputW->setValue(det, offset);
197 }
198 }
199 prog.report();
201 }
203 // Make sure that the output workspaces' detector masks are consistent with the mask values.
204 maskWS->combineToDetectorMasks();
205 maskWS->combineToDetectorMasks(outputW->mutableDetectorInfo());
206
207 if (g_log.getLevel() >= Logger::Priority::PRIO_TRACE) {
208 auto &trace(g_log.getLogStream(Logger::Priority::PRIO_TRACE));
209 trace << "[GetDetectorOffsets]: Computed offsets:\n" << std::endl;
210 for (size_t ns = 0; ns < inputW->getNumberHistograms(); ++ns) {
211 const auto dets = inputW->getSpectrum(ns).getDetectorIDs();
212 for (const auto &det : dets)
213 trace << " " << outputW->getValue(det) << (maskWS->isMasked(det) ? "*" : "") << "\n";
214 }
215 }
216 // Return the output
217 setProperty("OutputWorkspace", outputW);
218
219 // Only return the mask workspace if it was specified.
220 if (!isDefault("MaskWorkspace"))
221 setProperty("MaskWorkspace", maskWS);
222
223 // Also save to .cal file, if requested
224 std::string filename = getProperty("GroupingFileName");
225 if (!filename.empty()) {
226 progress(0.9, "Saving .cal file");
227 auto childAlg = createChildAlgorithm("SaveCalFile");
228 childAlg->setProperty("OffsetsWorkspace", outputW);
229 childAlg->setProperty("MaskWorkspace", maskWS);
230 childAlg->setPropertyValue("Filename", filename);
231 childAlg->executeAsChildAlg();
232 }
233}
234
235//-----------------------------------------------------------------------------------------
241double GetDetectorOffsets::fitSpectra(const size_t wksp_index) {
242 constexpr double FIT_FAILURE = DBL_MAX;
243
244 // Find point of peak centre
245 const auto &yValues = inputW->y(wksp_index);
246 auto it = std::max_element(yValues.cbegin(), yValues.cend());
247
248 // Set the default peak height and location
249 double peakHeight = *it;
250 const double peakLoc = inputW->x(wksp_index)[static_cast<size_t>(it - yValues.cbegin())];
251
252 // Return if peak of Cross Correlation is nan (Happens when spectra is zero)
253 // Pixel with large offset will be masked
254 if (std::isnan(peakHeight))
255 return (FIT_FAILURE);
256
257 IFunction_sptr fun_ptr = createFunction(peakHeight, peakLoc);
258
259 // Try to observe the peak height and location
260 const auto &histogram = inputW->histogram(wksp_index);
261 const auto &vector_x = histogram.points();
262 const auto start_index = findXIndex(vector_x, m_Xmin);
263 const auto stop_index = findXIndex(vector_x, m_Xmax, start_index);
264 // observe parameters if we found a peak range, otherwise use defaults
265 if (start_index != stop_index) {
266 // create a background function
267 auto bkgdFunction = std::dynamic_pointer_cast<IBackgroundFunction>(fun_ptr->getFunction(0));
268 auto peakFunction = std::dynamic_pointer_cast<IPeakFunction>(fun_ptr->getFunction(1));
269 int result = estimatePeakParameters(histogram, std::pair<size_t, size_t>(start_index, stop_index), peakFunction,
270 bkgdFunction, m_estimateFWHM, EstimatePeakWidth::Observation, EMPTY_DBL(), 0.0);
271 if (result != PeakFitResult::GOOD) {
272 g_log.debug() << "ws index: " << wksp_index
273 << " bad result for estimating peak parameters, using default peak height and loc\n";
274 }
275 } else {
276 g_log.notice() << "ws index: " << wksp_index
277 << " range size is zero when estimating peak parameters, using default peak height and loc\n";
278 }
279
280 IAlgorithm_sptr fit_alg;
281 try {
282 // set the ChildAlgorithm no to log as this will be run once per spectra
283 fit_alg = createChildAlgorithm("Fit", -1, -1, false);
284 } catch (Exception::NotFoundError &) {
285 g_log.error("Can't locate Fit algorithm");
286 throw;
287 }
288
289 fit_alg->setProperty("Function", fun_ptr);
290
291 fit_alg->setProperty("InputWorkspace", inputW);
292 fit_alg->setProperty<int>("WorkspaceIndex",
293 static_cast<int>(wksp_index)); // TODO what is the right thing to do here?
294 fit_alg->setProperty("StartX", m_Xmin);
295 fit_alg->setProperty("EndX", m_Xmax);
296 fit_alg->setProperty("MaxIterations", 100);
297
298 fit_alg->executeAsChildAlg();
299 std::string fitStatus = fit_alg->getProperty("OutputStatus");
300 // Pixel with large offset will be masked
301 if (fitStatus != "success") {
302 g_log.debug() << "[GetDetectorOffsets]: Fit algorithm failure: " << fitStatus << "\n";
303 return (FIT_FAILURE);
304 }
305
306 // std::vector<double> params = fit_alg->getProperty("Parameters");
307 API::IFunction_sptr function = fit_alg->getProperty("Function");
308
309 double offset = function->getParameter(3); // params[3]; // f1.PeakCentre
310
312 // factor := factor * (1+offset) for d-spacemap conversion so factor cannot be negative
313 offset *= -1;
314 }
315
316 /* offset relative to the reference */
318 // factor := factor * (1+offset) for d-spacemap conversion so factor cannot be negative
319 offset = -1. * offset * m_step / (m_dreference + offset * m_step);
320 }
321
322 /* Offset relative to the ideal */
324
325 offset = -1. * offset * m_step / (m_dreference + offset * m_step);
326
327 // translated from(DIdeal - FittedPeakCentre)/(FittedPeakCentre)
328 // given by Matt Tucker in ticket #10642
329
330 offset += (m_dideal - m_dreference) / m_dreference;
331 }
332
333 return offset;
334}
335
341IFunction_sptr GetDetectorOffsets::createFunction(const double peakHeight, const double peakLoc) {
342 const FunctionFactoryImpl &creator = FunctionFactory::Instance();
343 auto background = creator.createFunction("LinearBackground");
344 auto peak = std::dynamic_pointer_cast<IPeakFunction>(creator.createFunction(getProperty("PeakFunction")));
345 peak->setHeight(peakHeight);
346 peak->setCentre(peakLoc);
347 const double sigma(10.0);
348 peak->setFwhm(2.0 * std::sqrt(2.0 * M_LN2) * sigma);
349
350 auto fitFunc = new CompositeFunction(); // Takes ownership of the functions
351 fitFunc->addFunction(background);
352 fitFunc->addFunction(peak);
353
354 return std::shared_ptr<IFunction>(fitFunc);
355}
356
357} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
double sigma
Definition GetAllEi.cpp:156
#define PARALLEL_START_INTERRUPT_REGION
Begins a block to skip processing is the algorithm has been interupted Note the end of the block if n...
#define PARALLEL_CRITICAL(name)
#define PARALLEL_END_INTERRUPT_REGION
Ends a block to skip processing is the algorithm has been interupted Note the start of the block if n...
#define PARALLEL_FOR_IF(condition)
Empty definitions - to enable set your complier to enable openMP.
#define PARALLEL_CHECK_INTERRUPT_REGION
Adds a check after a Parallel region to see if it was interupted.
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
virtual std::shared_ptr< Algorithm > createChildAlgorithm(const std::string &name, const double startProgress=-1., const double endProgress=-1., const bool enableLogging=true, const int &version=-1)
Create a Child Algorithm.
Kernel::Logger & g_log
Definition Algorithm.h:422
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
bool isDefault(const std::string &name) const
A composite function is a function containing other functions.
@ OptionalSave
to specify a file to write to but an empty string is
The FunctionFactory class is in charge of the creation of concrete instances of fitting functions.
std::shared_ptr< IFunction > createFunction(const std::string &type) const
Creates an instance of a function.
Helper class for reporting progress from algorithms.
Definition Progress.h:25
A property class for workspaces.
API::MatrixWorkspace_sptr inputW
A pointer to the input workspace.
double m_Xmax
The end of the X range for fitting.
API::IFunction_sptr createFunction(const double peakHeight, const double peakLoc)
Create a function string from the given parameters and the algorithm inputs.
double m_dreference
The expected peak position in d-spacing (?)
std::map< std::string, std::string > validateInputs() override
Method checking errors on ALL the inputs, before execution.
double fitSpectra(const size_t wksp_index)
Call Gaussian as a Child Algorithm to fit the peak in a spectrum.
void init() override
Initialisation method.
bool m_estimateFWHM
Flag to estimate fwhm fit parameter.
void exec() override
Executes the algorithm.
double m_Xmin
The start of the X range for fitting.
double m_dideal
The known peak centre value from the NIST standard.
double m_maxOffset
The maximum absolute value of offsets.
Exception for when an item is not found in a collection.
Definition Exception.h:145
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition Logger.h:51
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void notice(const std::string &msg)
Logs at notice level.
Definition Logger.cpp:126
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
std::ostream & getLogStream(const Priority &priority)
gets the correct log stream for a priority
Definition Logger.cpp:404
int getLevel() const
Returns the Logger's log level.
Definition Logger.cpp:217
void report()
Increments the loop counter by 1, then sends the progress notification on behalf of its algorithm.
The concrete, templated class for properties.
std::shared_ptr< IAlgorithm > IAlgorithm_sptr
shared pointer to Mantid::API::IAlgorithm
std::shared_ptr< const MatrixWorkspace > MatrixWorkspace_const_sptr
shared pointer to the matrix workspace base class (const version)
std::shared_ptr< IFunction > IFunction_sptr
shared pointer to the function base class
Definition IFunction.h:743
MANTID_ALGORITHMS_DLL size_t findXIndex(const vector_like &vecx, const double x, const size_t startindex=0)
Get an index of a value in a sorted vector.
MANTID_ALGORITHMS_DLL int estimatePeakParameters(const HistogramData::Histogram &histogram, const std::pair< size_t, size_t > &peak_window, const API::IPeakFunction_sptr &peakfunction, const API::IBackgroundFunction_sptr &bkgdfunction, bool observe_peak_width, const EstimatePeakWidth peakWidthEstimateApproach, const double peakWidthPercentage, const double minPeakHeight)
Estimate peak parameters by 'observation'.
std::shared_ptr< const MaskWorkspace > MaskWorkspace_const_sptr
shared pointer to a const MaskWorkspace
std::shared_ptr< MaskWorkspace > MaskWorkspace_sptr
shared pointer to the MaskWorkspace class
std::enable_if< std::is_pointer< Arg >::value, bool >::type threadSafe(Arg workspace)
Thread-safety check Checks the workspace to ensure it is suitable for multithreaded access.
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54