-
Notifications
You must be signed in to change notification settings - Fork 122
/
ReflectometryReductionOne2.cpp
1405 lines (1238 loc) · 52.4 KB
/
ReflectometryReductionOne2.cpp
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
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "MantidAlgorithms/ReflectometryReductionOne2.h"
#include "MantidAPI/AnalysisDataService.h"
#include "MantidAPI/Axis.h"
#include "MantidAPI/SpectrumInfo.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/WorkspaceFactory.h"
#include "MantidGeometry/Objects/BoundingBox.h"
#include "MantidHistogramData/LinearGenerator.h"
#include "MantidIndexing/IndexInfo.h"
#include "MantidKernel/MandatoryValidator.h"
#include "MantidKernel/StringTokenizer.h"
#include "MantidKernel/Unit.h"
#include "MantidGeometry/IDetector.h"
#include "MantidGeometry/Instrument.h"
#include "MantidGeometry/Instrument/ReferenceFrame.h"
#include <algorithm>
#include <boost/lexical_cast.hpp>
using namespace Mantid::Kernel;
using namespace Mantid::API;
using namespace Mantid::Geometry;
using namespace Mantid::HistogramData;
using namespace Mantid::Indexing;
namespace Mantid {
namespace Algorithms {
/*Anonomous namespace */
namespace {
/** Get the twoTheta angle for the centre of the detector associated with the
* given spectrum
*
* @param spectrumInfo : the spectrum info
* @param spectrumIdx : the workspace index of the spectrum
* @return : the twoTheta angle in radians
*/
double getDetectorTwoTheta(const SpectrumInfo *spectrumInfo,
const size_t spectrumIdx) {
return spectrumInfo->signedTwoTheta(spectrumIdx);
}
/** Get the start/end of the lambda range for the detector associated
* with the given spectrum
*
* @return : the lambda range
*/
double getLambdaRange(const HistogramX &xValues, const int xIdx) {
// The lambda range is the bin width from the given index to the next.
if (xIdx < 0 || xIdx + 1 >= static_cast<int>(xValues.size())) {
throw std::runtime_error("Error accessing X values out of range (index=" +
std::to_string(xIdx + 1) + ", size=" +
std::to_string(xValues.size()));
}
double result = xValues[xIdx + 1] - xValues[xIdx];
return result;
}
/** Get the lambda value at the centre of the detector associated
* with the given spectrum
*
* @return : the lambda range
*/
double getLambda(const HistogramX &xValues, const int xIdx) {
if (xIdx < 0 || xIdx >= static_cast<int>(xValues.size())) {
throw std::runtime_error("Error accessing X values out of range (index=" +
std::to_string(xIdx) + ", size=" +
std::to_string(xValues.size()));
}
// The centre of the bin is the lower bin edge plus half the width
return xValues[xIdx] + getLambdaRange(xValues, xIdx) / 2.0;
}
/** @todo The following translation functions are duplicates of code in
* GroupDetectors2.cpp. Longer term, we should move them to a common location if
* possible */
/* The following functions are used to translate single operators into
* groups, just like the ones this algorithm loads from .map files.
*
* Each function takes a string, such as "3+4", or "6:10" and then adds
* the resulting groups of spectra to outGroups.
*/
// An add operation, i.e. "3+4" -> [3+4]
void translateAdd(const std::string &instructions,
std::vector<std::vector<size_t>> &outGroups) {
auto spectra = Kernel::StringTokenizer(
instructions, "+", Kernel::StringTokenizer::TOK_TRIM |
Kernel::StringTokenizer::TOK_IGNORE_EMPTY);
std::vector<size_t> outSpectra;
outSpectra.reserve(spectra.count());
for (auto spectrum : spectra) {
// add this spectrum to the group we're about to add
outSpectra.push_back(boost::lexical_cast<size_t>(spectrum));
}
outGroups.push_back(std::move(outSpectra));
}
// A range summation, i.e. "3-6" -> [3+4+5+6]
void translateSumRange(const std::string &instructions,
std::vector<std::vector<size_t>> &outGroups) {
// add a group with the sum of the spectra in the range
auto spectra = Kernel::StringTokenizer(instructions, "-");
if (spectra.count() != 2)
throw std::runtime_error("Malformed range (-) operation.");
// fetch the start and stop spectra
size_t first = boost::lexical_cast<size_t>(spectra[0]);
size_t last = boost::lexical_cast<size_t>(spectra[1]);
// swap if they're back to front
if (first > last)
std::swap(first, last);
// add all the spectra in the range to the output group
std::vector<size_t> outSpectra;
outSpectra.reserve(last - first + 1);
for (size_t i = first; i <= last; ++i)
outSpectra.push_back(i);
if (!outSpectra.empty())
outGroups.push_back(std::move(outSpectra));
}
// A range insertion, i.e. "3:6" -> [3,4,5,6]
void translateRange(const std::string &instructions,
std::vector<std::vector<size_t>> &outGroups) {
// add a group per spectra
auto spectra = Kernel::StringTokenizer(
instructions, ":", Kernel::StringTokenizer::TOK_IGNORE_EMPTY);
if (spectra.count() != 2)
throw std::runtime_error("Malformed range (:) operation.");
// fetch the start and stop spectra
size_t first = boost::lexical_cast<size_t>(spectra[0]);
size_t last = boost::lexical_cast<size_t>(spectra[1]);
// swap if they're back to front
if (first > last)
std::swap(first, last);
// add all the spectra in the range to separate output groups
for (size_t i = first; i <= last; ++i) {
// create group of size 1 with the spectrum and add it to output
outGroups.emplace_back(1, i);
}
}
/**
* Translate the processing instructions into a vector of groups of indices
*
* @param instructions : Instructions to translate
* @return : A vector of groups, each group being a vector of its 0-based
* spectrum indices
*/
std::vector<std::vector<size_t>>
translateInstructions(const std::string &instructions) {
std::vector<std::vector<size_t>> outGroups;
try {
// split into comma separated groups, each group potentially containing
// an operation (+-:) that produces even more groups.
auto groups = Kernel::StringTokenizer(
instructions, ",",
StringTokenizer::TOK_TRIM | StringTokenizer::TOK_IGNORE_EMPTY);
for (const auto &groupStr : groups) {
// Look for the various operators in the string. If one is found then
// do the necessary translation into groupings.
if (groupStr.find('+') != std::string::npos) {
// add a group with the given spectra
translateAdd(groupStr, outGroups);
} else if (groupStr.find('-') != std::string::npos) {
translateSumRange(groupStr, outGroups);
} else if (groupStr.find(':') != std::string::npos) {
translateRange(groupStr, outGroups);
} else if (!groupStr.empty()) {
// contains no instructions, just add this spectrum as a new group
// create group of size 1 with the spectrum in it and add it to output
outGroups.emplace_back(1, boost::lexical_cast<size_t>(groupStr));
}
}
} catch (boost::bad_lexical_cast &) {
throw std::runtime_error("Invalid processing instructions: " +
instructions);
}
return outGroups;
}
/**
* Map a spectrum index from the given map to the given workspace
* @param originWS : the original workspace
* @param mapIdx : the index in the original workspace
* @param destWS : the destination workspace
* @return : the index in the destination workspace
*/
size_t mapSpectrumIndexToWorkspace(MatrixWorkspace_const_sptr originWS,
const size_t originIdx,
MatrixWorkspace_const_sptr destWS) {
SpectrumNumber specId = originWS->indexInfo().spectrumNumber(originIdx);
size_t wsIdx =
destWS->getIndexFromSpectrumNumber(static_cast<specnum_t>(specId));
return wsIdx;
}
/**
* @param originWS : Origin workspace, which provides the original workspace
* index to spectrum number mapping.
* @param hostWS : Workspace onto which the resulting workspace indexes will be
* hosted
* @throws :: If the specId are not found to exist on the host end-point
*workspace.
* @return :: Remapped workspace indexes applicable for the host workspace,
*as a vector of groups of vectors of spectrum indices
*/
std::vector<std::vector<size_t>> mapSpectrumIndicesToWorkspace(
MatrixWorkspace_const_sptr originWS, MatrixWorkspace_const_sptr hostWS,
const std::vector<std::vector<size_t>> &detectorGroups) {
std::vector<std::vector<size_t>> hostGroups;
for (auto group : detectorGroups) {
std::vector<size_t> hostDetectors;
for (auto i : group) {
const size_t hostIdx = mapSpectrumIndexToWorkspace(originWS, i, hostWS);
hostDetectors.push_back(hostIdx);
}
hostGroups.push_back(hostDetectors);
}
return hostGroups;
}
/**
* Translate all the workspace indexes in an origin workspace into workspace
* indexes of a host end-point workspace. This is done using spectrum numbers as
* the intermediate.
*
* @param originWS : Origin workspace, which provides the original workspace
* index to spectrum number mapping.
* @param hostWS : Workspace onto which the resulting workspace indexes will be
* hosted
* @throws :: If the specId are not found to exist on the host end-point
*workspace.
* @return :: Remapped workspace indexes applicable for the host workspace,
*as comma separated string.
*/
std::string createProcessingCommandsFromDetectorWS(
MatrixWorkspace_const_sptr originWS, MatrixWorkspace_const_sptr hostWS,
const std::vector<std::vector<size_t>> &detectorGroups) {
std::string result;
// Map the original indices to the host workspace
std::vector<std::vector<size_t>> hostGroups =
mapSpectrumIndicesToWorkspace(originWS, hostWS, detectorGroups);
// Add each group to the output, separated by ','
/// @todo Low priority: Add support to separate contiguous groups by ':' to
/// avoid having long lists of spectrum indices in the processing
/// instructions. This would not make any functional difference but would be
/// a cosmetic improvement when you view the history.
for (auto groupIt = hostGroups.begin(); groupIt != hostGroups.end();
++groupIt) {
const auto &hostDetectors = *groupIt;
// Add each detector index to the output string separated by '+' to indicate
// that all detectors in this group will be summed. We also check for
// contiguous ranges so we output e.g. 3-5 instead of 3+4+5
bool contiguous = false;
size_t contiguousStart = 0;
for (auto it = hostDetectors.begin(); it != hostDetectors.end(); ++it) {
// Check if the next iterator is a contiguous increment from this one
auto nextIt = it + 1;
if (nextIt != hostDetectors.end() && *nextIt == *it + 1) {
// If this is a start of a new contiguous region, remember the start
// index
if (!contiguous) {
contiguousStart = *it;
contiguous = true;
}
// Continue to find the end of the contiguous region
continue;
}
if (contiguous) {
// Output the contiguous range, then reset the flag
result.append(std::to_string(contiguousStart))
.append("-")
.append(std::to_string(*it));
contiguousStart = 0;
contiguous = false;
} else {
// Just output the value
result.append(std::to_string(*it));
}
// Add a separator ready for the next value/range
if (nextIt != hostDetectors.end()) {
result.append("+");
}
}
if (groupIt + 1 != hostGroups.end()) {
result.append(",");
}
}
return result;
}
/**
* Get the topbottom extent of a detector for the given axis
*
* @param axis [in] : the axis to get the extent for
* @param top [in] : if true, get the max extent, or min otherwise
* @return : the max/min extent on the given axis
*/
double getBoundingBoxExtent(const BoundingBox &boundingBox,
const PointingAlong axis, const bool top) {
double result = 0.0;
switch (axis) {
case X:
result = top ? boundingBox.xMax() : boundingBox.xMin();
break;
case Y:
result = top ? boundingBox.yMax() : boundingBox.yMin();
break;
case Z:
result = top ? boundingBox.zMax() : boundingBox.zMin();
break;
default:
throw std::runtime_error("Axis is not X/Y/Z");
break;
}
return result;
}
}
// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(ReflectometryReductionOne2)
//----------------------------------------------------------------------------------------------
/** Initialize the algorithm's properties.
*/
void ReflectometryReductionOne2::init() {
// Input workspace
declareProperty(make_unique<WorkspaceProperty<MatrixWorkspace>>(
"InputWorkspace", "", Direction::Input),
"Run to reduce.");
initReductionProperties();
// ThetaIn
declareProperty(make_unique<PropertyWithValue<double>>(
"ThetaIn", Mantid::EMPTY_DBL(), Direction::Input),
"Angle in degrees");
// Processing instructions
declareProperty(Kernel::make_unique<PropertyWithValue<std::string>>(
"ProcessingInstructions", "",
boost::make_shared<MandatoryValidator<std::string>>(),
Direction::Input),
"Grouping pattern on workspace indexes to yield only "
"the detectors of interest. See GroupDetectors for details.");
// Minimum wavelength
declareProperty(make_unique<PropertyWithValue<double>>(
"WavelengthMin", Mantid::EMPTY_DBL(),
boost::make_shared<MandatoryValidator<double>>(),
Direction::Input),
"Wavelength minimum in angstroms");
// Maximum wavelength
declareProperty(make_unique<PropertyWithValue<double>>(
"WavelengthMax", Mantid::EMPTY_DBL(),
boost::make_shared<MandatoryValidator<double>>(),
Direction::Input),
"Wavelength maximum in angstroms");
// Properties for direct beam normalization
initDirectBeamProperties();
// Init properties for monitors
initMonitorProperties();
// Normalization by integrated monitors
declareProperty("NormalizeByIntegratedMonitors", true,
"Normalize by dividing by the integrated monitors.");
// Init properties for transmission normalization
initTransmissionProperties();
// Init properties for algorithmic corrections
initAlgorithmicProperties();
// Init properties for diagnostics
initDebugProperties();
declareProperty(make_unique<WorkspaceProperty<>>("OutputWorkspace", "",
Direction::Output),
"Output Workspace IvsQ.");
declareProperty(make_unique<WorkspaceProperty<>>("OutputWorkspaceWavelength",
"", Direction::Output,
PropertyMode::Optional),
"Output Workspace IvsLam. Intermediate workspace.");
}
/** Validate inputs
*/
std::map<std::string, std::string>
ReflectometryReductionOne2::validateInputs() {
std::map<std::string, std::string> results;
const auto reduction = validateReductionProperties();
results.insert(reduction.begin(), reduction.end());
const auto wavelength = validateWavelengthRanges();
results.insert(wavelength.begin(), wavelength.end());
const auto directBeam = validateDirectBeamProperties();
results.insert(directBeam.begin(), directBeam.end());
const auto transmission = validateTransmissionProperties();
results.insert(transmission.begin(), transmission.end());
return results;
}
/** Execute the algorithm.
*/
void ReflectometryReductionOne2::exec() {
// Get input properties
m_runWS = getProperty("InputWorkspace");
const auto xUnitID = m_runWS->getAxis(0)->unit()->unitID();
// Neither TOF or Lambda? Abort.
if ((xUnitID != "Wavelength") && (xUnitID != "TOF"))
throw std::invalid_argument(
"InputWorkspace must have units of TOF or Wavelength");
// Cache the spectrum info and reference frame
m_spectrumInfo = &m_runWS->spectrumInfo();
auto instrument = m_runWS->getInstrument();
m_refFrame = instrument->getReferenceFrame();
// Find and cache detector groups and theta0
findDetectorGroups();
findTheta0();
// Check whether conversion, normalisation, summation etc. need to be done
m_convertUnits = true;
m_normaliseMonitors = true;
m_normaliseTransmission = true;
m_sum = true;
if (xUnitID == "Wavelength") {
// Already converted converted to wavelength
m_convertUnits = false;
// Assume it's also already been normalised by monitors and summed
m_normaliseMonitors = false;
m_sum = false;
}
// Create the output workspace in wavelength
MatrixWorkspace_sptr IvsLam = makeIvsLam();
// Convert to Q
auto IvsQ = convertToQ(IvsLam);
setProperty("OutputWorkspaceWavelength", IvsLam);
setProperty("OutputWorkspace", IvsQ);
}
/** Get the twoTheta angle range for the top/bottom of the detector associated
* with the given spectrum
*
* @param spectrumIdx : the workspace index of the spectrum
* @return : the twoTheta range in radians
*/
double
ReflectometryReductionOne2::getDetectorTwoThetaRange(const size_t spectrumIdx) {
double bTwoTheta = 0;
// Get the sample->detector distance along the beam
const V3D detSample =
m_spectrumInfo->position(spectrumIdx) - m_spectrumInfo->samplePosition();
const double beamOffset =
m_refFrame->vecPointingAlongBeam().scalar_prod(detSample);
// Get the bounding box for this detector/group
BoundingBox boundingBox;
auto detector = m_runWS->getDetector(spectrumIdx);
detector->getBoundingBox(boundingBox);
// Get the top and bottom on the axis pointing up
const double top =
getBoundingBoxExtent(boundingBox, m_refFrame->pointingUp(), true);
const double bottom =
getBoundingBoxExtent(boundingBox, m_refFrame->pointingUp(), false);
// Calculate the difference in twoTheta between the top and bottom
const double twoThetaTop = std::atan(top / beamOffset);
const double twoThetaBottom = std::atan(bottom / beamOffset);
bTwoTheta = twoThetaTop - twoThetaBottom;
// We must have non-zero width to project a range
if (bTwoTheta < Tolerance) {
throw std::runtime_error("Error calculating pixel size.");
}
return bTwoTheta;
}
/**
* Utility function to create a unique workspace name for diagnostic outputs
* based on a given input workspace name
*
* @param inputName [in] : the input name
* @return : the output name
*/
std::string ReflectometryReductionOne2::createDebugWorkspaceName(
const std::string &inputName) {
std::string result = inputName;
if (summingInQ()) {
if (getPropertyValue("ReductionType") == "DivergentBeam")
result += "_QSD"; // Q-summed divergent beam
else
result += "_QSB"; // Q-summed bent (non-flat) sample
} else {
result += "_LS"; // lambda-summed
}
return result;
}
/**
* Utility function to add the given workspace to the ADS if debugging is
* enabled.
*
* @param ws [in] : the workspace to add to the ADS
* @param wsName [in] : the name to output the workspace as
* @param wsSuffix [in] : a suffix to apply to the wsName
* @param debug [in] : true if the workspace should be added to the ADS (the
* function does nothing if this is false)
* @param step [inout] : the current step number, which is added to the wsSuffix
* and is incremented after the workspace is output
*/
void ReflectometryReductionOne2::outputDebugWorkspace(
MatrixWorkspace_sptr ws, const std::string &wsName,
const std::string &wsSuffix, const bool debug, int &step) {
// Nothing to do if debug is not enabled
if (debug) {
// Clone the workspace because otherwise we can end up outputting the same
// workspace twice with different names, which is confusing.
MatrixWorkspace_sptr cloneWS = ws->clone();
AnalysisDataService::Instance().addOrReplace(
wsName + "_" + std::to_string(step) + wsSuffix, cloneWS);
++step;
}
}
/**
* Creates the output 1D array in wavelength from an input 2D workspace in
* TOF. Summation is done over lambda or over lines of constant Q depending on
* the type of reduction. For the latter, the output is projected to "virtual
* lambda" at a reference angle twoThetaR.
*
* @return :: the output workspace in wavelength
*/
MatrixWorkspace_sptr ReflectometryReductionOne2::makeIvsLam() {
MatrixWorkspace_sptr result = m_runWS;
std::string wsName =
createDebugWorkspaceName(getPropertyValue("InputWorkspace"));
const bool debug = getProperty("Diagnostics");
int step = 1;
if (summingInQ()) {
if (m_convertUnits) {
g_log.debug("Converting input workspace to wavelength\n");
result = convertToWavelength(result);
findWavelengthMinMax(result);
outputDebugWorkspace(result, wsName, "_lambda", debug, step);
}
// Now the workspace is in wavelength, find the min/max wavelength
findWavelengthMinMax(result);
if (m_normaliseMonitors) {
g_log.debug("Normalising input workspace by monitors\n");
result = directBeamCorrection(result);
outputDebugWorkspace(result, wsName, "_norm_db", debug, step);
result = monitorCorrection(result);
outputDebugWorkspace(result, wsName, "_norm_monitor", debug, step);
}
if (m_normaliseTransmission) {
g_log.debug("Normalising input workspace by transmission run\n");
result = transOrAlgCorrection(result, false);
outputDebugWorkspace(result, wsName, "_norm_trans", debug, step);
}
if (m_sum) {
g_log.debug("Summing in Q\n");
result = sumInQ(result);
outputDebugWorkspace(result, wsName, "_summed", debug, step);
}
// Crop to wavelength limits
g_log.debug("Cropping output workspace\n");
result = cropWavelength(result, true, wavelengthMin(), wavelengthMax());
outputDebugWorkspace(result, wsName, "_cropped", debug, step);
} else {
if (m_sum) {
g_log.debug("Summing in wavelength\n");
result = makeDetectorWS(result, m_convertUnits);
outputDebugWorkspace(result, wsName, "_summed", debug, step);
}
// Now the workspace is in wavelength, find the min/max wavelength
findWavelengthMinMax(result);
if (m_normaliseMonitors) {
g_log.debug("Normalising output workspace by monitors\n");
result = directBeamCorrection(result);
outputDebugWorkspace(result, wsName, "_norm_db", debug, step);
result = monitorCorrection(result);
outputDebugWorkspace(result, wsName, "_norm_monitor", debug, step);
}
// Crop to wavelength limits
g_log.debug("Cropping output workspace\n");
result = cropWavelength(result, true, wavelengthMin(), wavelengthMax());
outputDebugWorkspace(result, wsName, "_cropped", debug, step);
if (m_normaliseTransmission) {
g_log.debug("Normalising output workspace by transmission run\n");
result = transOrAlgCorrection(result, true);
outputDebugWorkspace(result, wsName, "_norm_trans", debug, step);
}
}
return result;
}
/**
* Normalize by monitors (only if I0MonitorIndex, MonitorBackgroundWavelengthMin
* and MonitorBackgroundWavelengthMax have been given)
*
* @param detectorWS :: the detector workspace to normalise, in lambda
* @return :: the normalized workspace in lambda
*/
MatrixWorkspace_sptr
ReflectometryReductionOne2::monitorCorrection(MatrixWorkspace_sptr detectorWS) {
MatrixWorkspace_sptr IvsLam;
Property *monProperty = getProperty("I0MonitorIndex");
Property *backgroundMinProperty =
getProperty("MonitorBackgroundWavelengthMin");
Property *backgroundMaxProperty =
getProperty("MonitorBackgroundWavelengthMin");
if (!monProperty->isDefault() && !backgroundMinProperty->isDefault() &&
!backgroundMaxProperty->isDefault()) {
const bool integratedMonitors =
getProperty("NormalizeByIntegratedMonitors");
const auto monitorWS = makeMonitorWS(m_runWS, integratedMonitors);
if (!integratedMonitors)
detectorWS = rebinDetectorsToMonitors(detectorWS, monitorWS);
IvsLam = divide(detectorWS, monitorWS);
} else {
IvsLam = detectorWS;
}
return IvsLam;
}
/** Creates a direct beam workspace in wavelength from an input workspace in
* TOF. This method should only be called if RegionOfDirectBeam is provided.
*
* @param inputWS :: the input workspace in TOF
* @return :: the direct beam workspace in wavelength
*/
MatrixWorkspace_sptr
ReflectometryReductionOne2::makeDirectBeamWS(MatrixWorkspace_sptr inputWS) {
std::vector<int> directBeamRegion = getProperty("RegionOfDirectBeam");
// Sum over the direct beam.
const std::string processingCommands = std::to_string(directBeamRegion[0]) +
"-" +
std::to_string(directBeamRegion[1]);
auto groupDirectBeamAlg = this->createChildAlgorithm("GroupDetectors");
groupDirectBeamAlg->initialize();
groupDirectBeamAlg->setProperty("GroupingPattern", processingCommands);
groupDirectBeamAlg->setProperty("InputWorkspace", inputWS);
groupDirectBeamAlg->execute();
MatrixWorkspace_sptr directBeamWS =
groupDirectBeamAlg->getProperty("OutputWorkspace");
directBeamWS = convertToWavelength(directBeamWS);
return directBeamWS;
}
/**
* Normalize the workspace by the direct beam (optional)
*
* @param detectorWS : workspace in wavelength which is to be normalized
* @return : corrected workspace
*/
MatrixWorkspace_sptr ReflectometryReductionOne2::directBeamCorrection(
MatrixWorkspace_sptr detectorWS) {
MatrixWorkspace_sptr normalized = detectorWS;
Property *directBeamProperty = getProperty("RegionOfDirectBeam");
if (!directBeamProperty->isDefault()) {
auto directBeam = makeDirectBeamWS(m_runWS);
// Rebin the direct beam workspace to be the same as the input.
auto rebinToWorkspaceAlg = this->createChildAlgorithm("RebinToWorkspace");
rebinToWorkspaceAlg->initialize();
rebinToWorkspaceAlg->setProperty("WorkspaceToMatch", detectorWS);
rebinToWorkspaceAlg->setProperty("WorkspaceToRebin", directBeam);
rebinToWorkspaceAlg->execute();
directBeam = rebinToWorkspaceAlg->getProperty("OutputWorkspace");
normalized = divide(detectorWS, directBeam);
}
return normalized;
}
/**
* Perform either transmission or algorithmic correction according to the
* settings.
* @param detectorWS : workspace in wavelength which is to be normalized
* @param detectorWSReduced:: whether the input detector workspace has been
* reduced
* @return : corrected workspace
*/
MatrixWorkspace_sptr ReflectometryReductionOne2::transOrAlgCorrection(
MatrixWorkspace_sptr detectorWS, const bool detectorWSReduced) {
MatrixWorkspace_sptr normalized;
MatrixWorkspace_sptr transRun = getProperty("FirstTransmissionRun");
if (transRun) {
normalized = transmissionCorrection(detectorWS, detectorWSReduced);
} else if (getPropertyValue("CorrectionAlgorithm") != "None") {
normalized = algorithmicCorrection(detectorWS);
} else {
normalized = detectorWS;
}
return normalized;
}
/** Perform transmission correction by running 'CreateTransmissionWorkspace' on
* the input workspace
* @param detectorWS :: the input workspace
* @param detectorWSReduced:: whether the input detector workspace has been
* reduced
* @return :: the input workspace normalized by transmission
*/
MatrixWorkspace_sptr ReflectometryReductionOne2::transmissionCorrection(
MatrixWorkspace_sptr detectorWS, const bool detectorWSReduced) {
const bool strictSpectrumChecking = getProperty("StrictSpectrumChecking");
MatrixWorkspace_sptr transmissionWS = getProperty("FirstTransmissionRun");
// Reduce the transmission workspace, if not already done (assume that if
// the workspace is in wavelength then it has already been reduced)
Unit_const_sptr xUnit = transmissionWS->getAxis(0)->unit();
if (xUnit->unitID() == "TOF") {
// Processing instructions for transmission workspace. If strict spectrum
// checking is not enabled then just use the same processing instructions
// that were passed in.
std::string transmissionCommands = getProperty("ProcessingInstructions");
if (strictSpectrumChecking) {
// If we have strict spectrum checking, we should have the same
// spectrum numbers in both workspaces, but not necessarily with the
// same workspace indices. Therefore, map the processing instructions
// from the original workspace to the correct indices in the
// transmission workspace. Note that we use the run workspace here
// because the detectorWS may already have been reduced and may not
// contain the original spectra.
transmissionCommands = createProcessingCommandsFromDetectorWS(
m_runWS, transmissionWS, detectorGroups());
}
MatrixWorkspace_sptr secondTransmissionWS =
getProperty("SecondTransmissionRun");
auto alg = this->createChildAlgorithm("CreateTransmissionWorkspace");
alg->initialize();
alg->setProperty("FirstTransmissionRun", transmissionWS);
alg->setProperty("SecondTransmissionRun", secondTransmissionWS);
alg->setPropertyValue("Params", getPropertyValue("Params"));
alg->setPropertyValue("StartOverlap", getPropertyValue("StartOverlap"));
alg->setPropertyValue("EndOverlap", getPropertyValue("EndOverlap"));
alg->setPropertyValue("I0MonitorIndex", getPropertyValue("I0MonitorIndex"));
alg->setPropertyValue("WavelengthMin", getPropertyValue("WavelengthMin"));
alg->setPropertyValue("WavelengthMax", getPropertyValue("WavelengthMax"));
alg->setPropertyValue("MonitorBackgroundWavelengthMin",
getPropertyValue("MonitorBackgroundWavelengthMin"));
alg->setPropertyValue("MonitorBackgroundWavelengthMax",
getPropertyValue("MonitorBackgroundWavelengthMax"));
alg->setPropertyValue("MonitorIntegrationWavelengthMin",
getPropertyValue("MonitorIntegrationWavelengthMin"));
alg->setPropertyValue("MonitorIntegrationWavelengthMax",
getPropertyValue("MonitorIntegrationWavelengthMax"));
alg->setProperty("ProcessingInstructions", transmissionCommands);
alg->execute();
transmissionWS = alg->getProperty("OutputWorkspace");
}
// Rebin the transmission run to be the same as the input.
auto rebinToWorkspaceAlg = this->createChildAlgorithm("RebinToWorkspace");
rebinToWorkspaceAlg->initialize();
rebinToWorkspaceAlg->setProperty("WorkspaceToMatch", detectorWS);
rebinToWorkspaceAlg->setProperty("WorkspaceToRebin", transmissionWS);
rebinToWorkspaceAlg->execute();
transmissionWS = rebinToWorkspaceAlg->getProperty("OutputWorkspace");
// If the detector workspace has been reduced then the spectrum maps
// should match AFTER reducing the transmission workspace
if (detectorWSReduced) {
verifySpectrumMaps(detectorWS, transmissionWS, strictSpectrumChecking);
}
MatrixWorkspace_sptr normalized = divide(detectorWS, transmissionWS);
return normalized;
}
/**
* Perform transmission correction using alternative correction algorithms
* @param detectorWS : workspace in wavelength which is to be normalized by the
* results of the transmission corrections.
* @return : corrected workspace
*/
MatrixWorkspace_sptr ReflectometryReductionOne2::algorithmicCorrection(
MatrixWorkspace_sptr detectorWS) {
const std::string corrAlgName = getProperty("CorrectionAlgorithm");
IAlgorithm_sptr corrAlg = createChildAlgorithm(corrAlgName);
corrAlg->initialize();
if (corrAlgName == "PolynomialCorrection") {
corrAlg->setPropertyValue("Coefficients", getPropertyValue("Polynomial"));
} else if (corrAlgName == "ExponentialCorrection") {
corrAlg->setPropertyValue("C0", getPropertyValue("C0"));
corrAlg->setPropertyValue("C1", getPropertyValue("C1"));
} else {
throw std::runtime_error("Unknown correction algorithm: " + corrAlgName);
}
corrAlg->setProperty("InputWorkspace", detectorWS);
corrAlg->setProperty("Operation", "Divide");
corrAlg->execute();
return corrAlg->getProperty("OutputWorkspace");
}
/**
* The input workspace (in wavelength) to convert to Q
* @param inputWS : the input workspace to convert
* @return : output workspace in Q
*/
MatrixWorkspace_sptr
ReflectometryReductionOne2::convertToQ(MatrixWorkspace_sptr inputWS) {
// Convert to Q
auto convertUnits = this->createChildAlgorithm("ConvertUnits");
convertUnits->initialize();
convertUnits->setProperty("InputWorkspace", inputWS);
convertUnits->setProperty("Target", "MomentumTransfer");
convertUnits->setProperty("AlignBins", false);
convertUnits->execute();
MatrixWorkspace_sptr IvsQ = convertUnits->getProperty("OutputWorkspace");
return IvsQ;
}
/**
* Determine whether the reduction should sum along lines of constant
* Q or in the default lambda.
*
* @return : true if the reduction should sum in Q; false otherwise
*/
bool ReflectometryReductionOne2::summingInQ() {
bool result = false;
const std::string summationType = getProperty("SummationType");
if (summationType == "SumInQ") {
result = true;
}
return result;
}
/**
* Find and cache the indicies of the detectors of interest
*/
void ReflectometryReductionOne2::findDetectorGroups() {
std::string instructions = getPropertyValue("ProcessingInstructions");
m_detectorGroups = translateInstructions(instructions);
// Sort the groups by the first spectrum number in the group (to give the same
// output order as GroupDetectors)
std::sort(m_detectorGroups.begin(), m_detectorGroups.end(),
[](const std::vector<size_t> a, const std::vector<size_t> b) {
return a.front() < b.front();
});
if (m_detectorGroups.empty()) {
throw std::runtime_error("Invalid processing instructions");
}
for (const auto &group : m_detectorGroups) {
for (const auto &spIdx : group) {
if (spIdx > m_spectrumInfo->size() - 1) {
throw std::runtime_error(
"ProcessingInstructions contains an out-of-range index: " +
std::to_string(spIdx));
}
}
}
}
/**
* Find and cache the angle theta0 from which lines of constant Q emanate
*/
void ReflectometryReductionOne2::findTheta0() {
// Only requried if summing in Q
if (!summingInQ()) {
return;
}
const std::string reductionType = getProperty("ReductionType");
// For the non-flat sample case theta0 is 0
m_theta0 = 0.0;
if (reductionType == "DivergentBeam") {
// theta0 is the horizon angle, which is half the twoTheta angle of the
// detector position. This is the angle the detector has been rotated
// to, which we can get from ThetaIn
Property *thetaIn = getProperty("ThetaIn");
if (!thetaIn->isDefault()) {
m_theta0 = getProperty("ThetaIn");
} else {
/// @todo Currently, ThetaIn must be provided via a property. We could
/// calculate its value instead using
/// ReflectometryReductionOneAuto2::calculateTheta, which could be moved
/// to the base class (ReflectometryWorkflowBase2). Users normally use
/// ReflectometryReductionOneAuto2 though, so at the moment it isn't a
/// high priority to be able to calculate it here.
throw std::runtime_error(
"The ThetaIn property is required for the DivergentBeam case");
}
}
g_log.debug("theta0: " + std::to_string(theta0()) + " degrees\n");
// Convert to radians
m_theta0 *= M_PI / 180.0;
}
/**
* Get the (arbitrary) reference angle twoThetaR for use for summation
* in Q
*
* @return : the angle twoThetaR in radians
* @throws : if the angle could not be found
*/
double
ReflectometryReductionOne2::twoThetaR(const std::vector<size_t> &detectors) {
// Get the twoTheta value for the destinaion pixel that we're projecting onto
double twoThetaR =
getDetectorTwoTheta(m_spectrumInfo, twoThetaRDetectorIdx(detectors));
if (getPropertyValue("ReductionType") == "DivergentBeam") {
// The angle that should be used in the final conversion to Q is
// (twoThetaR-theta0). However, the angle actually used by ConvertUnits is
// twoThetaD/2 where twoThetaD is the detector's twoTheta angle. Since it
// is not easy to change what angle ConvertUnits uses, we can compensate by
// setting twoThetaR = twoThetaD/2+theta0
twoThetaR = twoThetaR / 2.0 + theta0();
}
return twoThetaR;
}
/**
* Get the spectrum index which defines the twoThetaR reference angle
* @return : the spectrum index
*/
size_t ReflectometryReductionOne2::twoThetaRDetectorIdx(
const std::vector<size_t> &detectors) {
// Get the mid-point of the area of interest
return detectors.front() + (detectors.back() - detectors.front()) / 2;
}
void ReflectometryReductionOne2::findWavelengthMinMax(
MatrixWorkspace_sptr inputWS) {