Ticket #9145: 9145_Calibrate_7.py

File 9145_Calibrate_7.py, 11.5 KB (added by Peter Parker, 6 years ago)
Line 
1import sys
2import itertools
3import operator
4import copy
5import os
6
7import numpy
8import scipy.special
9from mantid.simpleapi import *
10from mantidplot import *
11
12import tube
13from tube_spec import TubeSpec
14from tube_calib_fit_params import TubeCalibFitParams
15from tube_calib import getCalibration
16
17def mean(a):
18        return sum(a) / len(a)
19
20def 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
27def 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
35def 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
47def 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
58def 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
64def 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
86def 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
96def 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
104def 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
110def 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       
116def 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
129def 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
150def 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
171def 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
179BACKGROUND = 10
180
181class 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
210FunctionFactory.subscribe(SANS2DEndErfc)
211
212INF = sys.float_info.max
213TUBE_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.
219THRESHOLD = 500
220
221class 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
233data_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
238known_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
251assert len(known_edge_pairs) == len(data_files)
252
253ignore_guesses = {
254        69 : [20, 21], # right34
255        68 : [20, 21], # left34
256        70 : [20], # left35
257        118 : [12, 13], # left59
258}
259edges_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.
277shadows_to_remove = []
278
279for shadow in reversed(sorted(shadows_to_remove)):
280        del known_edge_pairs[shadow]
281        del data_files[shadow]
282
283known_edge_pairs = numpy.array(known_edge_pairs)
284
285ws_list = [get_integrated_workspace(data_file) for data_file in data_files]
286
287known_left_edge_pairs = copy.copy(known_edge_pairs)
288known_right_edge_pairs = copy.copy(known_edge_pairs + TUBE_OFFSET)
289
290_, boundaries = get_merged_edge_pairs_and_boundaries(known_edge_pairs)
291known_left_edges, _ = get_merged_edge_pairs_and_boundaries(known_left_edge_pairs)
292known_right_edges, _ = get_merged_edge_pairs_and_boundaries(known_right_edge_pairs)
293
294for 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
298result_ws_name = "result"
299
300multiply_ws_list(ws_list, result_ws_name)
301
302result = mtd[result_ws_name]
303original = CloneWorkspace(InputWorkspace=result_ws_name, OutputWorkspace="original")
304
305known_edges_left = list(itertools.chain.from_iterable(known_left_edges))
306known_edges_right = list(itertools.chain.from_iterable(known_right_edges))
307
308margin=10
309
310caltable = None
311
312failed_pixel_guesses = []
313pixel_guesses = []
314
315for 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
374ApplyCalibration(result, caltable)
375
376# Show result in instrument view. (See http://www.mantidproject.org/MantidPlot:_Instrument_View)
377inst_win = getInstrumentView(result_ws_name)
378inst_win.show()
379
380render = inst_win.getTab(InstrumentWindow.RENDER)
381render.setSurfaceType(InstrumentWindow.CYLINDRICAL_Y)
382render.setRange(0.0,900.0)
383
384tree = inst_win.getTab(InstrumentWindow.TREE)
385tree.selectComponentByName("rear-detector")
386
387left = pixel_guesses[0::2]
388right = pixel_guesses[1::2]
389
390left_avg = numpy.array(map(mean, zip(*left)))
391right_avg = numpy.array(map(mean, zip(*right)))
392
393print left_avg
394print right_avg
395print right_avg - left_avg
396