Mantid
Loading...
Searching...
No Matches
Stitch.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2021 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 +
7
8#include <utility>
9
13#include "MantidAPI/Run.h"
21
22using namespace Mantid::API;
23using namespace Mantid::DataObjects;
24using namespace Mantid::Kernel;
25
26namespace {
27static const std::string INPUT_WORKSPACE_PROPERTY = "InputWorkspaces";
28static const std::string REFERENCE_WORKSPACE_PROPERTY = "ReferenceWorkspace";
29static const std::string SCALE_FACTOR_CALCULATION_PROPERTY = "ScaleFactorCalculation";
30static const std::string MANUAL_SCALE_FACTORS_PROPERTY = "ManualScaleFactors";
31static const std::string TIE_SCALE_FACTORS_PROPERTY = "TieScaleFactors";
32static const std::string OUTPUT_WORKSPACE_PROPERTY = "OutputWorkspace";
33static const std::string OUTPUT_SCALE_FACTORS_PROPERTY = "OutputScaleFactorsWorkspace";
34
42std::pair<double, double> getInterval(const MatrixWorkspace &ws) {
43 return std::make_pair(ws.readX(0).front(), ws.readX(0).back());
44}
45
52bool compareInterval(const MatrixWorkspace_sptr &ws1, const MatrixWorkspace_sptr &ws2) {
53 const auto minmax1 = getInterval(*ws1);
54 const auto minmax2 = getInterval(*ws2);
55 if (minmax1.first < minmax2.first) {
56 return true;
57 } else if (minmax1.first > minmax2.first) {
58 return false;
59 } else {
60 return minmax1.second < minmax2.second;
61 }
62}
63
71std::pair<double, double> getOverlap(const MatrixWorkspace_sptr &ws1, const MatrixWorkspace_sptr &ws2) {
72 const auto minmax1 = getInterval(*ws1);
73 const auto minmax2 = getInterval(*ws2);
74 if (minmax1.second < minmax2.first || minmax2.second < minmax1.first) {
75 std::stringstream ss;
76 ss << "No overlap is found between the intervals: [" << minmax1.first << "," << minmax1.second << "] and ["
77 << minmax2.first << ", " << minmax2.second << "]";
78 throw std::runtime_error(ss.str());
79 }
80 return std::make_pair(std::max(minmax1.first, minmax2.first), std::min(minmax1.second, minmax2.second));
81}
82
88double median(const std::vector<double> &vec) {
89 if (vec.empty())
90 return 1;
91 const size_t s = vec.size();
92 std::vector<double> sorted = vec;
93 std::sort(sorted.begin(), sorted.end());
94 if (s % 2 == 0) {
95 return 0.5 * (sorted[s / 2] + sorted[s / 2 - 1]);
96 } else {
97 return sorted[s / 2];
98 }
99}
100
106MatrixWorkspace_sptr medianWorkspaceLocal(const MatrixWorkspace_sptr &ws) {
107 const size_t nSpectra = ws->getNumberHistograms();
108 MatrixWorkspace_sptr out = WorkspaceFactory::Instance().create("Workspace2D", nSpectra, 1, 1);
109 PARALLEL_FOR_IF(threadSafe(*ws, *out))
110 for (int i = 0; i < static_cast<int>(nSpectra); ++i) {
111 const size_t wsIndex = static_cast<size_t>(i);
112 auto &y = out->mutableY(wsIndex);
113 y = std::vector<double>(1, median(ws->readY(wsIndex)));
114 }
115 return out;
116}
117
123MatrixWorkspace_sptr medianWorkspaceGlobal(const MatrixWorkspace_sptr &ws) {
124 MatrixWorkspace_sptr out = WorkspaceFactory::Instance().create("Workspace2D", 1, 1, 1);
125 std::vector<double> allY;
126 allY.reserve(ws->getNumberHistograms() * ws->blocksize());
127 for (size_t i = 0; i < ws->getNumberHistograms(); ++i) {
128 const auto spectrum = ws->mutableY(i).rawData();
129 std::copy(spectrum.cbegin(), spectrum.cend(), std::back_inserter(allY));
130 }
131 auto &y = out->mutableY(0);
132 y = std::vector<double>(1, median(allY));
133 return out;
134}
135
142MatrixWorkspace_sptr initScaleFactorsWorkspace(const size_t nSpectra, const size_t nPoints) {
143 MatrixWorkspace_sptr ws = WorkspaceFactory::Instance().create("Workspace2D", nSpectra, nPoints, nPoints);
145 for (int i = 0; i < static_cast<int>(nSpectra); ++i) {
146 ws->mutableY(static_cast<size_t>(i)) = std::vector<double>(nPoints, 1.);
147 }
148 return ws;
149}
150
151} // namespace
152
153namespace Mantid::Algorithms {
154
155// Register the algorithm into the AlgorithmFactory
156DECLARE_ALGORITHM(Stitch)
157
158//----------------------------------------------------------------------------------------------
159
160
161const std::string Stitch::name() const { return "Stitch"; }
162
164int Stitch::version() const { return 1; }
165
167const std::string Stitch::category() const { return "Transforms\\Merging"; }
168
170const std::string Stitch::summary() const { return "Stitches overlapping spectra from multiple 2D workspaces."; }
171
173std::map<std::string, std::string> Stitch::validateInputs() {
174 std::map<std::string, std::string> issues;
175 const std::vector<std::string> inputs_given = getProperty(INPUT_WORKSPACE_PROPERTY);
176 std::vector<std::string> workspaces;
177 RunCombinationHelper combHelper;
178 try {
179 workspaces = combHelper.unWrapGroups(inputs_given);
180 } catch (const std::exception &e) {
181 issues[INPUT_WORKSPACE_PROPERTY] = std::string(e.what());
182 return issues;
183 }
184 if (workspaces.size() < 2) {
185 issues[INPUT_WORKSPACE_PROPERTY] = "Please provide at least 2 workspaces to stitch.";
186 return issues;
187 }
188 if (getPropertyValue(SCALE_FACTOR_CALCULATION_PROPERTY) == "Manual") {
189 const std::vector<double> factors = getProperty(MANUAL_SCALE_FACTORS_PROPERTY);
190 if (factors.size() != workspaces.size()) {
191 issues[MANUAL_SCALE_FACTORS_PROPERTY] = "If manual scale factors are requested, the number of scale factors must "
192 "match the number of input workspaces.";
193 }
194 }
195 try {
196 const auto first = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(workspaces.front());
197 combHelper.setReferenceProperties(first);
198 } catch (const std::exception &e) {
199 issues[INPUT_WORKSPACE_PROPERTY] =
200 std::string("Please provide MatrixWorkspaces as or groups of those as input: ") + e.what();
201 return issues;
202 }
203 if (!isDefault(REFERENCE_WORKSPACE_PROPERTY)) {
204 const auto referenceName = getPropertyValue(REFERENCE_WORKSPACE_PROPERTY);
205 if (std::find_if(workspaces.cbegin(), workspaces.cend(),
206 [&referenceName](const auto wsName) { return wsName == referenceName; }) == workspaces.cend()) {
207 issues[REFERENCE_WORKSPACE_PROPERTY] = "Reference workspace must be one of the input workspaces";
208 return issues;
209 }
210 }
211 for (const auto &wsName : workspaces) {
212 const auto ws = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(wsName);
213 // check if all the others are compatible with the first one
214 const std::string compatible = combHelper.checkCompatibility(ws, true);
215 if (!compatible.empty()) {
216 issues[INPUT_WORKSPACE_PROPERTY] += "Workspace " + ws->getName() + " is not compatible: " + compatible + "\n";
217 }
218 // check that the workspaces are not ragged
219 if (!ws->isCommonBins()) {
220 issues[INPUT_WORKSPACE_PROPERTY] += "Workspace " + ws->getName() + " is ragged which is not supported.\n";
221 }
222 if (ws->isHistogramData()) {
223 issues[INPUT_WORKSPACE_PROPERTY] +=
224 "Workspace " + ws->getName() + " contains histogram data, only point data are supported.\n";
225 }
226 }
227 return issues;
228}
229
230//----------------------------------------------------------------------------------------------
235 std::make_unique<ArrayProperty<std::string>>(INPUT_WORKSPACE_PROPERTY, std::make_unique<ADSValidator>()),
236 "The names of the input workspaces or groups of those as a list. "
237 "At least two compatible MatrixWorkspaces are required, having one spectrum each. ");
238 declareProperty(REFERENCE_WORKSPACE_PROPERTY, "",
239 "The name of the workspace that will serve as the reference; "
240 "that is, the one that will not be scaled. If left blank, "
241 "stitching will be performed left to right in the order of x-axes ascending, "
242 "no matter the order of workspaces names in the input.");
243 declareProperty(SCALE_FACTOR_CALCULATION_PROPERTY, "MedianOfRatios",
244 std::make_unique<ListValidator<std::string>>(std::array<std::string, 2>{"MedianOfRatios", "Manual"}));
245 declareProperty(std::make_unique<ArrayProperty<double>>(MANUAL_SCALE_FACTORS_PROPERTY),
246 "Manually specified scale factors, must follow the same order of the workspaces in the list.");
247 setPropertySettings(MANUAL_SCALE_FACTORS_PROPERTY,
248 std::make_unique<EnabledWhenProperty>(SCALE_FACTOR_CALCULATION_PROPERTY, IS_EQUAL_TO, "Manual"));
249 declareProperty(TIE_SCALE_FACTORS_PROPERTY, false,
250 "Whether or not to calculate a single scale factor per workspace for all the spectra.");
252 std::make_unique<WorkspaceProperty<MatrixWorkspace>>(OUTPUT_WORKSPACE_PROPERTY, "", Direction::Output),
253 "The output workspace.");
254 declareProperty(std::make_unique<WorkspaceProperty<MatrixWorkspace>>(OUTPUT_SCALE_FACTORS_PROPERTY, "",
256 "The output workspace containing the applied scale factors.");
257}
258
259//----------------------------------------------------------------------------------------------
263 const auto referenceName = getPropertyValue(REFERENCE_WORKSPACE_PROPERTY);
264 const auto scaleFactorCalculation = getPropertyValue(SCALE_FACTOR_CALCULATION_PROPERTY);
265 const auto inputs = RunCombinationHelper::unWrapGroups(getProperty(INPUT_WORKSPACE_PROPERTY));
266 MatrixWorkspace_sptr scaleFactorsWorkspace;
267 cloneWorkspaces(inputs);
268 std::vector<std::string> clones;
269 std::transform(inputs.cbegin(), inputs.cend(), std::back_inserter(clones),
270 [](const auto ws) { return "__cloned_" + ws; });
271 if (scaleFactorCalculation == "Manual") {
272 scaleFactorsWorkspace = initScaleFactorsWorkspace(1, clones.size());
273 scaleManual(clones, getProperty(MANUAL_SCALE_FACTORS_PROPERTY), scaleFactorsWorkspace);
274 } else {
275 scaleWithMedianRatios(clones, referenceName, scaleFactorsWorkspace);
276 }
277 setProperty(OUTPUT_WORKSPACE_PROPERTY, merge(clones));
278 for (const auto &ws : clones) {
279 AnalysisDataService::Instance().remove(ws);
280 }
281 if (!isDefault(OUTPUT_SCALE_FACTORS_PROPERTY)) {
282 setProperty(OUTPUT_SCALE_FACTORS_PROPERTY, scaleFactorsWorkspace);
283 }
284}
285
292void Stitch::scaleWithMedianRatios(const std::vector<std::string> &clones, const std::string &referenceName,
293 MatrixWorkspace_sptr &scaleFactorsWorkspace) {
294 std::vector<MatrixWorkspace_sptr> workspaces;
295 std::transform(clones.cbegin(), clones.cend(), std::back_inserter(workspaces),
296 [](const auto ws) { return AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(ws); });
297 const size_t nSpectrumInScaleFactors =
298 isDefault(TIE_SCALE_FACTORS_PROPERTY) ? workspaces[0]->getNumberHistograms() : 1;
299 scaleFactorsWorkspace = initScaleFactorsWorkspace(nSpectrumInScaleFactors, workspaces.size());
300 // sort internally by the x-extent interval ascending, but the scale factors will be stored in the original order
301 std::sort(workspaces.begin(), workspaces.end(), compareInterval);
302 auto progress = std::make_unique<Progress>(this, 0.0, 1.0, workspaces.size());
303 const size_t referenceIndex = getReferenceIndex(workspaces, referenceName);
304 size_t leftIterator = referenceIndex, rightIterator = referenceIndex;
305 // we start from the reference index and iterate to the left, then to the right
306 // note that these loops are delibarately serial, as the subsequent scale factors need to be computed wrt already
307 // scaled previous workspaces
308 while (leftIterator > 0) {
309 scale(workspaces[leftIterator], workspaces[leftIterator - 1], scaleFactorsWorkspace, clones);
310 progress->report();
311 --leftIterator;
312 }
313 while (rightIterator < workspaces.size() - 1) {
314 scale(workspaces[rightIterator], workspaces[rightIterator + 1], scaleFactorsWorkspace, clones);
315 progress->report();
316 ++rightIterator;
317 }
318}
319
326size_t Stitch::getReferenceIndex(const std::vector<MatrixWorkspace_sptr> &workspaces,
327 const std::string &referenceName) {
328 size_t referenceIndex = 0;
329 if (!isDefault(REFERENCE_WORKSPACE_PROPERTY)) {
330 const auto ref = std::find_if(workspaces.cbegin(), workspaces.cend(), [&referenceName](const auto ws) {
331 return ws->getName() == "__cloned_" + referenceName;
332 });
333 referenceIndex = std::distance(workspaces.cbegin(), ref);
334 }
335 return referenceIndex;
336}
337
343void Stitch::cloneWorkspaces(const std::vector<std::string> &inputs) {
344 auto cloner = createChildAlgorithm("CloneWorkspace");
345 cloner->setAlwaysStoreInADS(true);
346 for (const auto &ws : inputs) {
347 cloner->setProperty("InputWorkspace", ws);
348 cloner->setProperty("OutputWorkspace", "__cloned_" + ws);
349 cloner->execute();
350 }
351}
352
359MatrixWorkspace_sptr Stitch::merge(const std::vector<std::string> &inputs) {
360 // interleave option is equivalent to concatenation followed by sort X axis
361 auto joiner = createChildAlgorithm("ConjoinXRuns");
362 joiner->setProperty("InputWorkspaces", inputs);
363 joiner->execute();
364 Workspace_sptr output = joiner->getProperty("OutputWorkspace");
365 MatrixWorkspace_sptr joined = std::dynamic_pointer_cast<MatrixWorkspace>(output);
366
367 auto sorter = createChildAlgorithm("SortXAxis");
368 sorter->setProperty("InputWorkspace", joined);
369 sorter->execute();
370 MatrixWorkspace_sptr sorted = sorter->getProperty("OutputWorkspace");
371 return sorted;
372}
373
383void Stitch::scale(const MatrixWorkspace_sptr &wsToMatch, const MatrixWorkspace_sptr &wsToScale,
384 Mantid::API::MatrixWorkspace_sptr scaleFactorsWorkspace, const std::vector<std::string> &inputs) {
385 const auto overlap = getOverlap(wsToMatch, wsToScale);
386 auto cropper = createChildAlgorithm("CropWorkspaceRagged");
387 cropper->setProperty("XMin", std::vector<double>({overlap.first}));
388 cropper->setProperty("XMax", std::vector<double>({overlap.second}));
389
390 cropper->setProperty("InputWorkspace", wsToMatch);
391 cropper->execute();
392 MatrixWorkspace_sptr croppedToMatch = cropper->getProperty("OutputWorkspace");
393 cropper->setProperty("InputWorkspace", wsToScale);
394 cropper->execute();
395 MatrixWorkspace_sptr croppedToScale = cropper->getProperty("OutputWorkspace");
396
397 MatrixWorkspace_sptr rebinnedToScale;
398 if (croppedToMatch->blocksize() > 1) {
399 auto interpolator = createChildAlgorithm("SplineInterpolation");
400 interpolator->setProperty("WorkspaceToMatch", croppedToMatch);
401 interpolator->setProperty("WorkspaceToInterpolate", croppedToScale);
402 interpolator->setProperty("Linear2Points", true);
403 interpolator->execute();
404 rebinnedToScale = interpolator->getProperty("OutputWorkspace");
405 } else {
406 if (croppedToMatch->readX(0) != croppedToScale->readX(0)) {
407 throw std::runtime_error(
408 "Unable to make the ratio; only one overlapping point is found and it is at different x");
409 } else {
410 rebinnedToScale = croppedToScale;
411 }
412 }
413
414 auto divider = createChildAlgorithm("Divide");
415 divider->setProperty("LHSWorkspace", rebinnedToScale);
416 divider->setProperty("RHSWorkspace", croppedToMatch);
417 divider->execute();
418 MatrixWorkspace_sptr ratio = divider->getProperty("OutputWorkspace");
419 MatrixWorkspace_sptr median =
420 getProperty("TieScaleFactors") ? medianWorkspaceGlobal(ratio) : medianWorkspaceLocal(ratio);
421
422 auto scaler = createChildAlgorithm("Divide");
423 scaler->setAlwaysStoreInADS(true);
424 scaler->setProperty("LHSWorkspace", wsToScale);
425 scaler->setProperty("RHSWorkspace", median);
426 scaler->setPropertyValue("OutputWorkspace", wsToScale->getName());
427 scaler->execute();
428
429 recordScaleFactor(scaleFactorsWorkspace, median, wsToScale, inputs);
430}
431
441 const Mantid::API::MatrixWorkspace_sptr &medianWorkspace,
442 const Mantid::API::MatrixWorkspace_sptr &scaledWorkspace,
443 const std::vector<std::string> &inputs) {
444 const auto it = std::find(inputs.cbegin(), inputs.cend(), scaledWorkspace->getName());
445 const size_t index = std::distance(inputs.cbegin(), it);
446 PARALLEL_FOR_IF(threadSafe(*scaleFactorWorkspace))
447 for (int i = 0; i < static_cast<int>(scaleFactorWorkspace->getNumberHistograms()); ++i) {
448 scaleFactorWorkspace->mutableY(i)[index] = 1. / medianWorkspace->readY(i)[0];
449 }
450}
451
460void Stitch::scaleManual(const std::vector<std::string> &inputs, const std::vector<double> &scaleFactors,
461 const MatrixWorkspace_sptr &scaleFactorsWorkspace) {
462 auto &outputFactors = scaleFactorsWorkspace->mutableY(0);
463 auto progress = std::make_unique<Progress>(this, 0.0, 1.0, inputs.size());
464 PARALLEL_FOR_IF(threadSafe(*scaleFactorsWorkspace))
465 for (int i = 0; i < static_cast<int>(inputs.size()); ++i) {
466 outputFactors[i] = scaleFactors[i];
467 auto scaler = createChildAlgorithm("Scale");
468 scaler->setAlwaysStoreInADS(true);
469 scaler->setPropertyValue("InputWorkspace", inputs[i]);
470 scaler->setProperty("Factor", scaleFactors[i]);
471 scaler->setPropertyValue("OutputWorkspace", inputs[i]);
472 scaler->execute();
473 progress->report();
474 }
475}
476
477} // namespace Mantid::Algorithms
std::string name
Definition Run.cpp:60
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
std::map< DeltaEMode::Type, std::string > index
int64_t nSpectra
#define PARALLEL_FOR_IF(condition)
Empty definitions - to enable set your complier to enable openMP.
std::vector< T > const * vec
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
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.
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
bool isDefault(const std::string &name) const
Base MatrixWorkspace Abstract Class.
const MantidVec & readX(std::size_t const index) const
Deprecated, use x() instead.
A property class for workspaces.
static std::vector< std::string > unWrapGroups(const std::vector< std::string > &)
Flattens the list of group workspaces (if any) into list of workspaces.
void setReferenceProperties(const API::MatrixWorkspace_sptr &)
Sets the properties of the reference (usually first) workspace, to later check the compatibility of t...
std::string checkCompatibility(const API::MatrixWorkspace_sptr &, bool checkNumberHistograms=false)
Compares the properties of the input workspace with the reference.
Stitches overlapping spectra from multiple 2D workspaces.
Definition Stitch.h:17
void cloneWorkspaces(const std::vector< std::string > &inputs)
Clones all the input workspaces so that they can be scaled in-place without altering the inputs Clone...
Definition Stitch.cpp:343
void scaleWithMedianRatios(const std::vector< std::string > &clones, const std::string &referenceName, API::MatrixWorkspace_sptr &scaleFactorsWorkspace)
Scales workspaces by medians of point-wise ratios in the overlap regions.
Definition Stitch.cpp:292
void init() override
Initialize the algorithm's properties.
Definition Stitch.cpp:233
const std::string summary() const override
Algorithm's summary for use in the GUI and help.
Definition Stitch.cpp:170
API::MatrixWorkspace_sptr merge(const std::vector< std::string > &workspaces)
Combines the scaled workspaces together by interleaving their data This is equivalent to concatenatio...
Definition Stitch.cpp:359
void scale(const API::MatrixWorkspace_sptr &wsToMatch, const API::MatrixWorkspace_sptr &wsToScale, API::MatrixWorkspace_sptr scaleFactorsWorkspace, const std::vector< std::string > &inputs)
Scales one workspace to match the scale of the other The scale factors are calculated as medians of p...
Definition Stitch.cpp:383
int version() const override
Algorithm's version for identification.
Definition Stitch.cpp:164
std::map< std::string, std::string > validateInputs() override
Validate the input workspaces for compatibility.
Definition Stitch.cpp:173
void exec() override
Execute the algorithm.
Definition Stitch.cpp:262
void recordScaleFactor(const API::MatrixWorkspace_sptr &scaleFactorWorkspace, const API::MatrixWorkspace_sptr &medianWorkspace, const API::MatrixWorkspace_sptr &scaledWorkspace, const std::vector< std::string > &inputs)
Stores the multiplicative scale factors into a workspace Note that the scale factors are stored in th...
Definition Stitch.cpp:440
size_t getReferenceIndex(const std::vector< API::MatrixWorkspace_sptr > &workspaces, const std::string &referenceName)
Returns the index of the reference workspace in the sorted workspace list.
Definition Stitch.cpp:326
void scaleManual(const std::vector< std::string > &inputs, const std::vector< double > &scaleFactors, const API::MatrixWorkspace_sptr &scaleFactorsWorkspace)
Performs scaling with manual scale factors, which are treated as global, i.e.
Definition Stitch.cpp:460
const std::string category() const override
Algorithm's category for identification.
Definition Stitch.cpp:167
Support for a property that holds an array of values.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void setPropertySettings(const std::string &name, std::unique_ptr< IPropertySettings > settings)
ListValidator is a validator that requires the value of a property to be one of a defined list of pos...
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base 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.
STL namespace.
@ Output
An output workspace.
Definition Property.h:54