-
Notifications
You must be signed in to change notification settings - Fork 122
/
PowderILLDetectorScan.py
272 lines (224 loc) · 13.7 KB
/
PowderILLDetectorScan.py
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
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source,
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
from mantid.kernel import CompositeValidator, Direction, FloatArrayLengthValidator, FloatArrayOrderedPairsValidator, \
FloatArrayProperty, StringListValidator, IntBoundedValidator
from mantid.api import DataProcessorAlgorithm, MultipleFileProperty, Progress, WorkspaceGroupProperty, FileProperty, FileAction
from mantid.simpleapi import *
class PowderILLDetectorScan(DataProcessorAlgorithm):
_progress = None
_height_range = ''
_mirror = None
_crop_negative = None
_out_ws_name = None
_final_mask = None
def category(self):
return "ILL\\Diffraction;Diffraction\\Reduction"
def summary(self):
return 'Performs powder diffraction data reduction for D2B and D20 (when doing a detector scan).'
def seeAlso(self):
return [ "PowderILLParameterScan", "PowderILLEfficiency" ]
def name(self):
return "PowderILLDetectorScan"
def validateInputs(self):
issues = dict()
if not (self.getProperty("Output2DTubes").value
or self.getProperty("Output2D").value
or self.getProperty("Output1D").value):
issues['Output2DTubes'] = 'No output chosen'
issues['Output2D'] = 'No output chosen'
issues['Output1D'] = 'No output chosen'
if self.getPropertyValue("ComponentsToReduce") and self.getProperty("CropNegativeScatteringAngles").value:
issues['CropNegativeScatteringAngles'] = 'For component-wise reduction, this has to be unchecked.'
return issues
def PyInit(self):
self.declareProperty(MultipleFileProperty('Run', extensions=['nxs']),
doc='File path of run(s).')
self.declareProperty(name='NormaliseTo',
defaultValue='Monitor',
validator=StringListValidator(['None', 'Monitor']),
doc='Normalise to monitor, or skip normalisation.')
self.declareProperty(FileProperty('CalibrationFile', '', action=FileAction.OptionalLoad, extensions=['nxs']),
doc='File containing the detector efficiencies.')
self.declareProperty(name='UseCalibratedData',
defaultValue=True,
doc='Whether or not to use the calibrated data in the NeXus files.')
self.declareProperty(name='Output2DTubes',
defaultValue=False,
doc='Output a 2D workspace of height along tube against tube scattering angle.')
self.declareProperty(name='Output2D',
defaultValue=False,
doc='Output a 2D workspace of height along tube against the real scattering angle.')
self.declareProperty(name='Output1D',
defaultValue=True,
doc='Output a 1D workspace with counts against scattering angle.')
self.declareProperty(name='CropNegativeScatteringAngles', defaultValue=True,
doc='Whether or not to crop the negative scattering angles.')
self.declareProperty(FloatArrayProperty(name='HeightRange', values=[],
validator=CompositeValidator([FloatArrayOrderedPairsValidator(),
FloatArrayLengthValidator(0, 2)])),
doc='A pair of values, comma separated, to give the minimum and maximum height range (in m). If not specified '
'the full height range is used.')
self.declareProperty(WorkspaceGroupProperty('OutputWorkspace', '',
direction=Direction.Output),
doc='Output workspace containing the reduced data.')
self.declareProperty(name='InitialMask', defaultValue=20, validator=IntBoundedValidator(lower=0, upper=64),
doc='Number of pixels to mask from the bottom and the top of each tube before superposition.')
self.declareProperty(name='FinalMask', defaultValue=30, validator=IntBoundedValidator(lower=0, upper=70),
doc='Number of spectra to mask from the bottom and the top of the result of 2D options.')
self.declareProperty(name='ComponentsToMask', defaultValue='',
doc='Comma separated list of component names to mask, for instance: tube_1, tube_2')
self.declareProperty(name='ComponentsToReduce', defaultValue='',
doc='Comma separated list of component names to output the reduced data for; for example tube_1')
self.declareProperty(name='AlignTubes', defaultValue=True,
doc='Align the tubes vertically and horizontally according to IPF.')
def _generate_mask(self, n_pix, instrument):
"""
Generates the DetectorList input for MaskDetectors
Masks the bottom and top n_pix pixels in each tube, for D2B only
@param n_pix : Number of pixels to mask from top and bottom of each tube
@param instrument : Instrument
@return the DetectorList string
"""
mask = ''
det = instrument.getComponentByName('detectors')
tube = instrument.getComponentByName('tube_1')
n_tubes = det.nelements()
n_pixels = tube.nelements()
for tube in range(n_tubes):
start_bottom = tube * n_pixels + 1
end_bottom = start_bottom + n_pix - 1
start_top = (tube + 1) * n_pixels - n_pix + 1
end_top = start_top + n_pix - 1
mask += str(start_bottom)+'-'+str(end_bottom)+','
mask += str(start_top)+'-'+str(end_top)+','
self.log().debug('Preparing to mask with DetectorList='+mask[:-1])
return mask[:-1]
def _validate_instrument(self, instrument_name):
supported_instruments = ['D2B', 'D20']
if instrument_name not in supported_instruments:
self.log.warning('Running for unsupported instrument, use with caution. Supported instruments are: '
+ str(supported_instruments))
if instrument_name == 'D20':
if self.getProperty('Output2DTubes').value:
raise RuntimeError('Output2DTubes is not supported for D20 (1D detector)')
if self.getProperty('Output2D').value:
raise RuntimeError('Output2D is not supported for D20 (1D detector)')
def _reduce_1D(self, input_group):
output1D = SumOverlappingTubes(InputWorkspaces=input_group,
OutputType='1D',
HeightAxis=self._height_range,
MirrorScatteringAngles=self._mirror,
CropNegativeScatteringAngles=self._crop_negative,
OutputWorkspace=self._out_ws_name + '_1D')
return output1D
def _reduce_2DTubes(self, input_group):
output2DtubesName = self._out_ws_name + '_2DTubes'
output2DTubes = SumOverlappingTubes(InputWorkspaces=input_group,
OutputType='2DTubes',
HeightAxis=self._height_range,
MirrorScatteringAngles=self._mirror,
CropNegativeScatteringAngles=self._crop_negative,
OutputWorkspace=output2DtubesName)
if self._final_mask != 0:
nSpec = mtd[output2DtubesName].getNumberHistograms()
mask_list = '0-{0},{1}-{2}'.format(self._final_mask,nSpec-self._final_mask,nSpec-1)
MaskDetectors(Workspace=output2DtubesName,WorkspaceIndexList=mask_list)
return output2DTubes
def _reduce_2D(self, input_group):
output2DName = self._out_ws_name + '_2D'
output2D = SumOverlappingTubes(InputWorkspaces=input_group,
OutputType='2D',
HeightAxis=self._height_range,
MirrorScatteringAngles=self._mirror,
CropNegativeScatteringAngles=self._crop_negative,
OutputWorkspace = output2DName)
if self._final_mask != 0:
nSpec = mtd[output2DName].getNumberHistograms()
mask_list = '0-{0},{1}-{2}'.format(self._final_mask,nSpec-self._final_mask,nSpec-1)
MaskDetectors(Workspace=output2DName,WorkspaceIndexList=mask_list)
return output2D
def PyExec(self):
data_type = 'Raw'
if self.getProperty('UseCalibratedData').value:
data_type = 'Auto' # this will use calibrated data, if they exist
align_tubes = self.getProperty('AlignTubes').value
self._progress = Progress(self, start=0.0, end=1.0, nreports=6)
self._progress.report('Loading data')
# Do not merge the runs yet, since it will break the calibration
# Load and calibrate separately, then SumOverlappingTubes will merge correctly
# Besides + here does not make sense, and it will also slow down D2B a lot
input_workspace = LoadAndMerge(Filename=self.getPropertyValue('Run').replace('+', ','),
LoaderName='LoadILLDiffraction',
LoaderOptions={'DataType': data_type, 'AlignTubes': align_tubes})
# We might already have a group, but group just in case
input_group = GroupWorkspaces(InputWorkspaces=input_workspace)
instrument = input_group[0].getInstrument()
instrument_name = instrument.getName()
self._validate_instrument(instrument_name)
self._progress.report('Normalising to monitor')
if self.getPropertyValue('NormaliseTo') == 'Monitor':
input_group = NormaliseToMonitor(InputWorkspace=input_group, MonitorID=0)
if instrument_name == 'D2B':
input_group = Scale(InputWorkspace=input_group, Factor=1e+6)
calib_file = self.getPropertyValue('CalibrationFile')
if calib_file:
self._progress.report('Applying detector efficiencies')
LoadNexusProcessed(Filename=calib_file, OutputWorkspace='__det_eff')
for ws in input_group:
name = ws.getName()
ExtractMonitors(InputWorkspace=name, DetectorWorkspace=name)
ApplyDetectorScanEffCorr(InputWorkspace=name,DetectorEfficiencyWorkspace='__det_eff',OutputWorkspace=name)
instrument = input_group[0].getInstrument()
instrument_name = instrument.getName()
pixels_to_mask = self.getProperty('InitialMask').value
if pixels_to_mask != 0 and instrument_name == 'D2B':
mask = self._generate_mask(pixels_to_mask, instrument)
for ws in input_group:
MaskDetectors(Workspace=ws, DetectorList=mask)
components_to_mask = self.getPropertyValue('ComponentsToMask')
if components_to_mask:
for ws in input_group:
MaskDetectors(Workspace=ws, ComponentList=components_to_mask)
height_range_prop = self.getProperty('HeightRange').value
if len(height_range_prop) == 0:
run = mtd["input_group"].getItem(0).getRun()
if run.hasProperty("PixelHeight") and run.hasProperty("MaxHeight"):
pixelHeight = run.getLogData("PixelHeight").value
maxHeight = run.getLogData("MaxHeight").value
self._height_range = str(-maxHeight) + ',' + str(pixelHeight) + ',' + str(maxHeight)
elif len(height_range_prop) == 2:
self._height_range = str(height_range_prop[0]) + ', ' + str(height_range_prop[1])
output_workspaces = []
self._out_ws_name = self.getPropertyValue('OutputWorkspace')
self._mirror = False
self._crop_negative = self.getProperty('CropNegativeScatteringAngles').value
if instrument.hasParameter("mirror_scattering_angles"):
self._mirror = instrument.getBoolParameter("mirror_scattering_angles")[0]
self._final_mask = self.getProperty('FinalMask').value
components = self.getPropertyValue('ComponentsToReduce')
if components:
for ws in input_group:
CropToComponent(InputWorkspace=ws.getName(), OutputWorkspace=ws.getName(),
ComponentNames=components)
self._progress.report('Doing Output2DTubes Option')
if self.getProperty('Output2DTubes').value:
output2DTubes = self._reduce_2DTubes(input_group)
output_workspaces.append(output2DTubes)
self._progress.report('Doing Output2D Option')
if self.getProperty('Output2D').value:
output2D = self._reduce_2D(input_group)
output_workspaces.append(output2D)
self._progress.report('Doing Output1D Option')
if self.getProperty('Output1D').value:
output1D = self._reduce_1D(input_group)
output_workspaces.append(output1D)
self._progress.report('Finishing up...')
DeleteWorkspace('input_group')
GroupWorkspaces(InputWorkspaces=output_workspaces, OutputWorkspace=self._out_ws_name)
self.setProperty('OutputWorkspace', self._out_ws_name)
# Register the algorithm with Mantid
AlgorithmFactory.subscribe(PowderILLDetectorScan)