1 | import sys |
---|
2 | import itertools |
---|
3 | import operator |
---|
4 | import copy |
---|
5 | import os |
---|
6 | |
---|
7 | import numpy |
---|
8 | import scipy.special |
---|
9 | from mantid.simpleapi import * |
---|
10 | from mantidplot import * |
---|
11 | |
---|
12 | import tube |
---|
13 | from tube_spec import TubeSpec |
---|
14 | from tube_calib_fit_params import TubeCalibFitParams |
---|
15 | from tube_calib import getCalibration |
---|
16 | |
---|
17 | def mean(a): |
---|
18 | return sum(a) / len(a) |
---|
19 | |
---|
20 | def pairwise(iterable): |
---|
21 | """Helper function from: http://docs.python.org/2/library/itertools.html: |
---|
22 | s -> (s0,s1), (s1,s2), (s2, s3), ...""" |
---|
23 | a, b = itertools.tee(iterable) |
---|
24 | next(b, None) |
---|
25 | return itertools.izip(a, b) |
---|
26 | |
---|
27 | def edge_pairs_overlap(edges_pair_a, edges_pair_b): |
---|
28 | """""" |
---|
29 | if edges_pair_a[0] < edges_pair_b[0] < edges_pair_a[1]: |
---|
30 | return True |
---|
31 | if edges_pair_b[0] < edges_pair_a[0] < edges_pair_b[1]: |
---|
32 | return True |
---|
33 | return False |
---|
34 | |
---|
35 | def sort_and_merge_overlapping_edge_pairs(edge_pairs): |
---|
36 | """Algorithm from: http://stackoverflow.com/a/5679899/778572""" |
---|
37 | temp = edge_pairs[0] |
---|
38 | for start, end in sorted([sorted(edge_pair) for edge_pair in edge_pairs]): |
---|
39 | if start <= temp[1]: |
---|
40 | temp[1] = max(temp[1], end) |
---|
41 | else: |
---|
42 | yield tuple(temp) |
---|
43 | temp[0] = start |
---|
44 | temp[1] = end |
---|
45 | yield tuple(temp) |
---|
46 | |
---|
47 | def set_counts_to_one_between_x_range(ws, x_1, x_2): |
---|
48 | """""" |
---|
49 | if x_1 > x_2: x_1, x_2 = x_2, x_1 |
---|
50 | for wsIndex in range(ws.getNumberHistograms()): |
---|
51 | try: |
---|
52 | if x_1 < ws.getDetector(wsIndex).getPos().getX() < x_2: |
---|
53 | ws.dataY(wsIndex)[0] = 1 |
---|
54 | except RuntimeError: |
---|
55 | break |
---|
56 | #pass # Ignore "Detector with ID _____ not found" errors. |
---|
57 | |
---|
58 | def set_counts_to_one_outside_x_range(ws, x_1, x_2): |
---|
59 | """""" |
---|
60 | if x_1 > x_2: x_1, x_2 = x_2, x_1 |
---|
61 | set_counts_to_one_between_x_range(ws, -INF, x_1) |
---|
62 | set_counts_to_one_between_x_range(ws, x_2, INF) |
---|
63 | |
---|
64 | def get_merged_edge_pairs_and_boundaries(edge_pairs): |
---|
65 | boundaries = [-INF] |
---|
66 | edge_pairs_merged = [] |
---|
67 | |
---|
68 | temp = edge_pairs[0] |
---|
69 | |
---|
70 | for start, end in sorted([sorted(edge_pair) for edge_pair in edge_pairs]): |
---|
71 | if start <= temp[1]: |
---|
72 | boundary = start + (temp[1] - start) / 2 |
---|
73 | temp[1] = max(temp[1], end) |
---|
74 | if start != temp[0]: |
---|
75 | boundaries.append(boundary) |
---|
76 | else: |
---|
77 | boundaries.append(temp[1] + (start - temp[1]) / 2) |
---|
78 | edge_pairs_merged.append(tuple(temp)) |
---|
79 | temp[0] = start |
---|
80 | temp[1] = end |
---|
81 | edge_pairs_merged.append(tuple(temp)) |
---|
82 | boundaries.append(INF) |
---|
83 | |
---|
84 | return edge_pairs_merged, boundaries |
---|
85 | |
---|
86 | def get_integrated_workspace(data_file): |
---|
87 | ws_name = os.path.splitext(data_file)[0] |
---|
88 | try: |
---|
89 | ws = mtd[ws_name] |
---|
90 | except: |
---|
91 | print "Loading and integrating data from %s." % data_file |
---|
92 | ws = Load(Filename=data_file, OutputWorkspace=ws_name) |
---|
93 | ws = Integration(ws, OutputWorkspace=ws_name) |
---|
94 | return ws |
---|
95 | |
---|
96 | def multiply_ws_list(ws_list, output_ws_name): |
---|
97 | print "Multiplying workspaces together..." |
---|
98 | it = iter(ws_list) |
---|
99 | total = next(it) |
---|
100 | for element in it: |
---|
101 | total = Multiply(RHSWorkspace=total, LHSWorkspace=element, OutputWorkspace = output_ws_name) |
---|
102 | return total |
---|
103 | |
---|
104 | def get_pixels_from_edges(edges): |
---|
105 | pixels = [] |
---|
106 | for edge in edges: |
---|
107 | pixels.append(int((edge + 0.5207) * 512)) |
---|
108 | return numpy.array(pixels) |
---|
109 | |
---|
110 | def get_tube_name(tube_id): |
---|
111 | # Construct the name of the tube based on the id (0-119) given. |
---|
112 | side = TubeSide.getTubeSide(tube_id) |
---|
113 | tube_side_num = tube_id / 2 |
---|
114 | return "rear-detector/" + side + str(tube_side_num) |
---|
115 | |
---|
116 | def get_tube_data(tube_id, ws): |
---|
117 | tube_name = get_tube_name(tube_id) |
---|
118 | |
---|
119 | # Piggy-back the TubeSpec class from Karl's Calibration code so that dealing with tubes is easier than interrogating the IDF ourselves. |
---|
120 | tube_spec = TubeSpec(ws) |
---|
121 | tube_spec.setTubeSpecByString(tube_name) |
---|
122 | assert tube_spec.getNumTubes() == 1 |
---|
123 | tube_ws_index_list = tube_spec.getTube(0)[0] |
---|
124 | assert len(tube_ws_index_list) == 512 |
---|
125 | |
---|
126 | # Return an array of all counts for the tube. |
---|
127 | return numpy.array([ws.dataY(ws_index)[0] for ws_index in tube_ws_index_list]) |
---|
128 | |
---|
129 | def get_tube_edge_pixels(tube_id, ws, cutoff, first_pixel=0, last_pixel=sys.maxint): |
---|
130 | count_data = get_tube_data(tube_id, ws) |
---|
131 | |
---|
132 | if count_data[first_pixel] < cutoff: |
---|
133 | up_edge = True |
---|
134 | else: |
---|
135 | up_edge = False |
---|
136 | |
---|
137 | for i, count in enumerate(count_data[first_pixel:last_pixel+1]): |
---|
138 | pixel = first_pixel + i |
---|
139 | if pixel > last_pixel: |
---|
140 | break |
---|
141 | if up_edge: |
---|
142 | if count >= cutoff: |
---|
143 | up_edge = False |
---|
144 | yield pixel |
---|
145 | else: |
---|
146 | if count < cutoff: |
---|
147 | up_edge = True |
---|
148 | yield pixel |
---|
149 | |
---|
150 | def compile_param_table(tube_id, peaks): |
---|
151 | # Dirty hack we need to replace with something better. |
---|
152 | full_table = CreateEmptyTableWorkspace(OutputWorkspace="ParamTable_Tube" + str(tube_id)) |
---|
153 | full_table.addColumn("int", "Peak #") |
---|
154 | full_table.addColumn("float", "A") |
---|
155 | full_table.addColumn("float", "B") |
---|
156 | full_table.addColumn("float", "C") |
---|
157 | full_table.addColumn("float", "D") |
---|
158 | base_name = "CalibPoint_Parameters_tube%i_peak_%i" |
---|
159 | table_names = [base_name % (tube_id, peak) for peak in range(peaks)] |
---|
160 | for peak, table_name in enumerate(table_names): |
---|
161 | table = mtd[table_name] |
---|
162 | row = [ |
---|
163 | peak, |
---|
164 | table.cell("Value", 0), |
---|
165 | table.cell("Value", 1), |
---|
166 | table.cell("Value", 2), |
---|
167 | table.cell("Value", 3) |
---|
168 | ] |
---|
169 | full_table.addRow(row) |
---|
170 | |
---|
171 | def cleanup_param_tables(tube_id, peaks): |
---|
172 | # Dirty hack we need to replace with something better. |
---|
173 | base_name = "CalibPoint_Parameters_tube%i_peak_%i" |
---|
174 | table_names = [base_name % (tube_id, peak) for peak in range(peaks)] |
---|
175 | for table_name in table_names: |
---|
176 | table = mtd[table_name] |
---|
177 | table.delete() |
---|
178 | |
---|
179 | BACKGROUND = 10 |
---|
180 | |
---|
181 | class SANS2DEndErfc(IFunction1D): |
---|
182 | def init(self): |
---|
183 | self.declareParameter("A", 350.0) |
---|
184 | self.declareParameter("B", 50.0) |
---|
185 | self.declareParameter("C", 6.0) |
---|
186 | self.declareParameter("D", BACKGROUND) |
---|
187 | |
---|
188 | def function1D(self, xvals): |
---|
189 | a = self.getParameterValue("A") |
---|
190 | b = self.getParameterValue("B") |
---|
191 | c = self.getParameterValue("C") |
---|
192 | d = self.getParameterValue("D") |
---|
193 | |
---|
194 | out = a * scipy.special.erfc((b - xvals) / c) + d |
---|
195 | if a < 0: |
---|
196 | out = -2*a * out |
---|
197 | |
---|
198 | return out |
---|
199 | |
---|
200 | def setActiveParameter(self, index, value): |
---|
201 | # Heavy-handed way to constrain background. |
---|
202 | if self.parameterName(index) == "D" and value < 0.0: |
---|
203 | self.setParameter(index, 0.0, False) |
---|
204 | elif self.parameterName(index) == "D" and value > BACKGROUND: |
---|
205 | self.setParameter(index, BACKGROUND, False) |
---|
206 | else: |
---|
207 | self.setParameter(index, value, False) |
---|
208 | |
---|
209 | # Required to have Mantid recognise the new function |
---|
210 | FunctionFactory.subscribe(SANS2DEndErfc) |
---|
211 | |
---|
212 | INF = sys.float_info.max |
---|
213 | TUBE_OFFSET = 0.003 |
---|
214 | |
---|
215 | # The number of counts past which we class something as an edge. This is quite sensitive to change, since we sometimes end up picking |
---|
216 | # up edges more than once, (e.g. we might see an edge at pixels 207 and 209, obviously due to the counts dipping back down below the |
---|
217 | # threshold at pixel 206) which we then have to remove using ignore_guesses. So, if THRESHOLD is changed you're probably going |
---|
218 | # to have to change the contents of ignore_guesses for any affected tubes. |
---|
219 | THRESHOLD = 500 |
---|
220 | |
---|
221 | class TubeSide: |
---|
222 | LEFT = "left" |
---|
223 | RIGHT = "right" |
---|
224 | @classmethod |
---|
225 | def getTubeSide(cls, tube_id): |
---|
226 | if tube_id % 2 == 0: |
---|
227 | return TubeSide.LEFT |
---|
228 | else: |
---|
229 | return TubeSide.RIGHT |
---|
230 | |
---|
231 | # second runs, mostly with M4 removed |
---|
232 | # 23088-add really was added, the hst files are single runs converted to histogram, so can merge with the added file |
---|
233 | data_files = ["SANS2D00023098-hst.nxs","SANS2D00023097-hst.nxs","SANS2D00023096-hst.nxs","SANS2D00023095-hst.nxs","SANS2D00023094-hst.nxs", |
---|
234 | "SANS2D00023093-hst.nxs","SANS2D00023092-hst.nxs","SANS2D00023091-hst.nxs", |
---|
235 | "SANS2D00023088-add.nxs", |
---|
236 | "SANS2D00023084-hst.nxs"] |
---|
237 | |
---|
238 | known_edge_pairs = [ |
---|
239 | [-0.415366832, -0.377084653], |
---|
240 | [-0.334772772, -0.296490594], |
---|
241 | [-0.254178713, -0.215896535], |
---|
242 | [-0.163510396, -0.125228218], |
---|
243 | [-0.082916337, -0.044634158], |
---|
244 | [0.00775198, 0.046034158], |
---|
245 | [0.098420297, 0.136702475], |
---|
246 | [0.189088614, 0.227370792], |
---|
247 | [0.279756931, 0.318039109], |
---|
248 | [0.370425248, 0.408707426] |
---|
249 | ] |
---|
250 | |
---|
251 | assert len(known_edge_pairs) == len(data_files) |
---|
252 | |
---|
253 | ignore_guesses = { |
---|
254 | 69 : [20, 21], # right34 |
---|
255 | 68 : [20, 21], # left34 |
---|
256 | 70 : [20], # left35 |
---|
257 | 118 : [12, 13], # left59 |
---|
258 | } |
---|
259 | edges_not_to_fit = { |
---|
260 | 47 : [10, 11], # right23 |
---|
261 | 49 : [10, 11], # right24 |
---|
262 | 51 : [10, 11], # right25 |
---|
263 | 53 : [10, 11], # right26 |
---|
264 | |
---|
265 | 67 : [19], # right33 |
---|
266 | |
---|
267 | 44 : [10, 11], # left22 |
---|
268 | 46 : [10, 11], # left23 |
---|
269 | 48 : [10, 11], # left24 |
---|
270 | 50 : [10, 11], # left25 |
---|
271 | 52 : [10, 11], # left26 |
---|
272 | |
---|
273 | 68 : [19], # left34 |
---|
274 | } |
---|
275 | |
---|
276 | # Array indices of shadows (edge pairs) to remove. |
---|
277 | shadows_to_remove = [] |
---|
278 | |
---|
279 | for shadow in reversed(sorted(shadows_to_remove)): |
---|
280 | del known_edge_pairs[shadow] |
---|
281 | del data_files[shadow] |
---|
282 | |
---|
283 | known_edge_pairs = numpy.array(known_edge_pairs) |
---|
284 | |
---|
285 | ws_list = [get_integrated_workspace(data_file) for data_file in data_files] |
---|
286 | |
---|
287 | known_left_edge_pairs = copy.copy(known_edge_pairs) |
---|
288 | known_right_edge_pairs = copy.copy(known_edge_pairs + TUBE_OFFSET) |
---|
289 | |
---|
290 | _, boundaries = get_merged_edge_pairs_and_boundaries(known_edge_pairs) |
---|
291 | known_left_edges, _ = get_merged_edge_pairs_and_boundaries(known_left_edge_pairs) |
---|
292 | known_right_edges, _ = get_merged_edge_pairs_and_boundaries(known_right_edge_pairs) |
---|
293 | |
---|
294 | for ws, (boundary_start, boundary_end) in zip(ws_list, pairwise(boundaries)): |
---|
295 | print "Isolating shadow in %s between boundaries %g and %g." % (str(ws), boundary_start, boundary_end) |
---|
296 | set_counts_to_one_outside_x_range(ws, boundary_start, boundary_end) |
---|
297 | |
---|
298 | result_ws_name = "result" |
---|
299 | |
---|
300 | multiply_ws_list(ws_list, result_ws_name) |
---|
301 | |
---|
302 | result = mtd[result_ws_name] |
---|
303 | original = CloneWorkspace(InputWorkspace=result_ws_name, OutputWorkspace="original") |
---|
304 | |
---|
305 | known_edges_left = list(itertools.chain.from_iterable(known_left_edges)) |
---|
306 | known_edges_right = list(itertools.chain.from_iterable(known_right_edges)) |
---|
307 | |
---|
308 | margin=10 |
---|
309 | |
---|
310 | caltable = None |
---|
311 | |
---|
312 | failed_pixel_guesses = [] |
---|
313 | pixel_guesses = [] |
---|
314 | |
---|
315 | for tube_id in range(120): |
---|
316 | tube_name = get_tube_name(tube_id) |
---|
317 | print "\n==================================================" |
---|
318 | print "ID = %i, Name = \"%s\"" % (tube_id, tube_name) |
---|
319 | if TubeSide.getTubeSide(tube_id) == TubeSide.LEFT: |
---|
320 | known_edges = copy.copy(known_edges_left) |
---|
321 | else: |
---|
322 | known_edges = copy.copy(known_edges_right) |
---|
323 | |
---|
324 | guessed_pixels = list(get_tube_edge_pixels(tube_id, result, THRESHOLD, 20, 482)) |
---|
325 | |
---|
326 | print guessed_pixels |
---|
327 | print known_edges |
---|
328 | |
---|
329 | # Remove the pixel guesses that don't correspond to Cd shadows: |
---|
330 | if tube_id in ignore_guesses: |
---|
331 | print "Removing guesses", list(reversed(sorted(ignore_guesses[tube_id]))) |
---|
332 | for index in reversed(sorted(ignore_guesses[tube_id])): |
---|
333 | del guessed_pixels[index] |
---|
334 | |
---|
335 | assert len(guessed_pixels) == len(known_edges) |
---|
336 | |
---|
337 | # Store the guesses for printing out later, along with the tube id and name. |
---|
338 | pixel_guesses.append(guessed_pixels) |
---|
339 | |
---|
340 | # Remove the edges that have been altered by the presence of the beam stop and arm. |
---|
341 | if tube_id in edges_not_to_fit: |
---|
342 | for index in reversed(sorted(edges_not_to_fit[tube_id])): |
---|
343 | del guessed_pixels[index] |
---|
344 | del known_edges[index] |
---|
345 | |
---|
346 | fitPar = TubeCalibFitParams(guessed_pixels, outEdge=10.0, inEdge=10.0) |
---|
347 | funcForm = [2]*len(guessed_pixels) |
---|
348 | |
---|
349 | if caltable: |
---|
350 | caltable = tube.calibrate( |
---|
351 | result, |
---|
352 | tube_name, |
---|
353 | numpy.array(known_edges), |
---|
354 | funcForm, |
---|
355 | rangeList=[0], |
---|
356 | plotTube=[0], |
---|
357 | margin=margin, |
---|
358 | fitPar=fitPar, |
---|
359 | calibTable=caltable) |
---|
360 | else: |
---|
361 | caltable = tube.calibrate( |
---|
362 | result, |
---|
363 | tube_name, |
---|
364 | numpy.array(known_edges), |
---|
365 | funcForm, |
---|
366 | rangeList=[0], |
---|
367 | plotTube=[0], |
---|
368 | margin=margin, |
---|
369 | fitPar=fitPar) |
---|
370 | |
---|
371 | #compile_param_table(tube_id, len(known_edges)) |
---|
372 | #cleanup_param_tables(tube_id, len(known_edges)) |
---|
373 | |
---|
374 | ApplyCalibration(result, caltable) |
---|
375 | |
---|
376 | # Show result in instrument view. (See http://www.mantidproject.org/MantidPlot:_Instrument_View) |
---|
377 | inst_win = getInstrumentView(result_ws_name) |
---|
378 | inst_win.show() |
---|
379 | |
---|
380 | render = inst_win.getTab(InstrumentWindow.RENDER) |
---|
381 | render.setSurfaceType(InstrumentWindow.CYLINDRICAL_Y) |
---|
382 | render.setRange(0.0,900.0) |
---|
383 | |
---|
384 | tree = inst_win.getTab(InstrumentWindow.TREE) |
---|
385 | tree.selectComponentByName("rear-detector") |
---|
386 | |
---|
387 | left = pixel_guesses[0::2] |
---|
388 | right = pixel_guesses[1::2] |
---|
389 | |
---|
390 | left_avg = numpy.array(map(mean, zip(*left))) |
---|
391 | right_avg = numpy.array(map(mean, zip(*right))) |
---|
392 | |
---|
393 | print left_avg |
---|
394 | print right_avg |
---|
395 | print right_avg - left_avg |
---|
396 | |
---|