1 | import datetime |
---|
2 | import math |
---|
3 | import os |
---|
4 | import re |
---|
5 | import sys |
---|
6 | import time |
---|
7 | import xml.dom.minidom |
---|
8 | |
---|
9 | from mantid.simpleapi import * |
---|
10 | from mantid.api import WorkspaceGroup, Workspace, ExperimentInfo |
---|
11 | from mantid.kernel import Logger |
---|
12 | import SANSUtility as su |
---|
13 | |
---|
14 | sanslog = Logger("SANS") |
---|
15 | |
---|
16 | class BaseInstrument(object): |
---|
17 | def __init__(self, instr_filen=None): |
---|
18 | """ |
---|
19 | Reads the instrument definition xml file |
---|
20 | @param instr_filen: the name of the instrument definition file to read |
---|
21 | @raise IndexError: if any parameters (e.g. 'default-incident-monitor-spectrum') aren't in the xml definition |
---|
22 | """ |
---|
23 | if instr_filen is None: |
---|
24 | instr_filen = self._NAME+'_Definition.xml' |
---|
25 | |
---|
26 | config = ConfigService.Instance() |
---|
27 | self._definition_file = os.path.join(config["instrumentDefinition.directory"], instr_filen) |
---|
28 | |
---|
29 | inst_ws_name = self.load_empty() |
---|
30 | self.definition = AnalysisDataService.retrieve(inst_ws_name).getInstrument() |
---|
31 | |
---|
32 | def get_default_beam_center(self): |
---|
33 | """ |
---|
34 | Returns the default beam center position, or the pixel location |
---|
35 | of real-space coordinates (0,0). |
---|
36 | """ |
---|
37 | return [0, 0] |
---|
38 | |
---|
39 | def name(self): |
---|
40 | """ |
---|
41 | Return the name of the instrument |
---|
42 | """ |
---|
43 | return self._NAME |
---|
44 | |
---|
45 | def versioned_name(self): |
---|
46 | """ |
---|
47 | Hack-workaround so that we may temporarily display "SANS2DTUBES" as |
---|
48 | an option in the instrument dropdown menu in the interface. To be removed |
---|
49 | as part of #9367. |
---|
50 | """ |
---|
51 | if "SANS2D_Definition_Tubes" in self.idf_path: |
---|
52 | return "SANS2DTUBES" |
---|
53 | return self._NAME |
---|
54 | |
---|
55 | def view(self, workspace_name = None): |
---|
56 | """ |
---|
57 | Opens Mantidplot's InstrumentView displaying the current instrument. This |
---|
58 | empty instrument created contained in the named workspace (a default name |
---|
59 | is generated if this the argument is left blank) unless the workspace already |
---|
60 | exists and then it's contents are displayed |
---|
61 | @param workspace_name: the name of the workspace to create and/or display |
---|
62 | """ |
---|
63 | if workspace_name is None: |
---|
64 | workspace_name = self._NAME+'_instrument_view' |
---|
65 | self.load_empty(workspace_name) |
---|
66 | elif not AnalysisDataService.doesExist(workspace_name): |
---|
67 | self.load_empty(workspace_name) |
---|
68 | |
---|
69 | import mantidplot |
---|
70 | instrument_win = mantidplot.getInstrumentView(workspace_name) |
---|
71 | instrument_win.show() |
---|
72 | |
---|
73 | return workspace_name |
---|
74 | |
---|
75 | def load_empty(self, workspace_name = None): |
---|
76 | """ |
---|
77 | Loads the instrument definition file into a workspace with the given name. |
---|
78 | If no name is given a hidden workspace is used |
---|
79 | @param workspace_name: the name of the workspace to create and/or display |
---|
80 | @return the name of the workspace that was created |
---|
81 | """ |
---|
82 | if workspace_name is None: |
---|
83 | workspace_name = '__'+self._NAME+'_empty' |
---|
84 | |
---|
85 | LoadEmptyInstrument(Filename=self._definition_file, OutputWorkspace=workspace_name) |
---|
86 | |
---|
87 | return workspace_name |
---|
88 | |
---|
89 | |
---|
90 | class DetectorBank: |
---|
91 | class _DectShape: |
---|
92 | """ |
---|
93 | Stores the dimensions of the detector, normally this is a square |
---|
94 | which is easy, but it can have a hole in it which is harder! |
---|
95 | """ |
---|
96 | def __init__(self, width, height, isRect = True, n_pixels = None): |
---|
97 | """ |
---|
98 | Sets the dimensions of the detector |
---|
99 | @param width: the detector's width, spectra numbers along the width should increase in intervals of one |
---|
100 | @param height: the detector's height, spectra numbers along the down the height should increase in intervals of width |
---|
101 | @param isRect: true for rectangular or square detectors, i.e. number of pixels = width * height |
---|
102 | @param n_pixels: optional for rectangular shapes because if it is not given it is calculated from the height and width in that case |
---|
103 | """ |
---|
104 | self._width = width |
---|
105 | self._height = height |
---|
106 | self._isRect = bool(isRect) |
---|
107 | self._n_pixels = n_pixels |
---|
108 | if n_pixels is None: |
---|
109 | if self._isRect: |
---|
110 | self._n_pixels = self._width*self._height |
---|
111 | else: |
---|
112 | raise AttributeError('Number of pixels in the detector unknown, you must state the number of pixels for non-rectangular detectors') |
---|
113 | |
---|
114 | |
---|
115 | def width(self): |
---|
116 | """ |
---|
117 | read-only property getter, this object can't be altered |
---|
118 | """ |
---|
119 | return self._width |
---|
120 | |
---|
121 | def height(self): |
---|
122 | return self._height |
---|
123 | |
---|
124 | def isRectangle(self): |
---|
125 | return self._isRect |
---|
126 | |
---|
127 | def n_pixels(self): |
---|
128 | return self._n_pixels |
---|
129 | |
---|
130 | class _RescaleAndShift: |
---|
131 | """ |
---|
132 | Stores property about the detector which is used to rescale and shift |
---|
133 | data in the bank after data have been reduced. The scale attempts to |
---|
134 | take into account that the relative efficiency of different banks may not |
---|
135 | be the same. By default this scale is set to 1.0. The shift is strictly |
---|
136 | speaking more an effect of the geometry of the sample than the detector |
---|
137 | but is included here since both these are required to bring the scale+shift of reduced data |
---|
138 | collected on front and rear bank on the same 'level' before e.g. merging |
---|
139 | such data |
---|
140 | """ |
---|
141 | def __init__(self, scale=1.0, shift=0.0, fitScale=False, fitShift=False, qMin=None, qMax=None): |
---|
142 | """ |
---|
143 | @param scale: Default to 1.0. Value to multiply data with |
---|
144 | @param shift: Default to 0.0. Value to add to data |
---|
145 | @param fitScale: Default is False. Whether or not to try and fit this param |
---|
146 | @param fitShift: Default is False. Whether or not to try and fit this param |
---|
147 | @param qMin: When set to None (default) then for fitting use the overlapping q region of front and rear detectors |
---|
148 | @param qMax: When set to None (default) then for fitting use the overlapping q region of front and rear detectors |
---|
149 | """ |
---|
150 | self.scale = scale |
---|
151 | self.shift = shift |
---|
152 | self.fitScale = bool(fitScale) |
---|
153 | self.fitShift = bool(fitShift) |
---|
154 | self.qMin = qMin |
---|
155 | self.qMax = qMax |
---|
156 | |
---|
157 | if self.qMin == None or self.qMax == None: |
---|
158 | self.qRangeUserSelected = False |
---|
159 | else: |
---|
160 | self.qRangeUserSelected = True |
---|
161 | |
---|
162 | |
---|
163 | def __init__(self, instr, det_type): |
---|
164 | #detectors are known by many names, the 'uni' name is an instrument independent alias the 'long' name is the instrument view name and 'short' name often used for convenience |
---|
165 | self._names = { |
---|
166 | 'uni' : det_type, |
---|
167 | 'long': instr.getStringParameter(det_type+'-detector-name')[0], |
---|
168 | 'short': instr.getStringParameter(det_type+'-detector-short-name')[0]} |
---|
169 | #the bank is often also referred to by its location, as seen by the sample |
---|
170 | if det_type.startswith('low'): |
---|
171 | position = 'rear' |
---|
172 | else: |
---|
173 | position = 'front' |
---|
174 | self._names['position'] = position |
---|
175 | |
---|
176 | cols_data = instr.getNumberParameter(det_type+'-detector-num-columns') |
---|
177 | if len(cols_data) > 0 : |
---|
178 | rectanglar_shape = True |
---|
179 | width = int(cols_data[0]) |
---|
180 | else : |
---|
181 | rectanglar_shape = False |
---|
182 | width = instr.getNumberParameter(det_type+'-detector-non-rectangle-width')[0] |
---|
183 | |
---|
184 | rows_data = instr.getNumberParameter(det_type+'-detector-num-rows') |
---|
185 | if len(rows_data) > 0 : |
---|
186 | height = int(rows_data[0]) |
---|
187 | else: |
---|
188 | rectanglar_shape = False |
---|
189 | height = instr.getNumberParameter(det_type+'-detector-non-rectangle-height')[0] |
---|
190 | |
---|
191 | n_pixels = None |
---|
192 | n_pixels_override = instr.getNumberParameter(det_type+'-detector-num-pixels') |
---|
193 | if len(n_pixels_override) > 0 : |
---|
194 | n_pixels = int(n_pixels_override[0]) |
---|
195 | #n_pixels is normally None and calculated by DectShape but LOQ (at least) has a detector with a hole |
---|
196 | self._shape = self._DectShape(width, height, rectanglar_shape, n_pixels) |
---|
197 | |
---|
198 | spec_entry = instr.getNumberParameter('first-low-angle-spec-number') |
---|
199 | if len(spec_entry) > 0 : |
---|
200 | self.set_first_spec_num(int(spec_entry[0])) |
---|
201 | else : |
---|
202 | #'first-low-angle-spec-number' is an optimal instrument parameter |
---|
203 | self.set_first_spec_num(0) |
---|
204 | |
---|
205 | #needed for compatibility with SANSReduction and SANSUtily, remove |
---|
206 | self.n_columns = width |
---|
207 | |
---|
208 | #this can be set to the name of a file with correction factor against wavelength |
---|
209 | self.correction_file = '' |
---|
210 | #this corrections are set by the mask file |
---|
211 | self.z_corr = 0.0 |
---|
212 | self.x_corr = 0.0 |
---|
213 | self._y_corr = 0.0 |
---|
214 | self._rot_corr = 0.0 |
---|
215 | #23/3/12 RKH add 2 more variables |
---|
216 | self._radius_corr = 0.0 |
---|
217 | self._side_corr = 0.0 |
---|
218 | # 11/11/14 RKH add 2 more, valid for all detectors. WHY do some of the above have an extra leading underscore?? Seems they are the optional ones sorted below |
---|
219 | self.x_tilt = 0.0 |
---|
220 | self.y_tilt = 0.0 |
---|
221 | |
---|
222 | # hold rescale and shift object _RescaleAndShift |
---|
223 | self.rescaleAndShift = self._RescaleAndShift() |
---|
224 | |
---|
225 | #in the empty instrument detectors are laid out as below on loading a run the orientation becomes run dependent |
---|
226 | self._orientation = 'HorizontalFlipped' |
---|
227 | |
---|
228 | def disable_y_and_rot_corrs(self): |
---|
229 | """ |
---|
230 | Not all corrections are supported on all detectors |
---|
231 | """ |
---|
232 | self._y_corr = None |
---|
233 | self._rot_corr = None |
---|
234 | #23/3/12 RKH add 2 more variables |
---|
235 | self._radius_corr = None |
---|
236 | self._side_corr = None |
---|
237 | |
---|
238 | def get_y_corr(self): |
---|
239 | if not self._y_corr is None: |
---|
240 | return self._y_corr |
---|
241 | else: |
---|
242 | raise NotImplementedError('y correction is not used for this detector') |
---|
243 | |
---|
244 | def set_y_corr(self, value): |
---|
245 | """ |
---|
246 | Only set the value if it isn't disabled |
---|
247 | @param value: set y_corr to this value, unless it's disabled |
---|
248 | """ |
---|
249 | if not self._y_corr is None: |
---|
250 | self._y_corr = value |
---|
251 | |
---|
252 | def get_rot_corr(self): |
---|
253 | if not self._rot_corr is None: |
---|
254 | return self._rot_corr |
---|
255 | else: |
---|
256 | raise NotImplementedError('rot correction is not used for this detector') |
---|
257 | |
---|
258 | def set_rot_corr(self, value): |
---|
259 | """ |
---|
260 | Only set the value if it isn't disabled |
---|
261 | @param value: set rot_corr to this value, unless it's disabled |
---|
262 | """ |
---|
263 | if not self._rot_corr is None: |
---|
264 | self._rot_corr = value |
---|
265 | |
---|
266 | # 22/3/12 RKH added two new variables radius_corr, side_corr |
---|
267 | def get_radius_corr(self): |
---|
268 | if not self._radius_corr is None: |
---|
269 | return self._radius_corr |
---|
270 | else: |
---|
271 | raise NotImplementedError('radius correction is not used for this detector') |
---|
272 | |
---|
273 | def set_radius_corr(self, value): |
---|
274 | """ |
---|
275 | Only set the value if it isn't disabled |
---|
276 | @param value: set radius_corr to this value, unless it's disabled |
---|
277 | """ |
---|
278 | if not self._rot_corr is None: |
---|
279 | self._radius_corr = value |
---|
280 | |
---|
281 | def get_side_corr(self): |
---|
282 | if not self._side_corr is None: |
---|
283 | return self._side_corr |
---|
284 | else: |
---|
285 | raise NotImplementedError('side correction is not used for this detector') |
---|
286 | |
---|
287 | def set_side_corr(self, value): |
---|
288 | """ |
---|
289 | Only set the value if it isn't disabled |
---|
290 | @param value: set side_corr to this value, unless it's disabled |
---|
291 | """ |
---|
292 | if not self._side_corr is None: |
---|
293 | self._side_corr = value |
---|
294 | |
---|
295 | |
---|
296 | y_corr = property(get_y_corr, set_y_corr, None, None) |
---|
297 | rot_corr = property(get_rot_corr , set_rot_corr, None, None) |
---|
298 | # 22/3/12 RKH added 2 new variables |
---|
299 | radius_corr = property(get_radius_corr , set_radius_corr, None, None) |
---|
300 | side_corr = property(get_side_corr , set_side_corr, None, None) |
---|
301 | |
---|
302 | def get_first_spec_num(self): |
---|
303 | return self._first_spec_num |
---|
304 | |
---|
305 | def set_first_spec_num(self, value): |
---|
306 | self._first_spec_num = value |
---|
307 | self.last_spec_num = self._first_spec_num + self._shape.n_pixels() - 1 |
---|
308 | |
---|
309 | def place_after(self, previousDet): |
---|
310 | self.set_first_spec_num(previousDet.last_spec_num + 1) |
---|
311 | |
---|
312 | def name(self, form = 'long') : |
---|
313 | if form.lower() == 'inst_view' : form = 'long' |
---|
314 | if not self._names.has_key(form) : form = 'long' |
---|
315 | |
---|
316 | return self._names[form] |
---|
317 | |
---|
318 | def isAlias(self, guess) : |
---|
319 | """ |
---|
320 | Detectors are often referred to by more than one name, check |
---|
321 | if the supplied name is in the list |
---|
322 | @param guess: this name will be searched for in the list |
---|
323 | @return : True if the name was found, otherwise false |
---|
324 | """ |
---|
325 | for name in self._names.values() : |
---|
326 | if guess.lower() == name.lower() : |
---|
327 | return True |
---|
328 | return False |
---|
329 | |
---|
330 | def spectrum_block(self, ylow, xlow, ydim, xdim): |
---|
331 | """ |
---|
332 | Compile a list of spectrum IDs for rectangular block of size xdim by ydim |
---|
333 | """ |
---|
334 | if ydim == 'all': |
---|
335 | ydim = self._shape.height() |
---|
336 | if xdim == 'all': |
---|
337 | xdim = self._shape.width() |
---|
338 | det_dimension = self._shape.width() |
---|
339 | base = self._first_spec_num |
---|
340 | |
---|
341 | if not self._shape.isRectangle(): |
---|
342 | sanslog.warning('Attempting to block rows or columns in a non-rectangular detector, this is likely to give unexpected results!') |
---|
343 | |
---|
344 | output = '' |
---|
345 | if self._orientation == 'Horizontal': |
---|
346 | start_spec = base + ylow*det_dimension + xlow |
---|
347 | for y in range(0, ydim): |
---|
348 | for x in range(0, xdim): |
---|
349 | output += str(start_spec + x + (y*det_dimension)) + ',' |
---|
350 | elif self._orientation == 'Vertical': |
---|
351 | start_spec = base + xlow*det_dimension + ylow |
---|
352 | for x in range(det_dimension - 1, det_dimension - xdim-1,-1): |
---|
353 | for y in range(0, ydim): |
---|
354 | std_i = start_spec + y + ((det_dimension-x-1)*det_dimension) |
---|
355 | output += str(std_i ) + ',' |
---|
356 | elif self._orientation == 'Rotated': |
---|
357 | # This is the horizontal one rotated so need to map the xlow and vlow to their rotated versions |
---|
358 | start_spec = base + ylow*det_dimension + xlow |
---|
359 | max_spec = det_dimension*det_dimension + base - 1 |
---|
360 | for y in range(0, ydim): |
---|
361 | for x in range(0, xdim): |
---|
362 | std_i = start_spec + x + (y*det_dimension) |
---|
363 | output += str(max_spec - (std_i - base)) + ',' |
---|
364 | elif self._orientation == 'HorizontalFlipped': |
---|
365 | for y in range(ylow,ylow+ydim): |
---|
366 | max_row = base + (y+1)*det_dimension - 1 |
---|
367 | min_row = base + (y)*det_dimension |
---|
368 | for x in range(xlow,xlow+xdim): |
---|
369 | std_i = base + x + (y*det_dimension) |
---|
370 | diff_s = std_i - min_row |
---|
371 | output += str(max_row - diff_s) + ',' |
---|
372 | |
---|
373 | return output.rstrip(",") |
---|
374 | |
---|
375 | # Used to constrain the possible values of the orientation of the detector bank against the direction that spectrum numbers increase in |
---|
376 | _ORIENTED = { |
---|
377 | 'Horizontal' : None, #most runs have the detectors in this state |
---|
378 | 'Vertical' : None, |
---|
379 | 'Rotated' : None, |
---|
380 | 'HorizontalFlipped' : None} # This is for the empty instrument |
---|
381 | |
---|
382 | def set_orien(self, orien): |
---|
383 | """ |
---|
384 | Sets to relationship between the detectors and the spectra numbers. The relationship |
---|
385 | is given by an orientation string and this function throws if the string is not recognised |
---|
386 | @param orien: the orienation string must be a string contained in the dictionary _ORIENTED |
---|
387 | """ |
---|
388 | dummy = self._ORIENTED[orien] |
---|
389 | self._orientation = orien |
---|
390 | |
---|
391 | def crop_to_detector(self, input_name, output_name=None): |
---|
392 | """ |
---|
393 | Crops the workspace that is passed so that it only contains the spectra that correspond |
---|
394 | to this detector |
---|
395 | @param input_name: name of the workspace to crop |
---|
396 | @param output_name: name the workspace will take (default is the input name) |
---|
397 | """ |
---|
398 | if not output_name: |
---|
399 | output_name = input_name |
---|
400 | |
---|
401 | try: |
---|
402 | wki = mtd[input_name] |
---|
403 | #Is it really necessary to crop? |
---|
404 | if (wki.getNumberHistograms() != self.last_spec_num - self.get_first_spec_num() + 1): |
---|
405 | CropWorkspace(InputWorkspace=input_name,OutputWorkspace= output_name, |
---|
406 | StartWorkspaceIndex = self.get_first_spec_num() - 1, |
---|
407 | EndWorkspaceIndex = self.last_spec_num - 1) |
---|
408 | except : |
---|
409 | raise ValueError('Can not find spectra for %s in the workspace %s [%d,%d]\nException:' |
---|
410 | %(self.name(), input_name,self.get_first_spec_num(),self.last_spec_num) |
---|
411 | + str(sys.exc_info())) |
---|
412 | |
---|
413 | class ISISInstrument(BaseInstrument): |
---|
414 | def __init__(self, filename=None): |
---|
415 | """ |
---|
416 | Reads the instrument definition xml file |
---|
417 | @param filename: the name of the instrument definition file to read |
---|
418 | @raise IndexError: if any parameters (e.g. 'default-incident-monitor-spectrum') aren't in the xml definition |
---|
419 | """ |
---|
420 | super(ISISInstrument, self).__init__(instr_filen=filename) |
---|
421 | |
---|
422 | self.idf_path = self._definition_file |
---|
423 | |
---|
424 | #the spectrum with this number is used to normalize the workspace data |
---|
425 | self._incid_monitor = int(self.definition.getNumberParameter( |
---|
426 | 'default-incident-monitor-spectrum')[0]) |
---|
427 | self.cen_find_step = float(self.definition.getNumberParameter( |
---|
428 | 'centre-finder-step-size')[0]) |
---|
429 | |
---|
430 | firstDetect = DetectorBank(self.definition, 'low-angle') |
---|
431 | #firstDetect.disable_y_and_rot_corrs() |
---|
432 | secondDetect = DetectorBank(self.definition, 'high-angle') |
---|
433 | secondDetect.place_after(firstDetect) |
---|
434 | #add det_selection variable that will receive the DET/ REAR/FRONT/BOTH/MERGED |
---|
435 | self.det_selection = 'REAR' |
---|
436 | self.DETECTORS = {'low-angle' : firstDetect} |
---|
437 | self.DETECTORS['high-angle'] = secondDetect |
---|
438 | |
---|
439 | self.setDefaultDetector() |
---|
440 | # if this is set InterpolationRebin will be used on the monitor spectrum used to normalize the sample, useful because wavelength resolution in the monitor spectrum can be course in the range of interest |
---|
441 | self._use_interpol_norm = False |
---|
442 | #remove use_interpol_trans_calc once the beam centre finder has been converted |
---|
443 | self.use_interpol_trans_calc = False |
---|
444 | |
---|
445 | # the sample will be moved this distance a long the beam axis |
---|
446 | self.SAMPLE_Z_CORR = 0 |
---|
447 | |
---|
448 | # Detector position information for SANS2D |
---|
449 | # why are these not defined in SANS2D |
---|
450 | self.FRONT_DET_RADIUS = 306.0 |
---|
451 | self.FRONT_DET_DEFAULT_SD_M = 4.0 |
---|
452 | self.FRONT_DET_DEFAULT_X_M = 1.1 |
---|
453 | self.REAR_DET_DEFAULT_SD_M = 4.0 |
---|
454 | |
---|
455 | # LOG files for SANS2D will have these encoder readings |
---|
456 | # why are these not defined in SANS2D |
---|
457 | self.FRONT_DET_X = 0.0 |
---|
458 | self.FRONT_DET_Z = 0.0 |
---|
459 | self.FRONT_DET_ROT = 0.0 |
---|
460 | self.REAR_DET_Z = 0.0 |
---|
461 | self.REAR_DET_X = 0 |
---|
462 | |
---|
463 | #spectrum number of the monitor used to as the incidient in the transmission calculations |
---|
464 | self.default_trans_spec = int(self.definition.getNumberParameter( |
---|
465 | 'default-transmission-monitor-spectrum')[0]) |
---|
466 | self.incid_mon_4_trans_calc = self._incid_monitor |
---|
467 | |
---|
468 | isis = config.getFacility('ISIS') |
---|
469 | # Number of digits in standard file name |
---|
470 | self.run_number_width = isis.instrument(self._NAME).zeroPadding(0) |
---|
471 | |
---|
472 | #this variable isn't used again and stops the instrument from being deep copied if this instance is deep copied |
---|
473 | self.definition = None |
---|
474 | |
---|
475 | #remove this function |
---|
476 | self._del_incidient_set = False |
---|
477 | |
---|
478 | #it is possible to set the TOF regions that is assumed to be background for each monitors |
---|
479 | self._back_ground = {} |
---|
480 | # the default start region, used for any monitors that a specific one wasn't set for |
---|
481 | self._back_start = None |
---|
482 | # default end region |
---|
483 | self._back_end = None |
---|
484 | #if the user moves a monitor to this z coordinate (with MON/LENGTH ...) this will be recorded here. These are overridden lines like TRANS/TRANSPEC=4/SHIFT=-100 |
---|
485 | self.monitor_zs = {} |
---|
486 | # Used when new calibration required. |
---|
487 | self._newCalibrationWS = None |
---|
488 | |
---|
489 | def get_incident_mon(self): |
---|
490 | """ |
---|
491 | @return: the spectrum number of the incident scattering monitor |
---|
492 | """ |
---|
493 | return self._incid_monitor |
---|
494 | |
---|
495 | def set_incident_mon(self, spectrum_number): |
---|
496 | """ |
---|
497 | set the incident scattering monitor spectrum number regardless of |
---|
498 | lock |
---|
499 | @param spectrum_number: monitor's sectrum number |
---|
500 | """ |
---|
501 | self._incid_monitor = int(spectrum_number) |
---|
502 | self._del_incidient_set = True |
---|
503 | |
---|
504 | def suggest_incident_mntr(self, spectrum_number): |
---|
505 | """ |
---|
506 | remove this function and the data memember it uses |
---|
507 | """ |
---|
508 | if not self._del_incidient_set: |
---|
509 | self.set_incident_mon(spectrum_number) |
---|
510 | |
---|
511 | def set_sample_offset(self, value): |
---|
512 | """ |
---|
513 | @param value: sample value offset |
---|
514 | """ |
---|
515 | self.SAMPLE_Z_CORR = float(value)/1000. |
---|
516 | |
---|
517 | def is_interpolating_norm(self): |
---|
518 | return self._use_interpol_norm |
---|
519 | |
---|
520 | def set_interpolating_norm(self, on=True): |
---|
521 | """ |
---|
522 | This method sets that the monitor spectrum should be interpolated before |
---|
523 | normalisation |
---|
524 | """ |
---|
525 | self._use_interpol_norm = on |
---|
526 | |
---|
527 | def cur_detector(self): |
---|
528 | if self.lowAngDetSet : return self.DETECTORS['low-angle'] |
---|
529 | else : return self.DETECTORS['high-angle'] |
---|
530 | |
---|
531 | def get_low_angle_detector(self): |
---|
532 | """ Provide a direct way to get the low bank detector. |
---|
533 | This method does not require to pass the name of the detector bank. |
---|
534 | """ |
---|
535 | return self.DETECTORS['low-angle'] |
---|
536 | |
---|
537 | def get_high_angle_detector(self): |
---|
538 | """ Provide a direct way to get the high bank detector |
---|
539 | This method does not require to pass the name of the detector bank. |
---|
540 | """ |
---|
541 | return self.DETECTORS['high-angle'] |
---|
542 | |
---|
543 | def other_detector(self) : |
---|
544 | if not self.lowAngDetSet : return self.DETECTORS['low-angle'] |
---|
545 | else : return self.DETECTORS['high-angle'] |
---|
546 | |
---|
547 | def getDetector(self, requested) : |
---|
548 | for n, detect in self.DETECTORS.iteritems(): |
---|
549 | if detect.isAlias(requested): |
---|
550 | return detect |
---|
551 | sanslog.notice("getDetector: Detector " + requested + "not found") |
---|
552 | |
---|
553 | def listDetectors(self) : |
---|
554 | return self.cur_detector().name(), self.other_detector().name() |
---|
555 | |
---|
556 | def isHighAngleDetector(self, detName) : |
---|
557 | if self.DETECTORS['high-angle'].isAlias(detName) : |
---|
558 | return True |
---|
559 | |
---|
560 | def isDetectorName(self, detName) : |
---|
561 | if self.other_detector().isAlias(detName) : |
---|
562 | return True |
---|
563 | |
---|
564 | return self.cur_detector().isAlias(detName) |
---|
565 | |
---|
566 | def setDetector(self, detName) : |
---|
567 | self.det_selection = detName |
---|
568 | if self.other_detector().isAlias(detName) : |
---|
569 | self.lowAngDetSet = not self.lowAngDetSet |
---|
570 | return True |
---|
571 | else: |
---|
572 | #there are only two detectors, they must have selected the current one so no change is need |
---|
573 | if self.cur_detector().isAlias(detName): |
---|
574 | return True |
---|
575 | else: |
---|
576 | sanslog.notice("setDetector: Detector not found") |
---|
577 | sanslog.notice("setDetector: Detector set to " + self.cur_detector().name() + ' in ' + self.name()) |
---|
578 | |
---|
579 | def setDefaultDetector(self): |
---|
580 | self.lowAngDetSet = True |
---|
581 | |
---|
582 | def copy_correction_files(self): |
---|
583 | """ |
---|
584 | Check if one of the efficiency files hasn't been set and assume the other is to be used |
---|
585 | """ |
---|
586 | a = self.cur_detector() |
---|
587 | b = self.other_detector() |
---|
588 | if a.correction_file == '' and b.correction_file != '': |
---|
589 | a.correction_file = b.correction_file != '' |
---|
590 | if b.correction_file == '' and a.correction_file != '': |
---|
591 | b.correction_file = a.correction_file != '' |
---|
592 | |
---|
593 | def detector_file(self, det_name): |
---|
594 | det = self.getDetector(det_name) |
---|
595 | return det.correction_file |
---|
596 | |
---|
597 | def get_TOFs(self, monitor): |
---|
598 | """ |
---|
599 | Gets the start and end time of flights for the region assumed to contain |
---|
600 | only background counts for this instrument |
---|
601 | @param monitor: spectrum number of the monitor's spectrum |
---|
602 | @return: the start time, the end time |
---|
603 | """ |
---|
604 | monitor = int(monitor) |
---|
605 | if self._back_ground.has_key(monitor): |
---|
606 | return self._back_ground[int(monitor)]['start'], \ |
---|
607 | self._back_ground[int(monitor)]['end'] |
---|
608 | else: |
---|
609 | return self._back_start, self._back_end |
---|
610 | |
---|
611 | def set_TOFs(self, start, end, monitor=None): |
---|
612 | """ |
---|
613 | Defines the start and end time of flights for the assumed background region |
---|
614 | for this instrument |
---|
615 | @param: start defines the start of the background region |
---|
616 | @param: end defines the end |
---|
617 | @param monitor: spectrum number of the monitor's spectrum, if none given affect the default |
---|
618 | """ |
---|
619 | if start != None: |
---|
620 | start = float(start) |
---|
621 | if end != None: |
---|
622 | end = float(end) |
---|
623 | |
---|
624 | if monitor: |
---|
625 | self._back_ground[int(monitor)] = { 'start' : start, 'end' : end } |
---|
626 | else: |
---|
627 | self._back_start = start |
---|
628 | self._back_end = end |
---|
629 | |
---|
630 | def reset_TOFs(self, monitor=None): |
---|
631 | """ |
---|
632 | Reset background region set by set_TOFs |
---|
633 | @param monitor: spectrum number of the monitor's spectrum, if none given affect the default |
---|
634 | """ |
---|
635 | if monitor: |
---|
636 | monitor = int(monitor) |
---|
637 | if self._back_ground.has_key(monitor): |
---|
638 | del self._back_ground[int(monitor)] |
---|
639 | else: |
---|
640 | self._back_ground = {} |
---|
641 | self._back_start = None |
---|
642 | self._back_end = None |
---|
643 | |
---|
644 | def move_all_components(self, ws): |
---|
645 | """ |
---|
646 | Move the sample object to the location set in the logs or user settings file |
---|
647 | @param ws: the workspace containing the sample to move |
---|
648 | """ |
---|
649 | MoveInstrumentComponent(Workspace=ws,ComponentName= 'some-sample-holder', Z = self.SAMPLE_Z_CORR, RelativePosition=True) |
---|
650 | |
---|
651 | for i in self.monitor_zs.keys(): |
---|
652 | #get the current location |
---|
653 | component = self.monitor_names[i] |
---|
654 | ws = mtd[str(ws)] |
---|
655 | mon = ws.getInstrument().getComponentByName(component) |
---|
656 | z_loc = mon.getPos().getZ() |
---|
657 | #now the relative move |
---|
658 | offset = (self.monitor_zs[i]/1000.) - z_loc |
---|
659 | MoveInstrumentComponent(Workspace=ws,ComponentName= component, Z = offset, |
---|
660 | RelativePosition=True) |
---|
661 | |
---|
662 | def move_components(self, ws, beamX, beamY): |
---|
663 | """Define how to move the bank to position beamX and beamY must be implemented""" |
---|
664 | raise RuntimeError("Not Implemented") |
---|
665 | |
---|
666 | def cur_detector_position(self, ws_name): |
---|
667 | """Return the position of the center of the detector bank""" |
---|
668 | raise RuntimeError("Not Implemented") |
---|
669 | |
---|
670 | def on_load_sample(self, ws_name, beamcentre, isSample): |
---|
671 | """It will be called just after loading the workspace for sample and can |
---|
672 | |
---|
673 | It configures the instrument for the specific run of the workspace for handle historical changes in the instrument. |
---|
674 | |
---|
675 | It centralizes the detector bank to teh beamcentre (tuple of two values) |
---|
676 | """ |
---|
677 | ws_ref = mtd[str(ws_name)] |
---|
678 | try: |
---|
679 | run_num = ws_ref.getRun().getLogData('run_number').value |
---|
680 | except: |
---|
681 | run_num = int(re.findall(r'\d+',str(ws_name))[-1]) |
---|
682 | |
---|
683 | if isSample: |
---|
684 | self.set_up_for_run(run_num) |
---|
685 | |
---|
686 | if self._newCalibrationWS: |
---|
687 | self.changeCalibration(ws_name) |
---|
688 | |
---|
689 | # centralize the bank to the centre |
---|
690 | self.move_components(ws_name, beamcentre[0], beamcentre[1]) |
---|
691 | |
---|
692 | def load_transmission_inst(self, ws_trans, ws_direct, beamcentre): |
---|
693 | """ |
---|
694 | Called on loading of transmissions |
---|
695 | """ |
---|
696 | pass |
---|
697 | |
---|
698 | def changeCalibration(self, ws_name): |
---|
699 | calib = mtd[self._newCalibrationWS] |
---|
700 | sanslog.notice("Applying new calibration for the detectors from " + str(calib.name())) |
---|
701 | CopyInstrumentParameters(calib, ws_name) |
---|
702 | |
---|
703 | def setCalibrationWorkspace(self, ws_reference): |
---|
704 | assert(isinstance(ws_reference, Workspace)) |
---|
705 | # we do deep copy of singleton - to be removed in 8470 |
---|
706 | # this forces us to have 'copyable' objects. |
---|
707 | self._newCalibrationWS = str(ws_reference) |
---|
708 | |
---|
709 | |
---|
710 | |
---|
711 | class LOQ(ISISInstrument): |
---|
712 | """ |
---|
713 | Contains all the LOQ specific data and functions |
---|
714 | """ |
---|
715 | _NAME = 'LOQ' |
---|
716 | #minimum wavelength of neutrons assumed to be measurable by this instrument |
---|
717 | WAV_RANGE_MIN = 2.2 |
---|
718 | #maximum wavelength of neutrons assumed to be measurable by this instrument |
---|
719 | WAV_RANGE_MAX = 10.0 |
---|
720 | |
---|
721 | def __init__(self): |
---|
722 | """ |
---|
723 | Reads LOQ's instrument definition xml file |
---|
724 | @raise IndexError: if any parameters (e.g. 'default-incident-monitor-spectrum') aren't in the xml definition |
---|
725 | """ |
---|
726 | super(LOQ, self).__init__('LOQ_Definition_20020226-.xml') |
---|
727 | #relates the numbers of the monitors to their names in the instrument definition file |
---|
728 | self.monitor_names = {1 : 'monitor1', |
---|
729 | 2 : 'monitor2'} |
---|
730 | |
---|
731 | def move_components(self, ws, xbeam, ybeam): |
---|
732 | """ |
---|
733 | Move the locations of the sample and detector bank based on the passed beam center |
---|
734 | and information from the sample workspace logs |
---|
735 | @param ws: workspace containing the instrument information |
---|
736 | @param xbeam: x-position of the beam |
---|
737 | @param ybeam: y-position of the beam |
---|
738 | @return: the locations of (in the new coordinates) beam center, center of detector bank |
---|
739 | """ |
---|
740 | self.move_all_components(ws) |
---|
741 | |
---|
742 | xshift = (317.5/1000.) - xbeam |
---|
743 | yshift = (317.5/1000.) - ybeam |
---|
744 | MoveInstrumentComponent(Workspace=ws,ComponentName= self.cur_detector().name(), X = xshift, Y = yshift, RelativePosition="1") |
---|
745 | |
---|
746 | # Have a separate move for x_corr, y_coor and z_coor just to make it more obvious in the |
---|
747 | # history, and to expert users what is going on |
---|
748 | det = self.cur_detector() |
---|
749 | if det.x_corr != 0.0 or det.y_corr != 0.0 or det.z_corr != 0.0: |
---|
750 | MoveInstrumentComponent(Workspace=ws,ComponentName= det.name(), X = det.x_corr/1000.0, Y = det.y_corr/1000.0, Z = det.z_corr/1000.0, RelativePosition="1") |
---|
751 | xshift = xshift + det.x_corr/1000.0 |
---|
752 | yshift = yshift + det.y_corr/1000.0 |
---|
753 | |
---|
754 | return [xshift, yshift], [xshift, yshift] |
---|
755 | |
---|
756 | def get_marked_dets(self): |
---|
757 | raise NotImplementedError('The marked detector list isn\'t stored for instrument '+self._NAME) |
---|
758 | |
---|
759 | def set_up_for_run(self, base_runno): |
---|
760 | """ |
---|
761 | Needs to run whenever a sample is loaded |
---|
762 | """ |
---|
763 | first = self.DETECTORS['low-angle'] |
---|
764 | second = self.DETECTORS['high-angle'] |
---|
765 | |
---|
766 | first.set_orien('Horizontal') |
---|
767 | #probably _first_spec_num was already set to this when the instrument parameter file was loaded |
---|
768 | first.set_first_spec_num(3) |
---|
769 | second.set_orien('Horizontal') |
---|
770 | second.place_after(first) |
---|
771 | |
---|
772 | def load_transmission_inst(self, ws_trans, ws_direct, beamcentre): |
---|
773 | """ |
---|
774 | Loads information about the setup used for LOQ transmission runs |
---|
775 | """ |
---|
776 | trans_definition_file = os.path.join(config.getString('instrumentDefinition.directory'), self._NAME+'_trans_Definition.xml') |
---|
777 | LoadInstrument(Workspace=ws_trans,Filename= trans_definition_file, RewriteSpectraMap=False) |
---|
778 | LoadInstrument(Workspace=ws_direct, Filename = trans_definition_file, RewriteSpectraMap=False) |
---|
779 | |
---|
780 | def cur_detector_position(self, ws_name): |
---|
781 | """Return the position of the center of the detector bank""" |
---|
782 | ws = mtd[ws_name] |
---|
783 | pos = ws.getInstrument().getComponentByName(self.cur_detector().name()).getPos() |
---|
784 | cent_pos = 317.5/1000.0 |
---|
785 | return [cent_pos - pos.getX(), cent_pos - pos.getY()] |
---|
786 | |
---|
787 | class SANS2D(ISISInstrument): |
---|
788 | """ |
---|
789 | The SANS2D instrument has movable detectors whose locations have to |
---|
790 | be read in from the workspace logs (Run object) |
---|
791 | """ |
---|
792 | _NAME = 'SANS2D' |
---|
793 | WAV_RANGE_MIN = 2.0 |
---|
794 | WAV_RANGE_MAX = 14.0 |
---|
795 | |
---|
796 | def __init__(self, idf_path=None): |
---|
797 | super(SANS2D, self).__init__(idf_path) |
---|
798 | |
---|
799 | self._marked_dets = [] |
---|
800 | # set to true once the detector positions have been moved to the locations given in the sample logs |
---|
801 | self.corrections_applied = False |
---|
802 | # a warning is issued if the can logs are not the same as the sample |
---|
803 | self._can_logs = {} |
---|
804 | #The user can set the distance between monitor 4 and the rear detector in millimetres, should be negative |
---|
805 | self.monitor_4_offset = None |
---|
806 | #relates the numbers of the monitors to their names in the instrument definition file |
---|
807 | self.monitor_names = {1 : 'monitor1', |
---|
808 | 2 : 'monitor2', |
---|
809 | 3 : 'monitor3', |
---|
810 | 4 : 'monitor4'} |
---|
811 | |
---|
812 | def set_up_for_run(self, base_runno): |
---|
813 | """ |
---|
814 | Handles changes required when a sample is loaded, both generic |
---|
815 | and run specific |
---|
816 | """ |
---|
817 | first = self.DETECTORS['low-angle'] |
---|
818 | second = self.DETECTORS['high-angle'] |
---|
819 | |
---|
820 | try: |
---|
821 | base_runno = int(base_runno) |
---|
822 | #first deal with some special cases |
---|
823 | if base_runno < 568: |
---|
824 | self.set_incident_mon(73730) |
---|
825 | first.set_first_spec_num(1) |
---|
826 | first.set_orien('Vertical') |
---|
827 | second.set_orien('Vertical') |
---|
828 | elif (base_runno >= 568 and base_runno < 684): |
---|
829 | first.set_first_spec_num(9) |
---|
830 | first.set_orien('Rotated') |
---|
831 | second.set_orien('Rotated') |
---|
832 | else: |
---|
833 | #this is the default case |
---|
834 | first.set_first_spec_num(9) |
---|
835 | first.set_orien('Horizontal') |
---|
836 | second.set_orien('Horizontal') |
---|
837 | except ValueError: |
---|
838 | #this is the default case |
---|
839 | first.set_first_spec_num(9) |
---|
840 | first.set_orien('Horizontal') |
---|
841 | # empty instrument number spectra differently. |
---|
842 | if base_runno == 'emptyInstrument': |
---|
843 | second.set_orien('HorizontalFlipped') |
---|
844 | else: |
---|
845 | second.set_orien('Horizontal') |
---|
846 | |
---|
847 | #as spectrum numbers of the first detector have changed we'll move those in the second too |
---|
848 | second.place_after(first) |
---|
849 | |
---|
850 | def getDetValues(self, ws_name): |
---|
851 | """ |
---|
852 | Retrive the values of Front_Det_Z, Front_Det_X, Front_Det_Rot, Rear_Det_Z and Rear_Det_X from |
---|
853 | the workspace. If it does not find the value at the run info, it takes as default value the |
---|
854 | self.FRONT_DET_Z, self... which are extracted from the sample workspace at apply_detector_log. |
---|
855 | |
---|
856 | This is done to allow the function move_components to use the correct values and not to use |
---|
857 | all the values for TRANS ans SAMPLE the same, as sometimes, this assumption is not valid. |
---|
858 | |
---|
859 | The reason for this method is explained at the ticket http://trac.mantidproject.org/mantid/ticket/7314. |
---|
860 | """ |
---|
861 | # set the default value for these variables |
---|
862 | values = [self.FRONT_DET_Z, self.FRONT_DET_X, self.FRONT_DET_ROT, self.REAR_DET_Z, self.REAR_DET_X] |
---|
863 | # get these variables from the workspace run |
---|
864 | run_info = mtd[str(ws_name)].run() |
---|
865 | ind = 0 |
---|
866 | for name in ('Front_Det_Z', 'Front_Det_X', 'Front_Det_Rot', |
---|
867 | 'Rear_Det_Z','Rear_Det_X'): |
---|
868 | try: |
---|
869 | var = run_info.get(name).value |
---|
870 | if hasattr(var, '__iter__'): |
---|
871 | var = var[-1] |
---|
872 | values[ind] = float(var) |
---|
873 | except: |
---|
874 | pass # ignore, because we do have a default value |
---|
875 | ind+=1 |
---|
876 | #return these variables |
---|
877 | return tuple(values) |
---|
878 | |
---|
879 | |
---|
880 | def move_components(self, ws, xbeam, ybeam): |
---|
881 | """ |
---|
882 | Move the locations of the sample and detector bank based on the passed beam center |
---|
883 | and information from the sample workspace logs. If the location of the monitor was |
---|
884 | set with TRANS/TRANSPEC=4/SHIFT=... this function does the move instrument |
---|
885 | @param ws: workspace containing the instrument information |
---|
886 | @param xbeam: x-position of the beam in meters |
---|
887 | @param ybeam: y-position of the beam in meters |
---|
888 | @return: the locations of (in the new coordinates) beam center, center of detector bank |
---|
889 | """ |
---|
890 | frontDet = self.getDetector('front') |
---|
891 | rearDet = self.getDetector('rear') |
---|
892 | |
---|
893 | FRONT_DET_Z, FRONT_DET_X, FRONT_DET_ROT, REAR_DET_Z, REAR_DET_X = self.getDetValues(ws) |
---|
894 | |
---|
895 | # Deal with front detector |
---|
896 | |
---|
897 | # 11/11/14 RKH need to add tilt of detector, in degrees, with respect to the horizontal or vertical of the detector plane |
---|
898 | # this time we can rotate about the detector's own axis so can use RotateInstrumentComponent, ytilt rotates about x axis, xtilt rotates about z axis |
---|
899 | # |
---|
900 | RotateInstrumentComponent(Workspace=ws,ComponentName= self.getDetector('front').name(), X = "1.", Y = "0.", Z = "0.", Angle = frontDet.y_tilt) |
---|
901 | RotateInstrumentComponent(Workspace=ws,ComponentName= self.getDetector('front').name(), X = "0.", Y = "0.", Z = "1.", Angle = frontDet.x_tilt) |
---|
902 | |
---|
903 | # 9/1/2 this all dates to Richard Heenan & Russell Taylor's original python development for SANS2d |
---|
904 | # the rotation axis on the SANS2d front detector is actually set front_det_radius = 306mm behind the detector. |
---|
905 | # Since RotateInstrumentComponent will only rotate about the centre of the detector, we have to to the rest here. |
---|
906 | # rotate front detector according to value in log file and correction value provided in user file |
---|
907 | rotateDet = (-FRONT_DET_ROT - frontDet.rot_corr) |
---|
908 | RotateInstrumentComponent(Workspace=ws,ComponentName= self.getDetector('front').name(), X="0.", Y="1.0", Z="0.", Angle=rotateDet) |
---|
909 | RotRadians = math.pi*(FRONT_DET_ROT + frontDet.rot_corr)/180. |
---|
910 | # The rear detector is translated to the beam position using the beam centre coordinates in the user file. |
---|
911 | # (Note that the X encoder values in NOT used for the rear detector.) |
---|
912 | # The front detector is translated using the difference in X encoder values, with a correction from the user file. |
---|
913 | # 21/3/12 RKH [In reality only the DIFFERENCE in X encoders is used, having separate X corrections for both detectors is unnecessary, |
---|
914 | # but we will continue with this as it makes the mask file smore logical and avoids a retrospective change.] |
---|
915 | # 21/3/12 RKH add .side_corr allows rotation axis of the front detector being offset from the detector X=0 |
---|
916 | # this inserts *(1.0-math.cos(RotRadians)) into xshift, and |
---|
917 | # - frontDet.side_corr*math.sin(RotRadians) into zshift. |
---|
918 | # (Note we may yet need to introduce further corrections for parallax errors in the detectors, which may be wavelength dependent!) |
---|
919 | xshift = (REAR_DET_X + rearDet.x_corr -frontDet.x_corr - FRONT_DET_X -frontDet.side_corr*(1-math.cos(RotRadians)) + (self.FRONT_DET_RADIUS +frontDet.radius_corr)*math.sin(RotRadians) )/1000. - self.FRONT_DET_DEFAULT_X_M - xbeam |
---|
920 | yshift = (frontDet.y_corr/1000. - ybeam) |
---|
921 | # Note don't understand the comment below (9/1/12 these are comments from the original python code, you may remove them if you like!) |
---|
922 | # default in instrument description is 23.281m - 4.000m from sample at 19,281m ! |
---|
923 | # need to add ~58mm to det1 to get to centre of detector, before it is rotated. |
---|
924 | # 21/3/12 RKH add .radius_corr |
---|
925 | zshift = (FRONT_DET_Z + frontDet.z_corr + (self.FRONT_DET_RADIUS +frontDet.radius_corr)*(1 - math.cos(RotRadians)) - frontDet.side_corr*math.sin(RotRadians))/1000. |
---|
926 | zshift -= self.FRONT_DET_DEFAULT_SD_M |
---|
927 | MoveInstrumentComponent(Workspace=ws,ComponentName= self.getDetector('front').name(), X = xshift, Y = yshift, Z = zshift, RelativePosition="1") |
---|
928 | |
---|
929 | |
---|
930 | # deal with rear detector |
---|
931 | |
---|
932 | # 11/11/14 RKH need to add tilt of detector, in degrees, with respect to the horizontal or vertical of the detector plane |
---|
933 | # Best to do the tilts first, while the detector is still centred on the z axis, ytilt rotates about x axis, xtilt rotates about z axis |
---|
934 | # NOTE the beam centre coordinates may change |
---|
935 | RotateInstrumentComponent(Workspace=ws,ComponentName= rearDet.name(), X = "1.", Y = "0.", Z = "0.", Angle = rearDet.y_tilt) |
---|
936 | RotateInstrumentComponent(Workspace=ws,ComponentName= rearDet.name(), X = "0.", Y = "0.", Z = "1.", Angle = rearDet.x_tilt) |
---|
937 | |
---|
938 | xshift = -xbeam |
---|
939 | yshift = -ybeam |
---|
940 | zshift = (REAR_DET_Z + rearDet.z_corr)/1000. |
---|
941 | zshift -= self.REAR_DET_DEFAULT_SD_M |
---|
942 | sanslog.notice("Setup move "+str(xshift*1000.)+" "+str(yshift*1000.)+" "+str(zshift*1000.)) |
---|
943 | MoveInstrumentComponent(Workspace=ws,ComponentName= rearDet.name(), X = xshift, Y = yshift, Z = zshift, RelativePosition="1") |
---|
944 | |
---|
945 | |
---|
946 | self.move_all_components(ws) |
---|
947 | |
---|
948 | #this implements the TRANS/TRANSPEC=4/SHIFT=... line, this overrides any other monitor move |
---|
949 | if self.monitor_4_offset: |
---|
950 | #get the current location of the monitor |
---|
951 | component = 'monitor4' |
---|
952 | ws = mtd[str(ws)] |
---|
953 | mon = ws.getInstrument().getComponentByName(component) |
---|
954 | z_orig = mon.getPos().getZ() |
---|
955 | |
---|
956 | #the location is relative to the rear-detector, get its location |
---|
957 | det = ws.getInstrument().getComponentByName(self.cur_detector().name()) |
---|
958 | det_z = det.getPos().getZ() |
---|
959 | |
---|
960 | monitor_4_offset = self.monitor_4_offset/1000. |
---|
961 | z_new = det_z + monitor_4_offset |
---|
962 | z_move = z_new - z_orig |
---|
963 | MoveInstrumentComponent(Workspace=ws,ComponentName= component, Z=z_move, |
---|
964 | RelativePosition=True) |
---|
965 | sanslog.notice('Monitor 4 is at z = ' + str(z_new) ) |
---|
966 | |
---|
967 | # Are these returned values used anywhere? |
---|
968 | if self.cur_detector().name() == 'front-detector': |
---|
969 | beam_cen = [0.0, 0.0] |
---|
970 | det_cen = [0.0, 0.0] |
---|
971 | else: |
---|
972 | beam_cen = [0.0,0.0] |
---|
973 | det_cen = [-xbeam, -ybeam] |
---|
974 | |
---|
975 | return beam_cen, det_cen |
---|
976 | |
---|
977 | def get_detector_log(self, wksp): |
---|
978 | """ |
---|
979 | Reads information about the state of the instrument on the information |
---|
980 | stored in the sample |
---|
981 | @param logs: a workspace pointer |
---|
982 | @return the values that were read as a dictionary |
---|
983 | """ |
---|
984 | self._marked_dets = [] |
---|
985 | wksp = su.getWorkspaceReference(wksp) |
---|
986 | #assume complete log information is stored in the first entry, it isn't stored in the group workspace itself |
---|
987 | if isinstance(wksp, WorkspaceGroup): |
---|
988 | wksp = wksp[0] |
---|
989 | |
---|
990 | samp = wksp.getRun() |
---|
991 | |
---|
992 | logvalues = {} |
---|
993 | logvalues['Front_Det_Z'] = self._get_const_num(samp, 'Front_Det_Z') |
---|
994 | logvalues['Front_Det_X'] = self._get_const_num(samp, 'Front_Det_X') |
---|
995 | logvalues['Front_Det_Rot'] = self._get_const_num(samp, 'Front_Det_Rot') |
---|
996 | logvalues['Rear_Det_Z'] = self._get_const_num(samp, 'Rear_Det_Z') |
---|
997 | logvalues['Rear_Det_X'] = self._get_const_num(samp, 'Rear_Det_X') |
---|
998 | |
---|
999 | return logvalues |
---|
1000 | |
---|
1001 | def _get_const_num(self, log_data, log_name): |
---|
1002 | """ |
---|
1003 | Get a the named entry from the log object. If the entry is a |
---|
1004 | time series it's assumed to contain unchanging data and the first |
---|
1005 | value is used. The answer must be convertible to float otherwise |
---|
1006 | this throws. |
---|
1007 | @param log_data: the sample object from a workspace |
---|
1008 | @param log_name: a string with the name of the individual entry to load |
---|
1009 | @return: the floating point number |
---|
1010 | @raise TypeError: if that log entry can't be converted to a float |
---|
1011 | """ |
---|
1012 | try: |
---|
1013 | # return the log value if it stored as a single number |
---|
1014 | return float(log_data.getLogData(log_name).value) |
---|
1015 | except TypeError: |
---|
1016 | # Python 2.4 doesn't have datetime.strptime... |
---|
1017 | def format_date(date_string, format, date_str_len): |
---|
1018 | if len(date_string)>date_str_len: |
---|
1019 | date_string = date_string[:date_str_len] |
---|
1020 | from datetime import datetime |
---|
1021 | if sys.version_info[0] == 2 and sys.version_info[1] < 5: |
---|
1022 | import time |
---|
1023 | return datetime(*(time.strptime(date_string, format)[0:6])) |
---|
1024 | else: |
---|
1025 | return datetime.strptime(date_string, format) |
---|
1026 | |
---|
1027 | # if the value was stored as a time series we have an array here |
---|
1028 | property = log_data.getLogData(log_name) |
---|
1029 | |
---|
1030 | size = len(property.value) |
---|
1031 | if size == 1: |
---|
1032 | return float(log_data.getLogData(log_name).value[0]) |
---|
1033 | |
---|
1034 | start = log_data.getLogData('run_start') |
---|
1035 | dt_0 = format_date(start.value,"%Y-%m-%dT%H:%M:%S",19) |
---|
1036 | for i in range(0, size): |
---|
1037 | dt = format_date(str(property.times[i]),"%Y-%m-%dT%H:%M:%S",19) |
---|
1038 | if dt > dt_0: |
---|
1039 | if i == 0: |
---|
1040 | return float(log_data.getLogData(log_name).value[0]) |
---|
1041 | else: |
---|
1042 | return float(log_data.getLogData(log_name).value[i-1]) |
---|
1043 | |
---|
1044 | # this gets executed if all entries is before the start-time |
---|
1045 | return float(log_data.getLogData(log_name).value[size-1]) |
---|
1046 | |
---|
1047 | def apply_detector_logs(self, logvalues): |
---|
1048 | #apply the corrections that came from the logs |
---|
1049 | self.FRONT_DET_Z = float(logvalues['Front_Det_Z']) |
---|
1050 | self.FRONT_DET_X = float(logvalues['Front_Det_X']) |
---|
1051 | self.FRONT_DET_ROT = float(logvalues['Front_Det_Rot']) |
---|
1052 | self.REAR_DET_Z = float(logvalues['Rear_Det_Z']) |
---|
1053 | self.REAR_DET_X = float(logvalues['Rear_Det_X']) |
---|
1054 | self.corrections_applied = True |
---|
1055 | if len(self._can_logs) > 0: |
---|
1056 | self.check_can_logs(self._can_logs) |
---|
1057 | |
---|
1058 | def check_can_logs(self, new_logs): |
---|
1059 | """ |
---|
1060 | Tests if applying the corrections from the passed logvalues |
---|
1061 | would give the same result as the corrections that were |
---|
1062 | already made |
---|
1063 | @param new_logs: the new values to check are equivalent |
---|
1064 | @return: True if the are the same False if not |
---|
1065 | """ |
---|
1066 | if not self.corrections_applied: |
---|
1067 | #the check needs to wait until there's something to compare against |
---|
1068 | self._can_logs = new_logs |
---|
1069 | |
---|
1070 | if len(new_logs) == 0: |
---|
1071 | return False |
---|
1072 | |
---|
1073 | existing_values = [] |
---|
1074 | existing_values.append(self.FRONT_DET_Z) |
---|
1075 | existing_values.append(self.FRONT_DET_X) |
---|
1076 | existing_values.append(self.FRONT_DET_ROT) |
---|
1077 | existing_values.append(self.REAR_DET_Z) |
---|
1078 | existing_values.append(self.REAR_DET_X) |
---|
1079 | |
---|
1080 | new_values = [] |
---|
1081 | new_values.append(float(new_logs['Front_Det_Z'])) |
---|
1082 | new_values.append(float(new_logs['Front_Det_X'])) |
---|
1083 | new_values.append(float(new_logs['Front_Det_Rot'])) |
---|
1084 | new_values.append(float(new_logs['Rear_Det_Z'])) |
---|
1085 | new_values.append(float(new_logs['Rear_Det_X'])) |
---|
1086 | |
---|
1087 | errors = 0 |
---|
1088 | corr_names = ['Front_Det_Z', 'Front_Det_X','Front_Det_Rot', 'Rear_Det_Z', 'Rear_Det_X'] |
---|
1089 | for i in range(0, len(existing_values)): |
---|
1090 | if math.fabs(existing_values[i] - new_values[i]) > 5e-04: |
---|
1091 | sanslog.warning('values differ between sample and can runs: Sample ' + corr_names[i] + ' = ' + str(existing_values[i]) + \ |
---|
1092 | ', can value is ' + str(new_values[i])) |
---|
1093 | errors += 1 |
---|
1094 | |
---|
1095 | self.append_marked(corr_names[i]) |
---|
1096 | |
---|
1097 | #the check has been done clear up |
---|
1098 | self._can_logs = {} |
---|
1099 | |
---|
1100 | return errors == 0 |
---|
1101 | |
---|
1102 | def append_marked(self, detNames): |
---|
1103 | self._marked_dets.append(detNames) |
---|
1104 | |
---|
1105 | def get_marked_dets(self): |
---|
1106 | return self._marked_dets |
---|
1107 | |
---|
1108 | def load_transmission_inst(self, ws_trans, ws_direct, beamcentre): |
---|
1109 | """ |
---|
1110 | SANS2D requires the centralize the detectors of the transmission |
---|
1111 | as well as the sample and can. |
---|
1112 | """ |
---|
1113 | self.move_components(ws_trans, beamcentre[0], beamcentre[1]) |
---|
1114 | if ws_trans != ws_direct: |
---|
1115 | self.move_components(ws_direct, beamcentre[0], beamcentre[1]) |
---|
1116 | |
---|
1117 | |
---|
1118 | def cur_detector_position(self, ws_name): |
---|
1119 | """Return the position of the center of the detector bank""" |
---|
1120 | ws = mtd[ws_name] |
---|
1121 | pos = ws.getInstrument().getComponentByName(self.cur_detector().name()).getPos() |
---|
1122 | |
---|
1123 | return [-pos.getX(), -pos.getY()] |
---|
1124 | |
---|
1125 | def on_load_sample(self, ws_name, beamcentre, isSample): |
---|
1126 | """For SANS2D in addition of the operations defines in on_load_sample of ISISInstrument |
---|
1127 | it has to deal with the log, which defines some offsets for the movement of the |
---|
1128 | detector bank. |
---|
1129 | """ |
---|
1130 | ws_ref = mtd[str(ws_name)] |
---|
1131 | try: |
---|
1132 | log = self.get_detector_log(ws_ref) |
---|
1133 | if log == "": |
---|
1134 | raise "Invalid log" |
---|
1135 | except: |
---|
1136 | if isSample: |
---|
1137 | raise RuntimeError('Sample logs cannot be loaded, cannot continue') |
---|
1138 | else: |
---|
1139 | logger.warning("Can logs could not be loaded, using sample values.") |
---|
1140 | |
---|
1141 | |
---|
1142 | if isSample: |
---|
1143 | self.apply_detector_logs(log) |
---|
1144 | else: |
---|
1145 | self.check_can_logs(log) |
---|
1146 | |
---|
1147 | |
---|
1148 | ISISInstrument.on_load_sample(self, ws_name, beamcentre, isSample) |
---|
1149 | |
---|
1150 | |
---|
1151 | class LARMOR(ISISInstrument): |
---|
1152 | _NAME = 'LARMOR' |
---|
1153 | WAV_RANGE_MIN = 2.2 |
---|
1154 | WAV_RANGE_MAX = 10.0 |
---|
1155 | def __init__(self): |
---|
1156 | super(LARMOR,self).__init__('LARMOR_Definition.xml') |
---|
1157 | self.monitor_names = dict() |
---|
1158 | |
---|
1159 | for i in range(1,6): |
---|
1160 | self.monitor_names[i] = 'monitor'+str(i) |
---|
1161 | |
---|
1162 | def set_up_for_run(self, base_runno): |
---|
1163 | """ |
---|
1164 | Needs to run whenever a sample is loaded |
---|
1165 | """ |
---|
1166 | first = self.DETECTORS['low-angle'] |
---|
1167 | second = self.DETECTORS['high-angle'] |
---|
1168 | |
---|
1169 | first.set_orien('Horizontal') |
---|
1170 | first.set_first_spec_num(10) |
---|
1171 | second.set_orien('Horizontal') |
---|
1172 | second.place_after(first) |
---|
1173 | |
---|
1174 | def move_components(self, ws, xbeam, ybeam): |
---|
1175 | self.move_all_components(ws) |
---|
1176 | |
---|
1177 | detBanch = self.getDetector('rear') |
---|
1178 | |
---|
1179 | xshift = -xbeam |
---|
1180 | yshift = -ybeam |
---|
1181 | #zshift = ( detBanch.z_corr)/1000. |
---|
1182 | #zshift -= self.REAR_DET_DEFAULT_SD_M |
---|
1183 | zshift = 0 |
---|
1184 | sanslog.notice("Setup move " + str(xshift*1000) + " " + str(yshift*1000) + " " + str(zshift*1000)) |
---|
1185 | MoveInstrumentComponent(ws, ComponentName=detBanch.name(), X=xshift, |
---|
1186 | Y=yshift, Z=zshift) |
---|
1187 | # beam centre, translation |
---|
1188 | return [0.0, 0.0], [-xbeam, -ybeam] |
---|
1189 | |
---|
1190 | def cur_detector_position(self, ws_name): |
---|
1191 | """Return the position of the center of the detector bank""" |
---|
1192 | ws = mtd[ws_name] |
---|
1193 | pos = ws.getInstrument().getComponentByName(self.cur_detector().name()).getPos() |
---|
1194 | |
---|
1195 | return [-pos.getX(), -pos.getY()] |
---|
1196 | |
---|
1197 | class LARMOR(ISISInstrument): |
---|
1198 | _NAME = 'LARMOR' |
---|
1199 | WAV_RANGE_MIN = 2.2 |
---|
1200 | WAV_RANGE_MAX = 10.0 |
---|
1201 | def __init__(self): |
---|
1202 | super(LARMOR,self).__init__('LARMOR_Definition.xml') |
---|
1203 | self.monitor_names = dict() |
---|
1204 | |
---|
1205 | for i in range(1,6): |
---|
1206 | self.monitor_names[i] = 'monitor'+str(i) |
---|
1207 | |
---|
1208 | def set_up_for_run(self, base_runno): |
---|
1209 | """ |
---|
1210 | Needs to run whenever a sample is loaded |
---|
1211 | """ |
---|
1212 | first = self.DETECTORS['low-angle'] |
---|
1213 | second = self.DETECTORS['high-angle'] |
---|
1214 | |
---|
1215 | first.set_orien('Horizontal') |
---|
1216 | first.set_first_spec_num(10) |
---|
1217 | second.set_orien('Horizontal') |
---|
1218 | second.place_after(first) |
---|
1219 | |
---|
1220 | def move_components(self, ws, xbeam, ybeam): |
---|
1221 | self.move_all_components(ws) |
---|
1222 | |
---|
1223 | detBanch = self.getDetector('rear') |
---|
1224 | |
---|
1225 | xshift = -xbeam |
---|
1226 | yshift = -ybeam |
---|
1227 | #zshift = ( detBanch.z_corr)/1000. |
---|
1228 | #zshift -= self.REAR_DET_DEFAULT_SD_M |
---|
1229 | zshift = 0 |
---|
1230 | sanslog.notice("Setup move " + str(xshift*1000) + " " + str(yshift*1000) + " " + str(zshift*1000)) |
---|
1231 | MoveInstrumentComponent(ws, ComponentName=detBanch.name(), X=xshift, |
---|
1232 | Y=yshift, Z=zshift) |
---|
1233 | # beam centre, translation |
---|
1234 | return [0.0, 0.0], [-xbeam, -ybeam] |
---|
1235 | |
---|
1236 | def load_transmission_inst(self, ws_trans, ws_direct, beamcentre): |
---|
1237 | """ |
---|
1238 | Not required for SANS2D |
---|
1239 | """ |
---|
1240 | pass |
---|
1241 | |
---|
1242 | def cur_detector_position(self, ws_name): |
---|
1243 | """Return the position of the center of the detector bank""" |
---|
1244 | ws = mtd[ws_name] |
---|
1245 | pos = ws.getInstrument().getComponentByName(self.cur_detector().name()).getPos() |
---|
1246 | |
---|
1247 | return [-pos.getX(), -pos.getY()] |
---|
1248 | |
---|
1249 | |
---|
1250 | |
---|
1251 | if __name__ == '__main__': |
---|
1252 | pass |
---|