source: sasview/DataLoader/manipulations.py @ 35d1092

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 35d1092 was 2e83ff3, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Added sector average (phi and q)

  • Property mode set to 100644
File size: 30.3 KB
Line 
1"""
2    Data manipulations for 2D data sets.
3    Using the meta data information, various types of averaging
4    are performed in Q-space
5"""
6
7"""
8This software was developed by the University of Tennessee as part of the
9Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
10project funded by the US National Science Foundation.
11
12See the license text in license.txt
13
14copyright 2008, University of Tennessee
15"""
16#TODO: copy the meta data from the 2D object to the resulting 1D object
17
18from data_info import plottable_2D, Data1D
19import math
20import numpy
21
22def get_q(dx, dy, det_dist, wavelength):
23    """
24        @param dx: x-distance from beam center [mm]
25        @param dy: y-distance from beam center [mm]
26        @return: q-value at the given position
27    """
28    # Distance from beam center in the plane of detector
29    plane_dist = math.sqrt(dx*dx + dy*dy)
30    # Half of the scattering angle
31    theta      = 0.5*math.atan(plane_dist/det_dist)
32    return (4.0*math.pi/wavelength)*math.sin(theta)
33   
34class _Slab(object):
35    """
36        Compute average I(Q) for a region of interest
37    """
38    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0, bin_width=0.001):
39        # Minimum Qx value [A-1]
40        self.x_min = x_min
41        # Maximum Qx value [A-1]
42        self.x_max = x_max
43        # Minimum Qy value [A-1]
44        self.y_min = y_min
45        # Maximum Qy value [A-1]
46        self.y_max = y_max
47        # Bin width (step size) [A-1]
48        self.bin_width = bin_width
49        # If True, I(|Q|) will be return, otherwise, negative q-values are allowed
50        self.fold = False
51       
52    def __call__(self, data2D): return NotImplemented
53       
54    def _avg(self, data2D, maj):
55        """
56             Compute average I(Q_maj) for a region of interest.
57             The major axis is defined as the axis of Q_maj.
58             The minor axis is the axis that we average over.
59             
60             @param data2D: Data2D object
61             @param maj_min: min value on the major axis
62             @return: Data1D object
63        """
64        if len(data2D.detector) != 1:
65            raise RuntimeError, "_Slab._avg: invalid number of detectors: %g" % len(data2D.detector)
66       
67        pixel_width_x = data2D.detector[0].pixel_size.x
68        pixel_width_y = data2D.detector[0].pixel_size.y
69        det_dist    = data2D.detector[0].distance
70        wavelength  = data2D.source.wavelength
71        center_x    = data2D.detector[0].beam_center.x/pixel_width_x
72        center_y    = data2D.detector[0].beam_center.y/pixel_width_y
73               
74        # Build array of Q intervals
75        if maj=='x':
76            nbins = int(math.ceil((self.x_max-self.x_min)/self.bin_width))
77            qbins = self.bin_width*numpy.arange(nbins)+self.x_min
78        elif maj=='y':
79            nbins = int(math.ceil((self.y_max-self.y_min)/self.bin_width))
80            qbins = self.bin_width*numpy.arange(nbins)+self.y_min           
81        else:
82            raise RuntimeError, "_Slab._avg: unrecognized axis %s" % str(maj)
83                               
84        x  = numpy.zeros(nbins)
85        y  = numpy.zeros(nbins)
86        err_y = numpy.zeros(nbins)
87        y_counts = numpy.zeros(nbins)
88                                               
89        for i in range(numpy.size(data2D.data,1)):
90            # Min and max x-value for the pixel
91            minx = pixel_width_x*(i - center_x)
92            maxx = pixel_width_x*(i+1.0 - center_x)
93           
94            qxmin = get_q(minx, 0.0, det_dist, wavelength)
95            qxmax = get_q(maxx, 0.0, det_dist, wavelength)
96           
97            # Get the count fraction in x for that pixel
98            frac_min = get_pixel_fraction_square(self.x_min, qxmin, qxmax)
99            frac_max = get_pixel_fraction_square(self.x_max, qxmin, qxmax)
100            frac_x = frac_max - frac_min
101           
102            if frac_x == 0: 
103                continue
104           
105            if maj=='x':
106                dx = pixel_width_x*(i+0.5 - center_x)
107                q_value = get_q(dx, 0.0, det_dist, wavelength)
108                if self.fold==False and dx<0:
109                    q_value = -q_value
110                i_q = int(math.ceil((q_value-self.x_min)/self.bin_width)) - 1
111               
112                if i_q<0 or i_q>=nbins:
113                    continue
114                       
115            for j in range(numpy.size(data2D.data,0)):
116                # Min and max y-value for the pixel
117                miny = pixel_width_y*(j - center_y)
118                maxy = pixel_width_y*(j+1.0 - center_y)
119
120                qymin = get_q(0.0, miny, det_dist, wavelength)
121                qymax = get_q(0.0, maxy, det_dist, wavelength)
122               
123                # Get the count fraction in x for that pixel
124                frac_min = get_pixel_fraction_square(self.y_min, qymin, qymax)
125                frac_max = get_pixel_fraction_square(self.y_max, qymin, qymax)
126                frac_y = frac_max - frac_min
127               
128                frac = frac_x * frac_y
129               
130                if frac == 0:
131                    continue
132
133                if maj=='y':
134                    dy = pixel_width_y*(j+0.5 - center_y)
135                    q_value = get_q(0.0, dy, det_dist, wavelength)
136                    if self.fold==False and dy<0:
137                        q_value = -q_value
138                    i_q = int(math.ceil((q_value-self.y_min)/self.bin_width)) - 1
139                   
140                    if i_q<0 or i_q>=nbins:
141                        continue
142           
143                x[i_q]          = q_value
144                y[i_q]         += frac * data2D.data[j][i]
145                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
146                    err_y[i_q] += frac * frac * math.fabs(data2D.data[j][i])
147                else:
148                    err_y[i_q] += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
149                y_counts[i_q]  += frac
150
151        # Average the sums
152        for i in range(nbins):
153            if y_counts[i]>0:
154                err_y[i] = math.sqrt(err_y[i])/y_counts[i]
155                y[i]     = y[i]/y_counts[i]
156       
157        return Data1D(x=x, y=y, dy=err_y)
158       
159class SlabY(_Slab):
160    """
161        Compute average I(Qy) for a region of interest
162    """
163    def __call__(self, data2D):
164        """
165             Compute average I(Qy) for a region of interest
166             
167             @param data2D: Data2D object
168             @return: Data1D object
169        """
170        return self._avg(data2D, 'y')
171       
172class SlabX(_Slab):
173    """
174        Compute average I(Qx) for a region of interest
175    """
176    def __call__(self, data2D):
177        """
178             Compute average I(Qx) for a region of interest
179             
180             @param data2D: Data2D object
181             @return: Data1D object
182        """
183        return self._avg(data2D, 'x') 
184       
185class Boxsum(object):
186    """
187        Perform the sum of counts in a 2D region of interest.
188    """
189    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
190        # Minimum Qx value [A-1]
191        self.x_min = x_min
192        # Maximum Qx value [A-1]
193        self.x_max = x_max
194        # Minimum Qy value [A-1]
195        self.y_min = y_min
196        # Maximum Qy value [A-1]
197        self.y_max = y_max
198
199    def __call__(self, data2D):
200        """
201             Perform the sum in the region of interest
202             
203             @param data2D: Data2D object
204             @return: number of counts, error on number of counts
205        """
206        y, err_y, y_counts = self._sum(data2D)
207       
208        # Average the sums
209        counts = 0 if y_counts==0 else y
210        error  = 0 if y_counts==0 else math.sqrt(err_y)
211       
212        return counts, error
213       
214    def _sum(self, data2D):
215        """
216             Perform the sum in the region of interest
217             @param data2D: Data2D object
218             @return: number of counts, error on number of counts, number of entries summed
219        """
220        if len(data2D.detector) != 1:
221            raise RuntimeError, "Circular averaging: invalid number of detectors: %g" % len(data2D.detector)
222       
223        pixel_width_x = data2D.detector[0].pixel_size.x
224        pixel_width_y = data2D.detector[0].pixel_size.y
225        det_dist    = data2D.detector[0].distance
226        wavelength  = data2D.source.wavelength
227        center_x    = data2D.detector[0].beam_center.x/pixel_width_x
228        center_y    = data2D.detector[0].beam_center.y/pixel_width_y
229               
230        y  = 0.0
231        err_y = 0.0
232        y_counts = 0.0
233               
234        for i in range(numpy.size(data2D.data,1)):
235            # Min and max x-value for the pixel
236            minx = pixel_width_x*(i - center_x)
237            maxx = pixel_width_x*(i+1.0 - center_x)
238           
239            qxmin = get_q(minx, 0.0, det_dist, wavelength)
240            qxmax = get_q(maxx, 0.0, det_dist, wavelength)
241           
242            # Get the count fraction in x for that pixel
243            frac_min = get_pixel_fraction_square(self.x_min, qxmin, qxmax)
244            frac_max = get_pixel_fraction_square(self.x_max, qxmin, qxmax)
245            frac_x = frac_max - frac_min
246           
247            for j in range(numpy.size(data2D.data,0)):
248                # Min and max y-value for the pixel
249                miny = pixel_width_y*(j - center_y)
250                maxy = pixel_width_y*(j+1.0 - center_y)
251
252                qymin = get_q(0.0, miny, det_dist, wavelength)
253                qymax = get_q(0.0, maxy, det_dist, wavelength)
254               
255                # Get the count fraction in x for that pixel
256                frac_min = get_pixel_fraction_square(self.y_min, qymin, qymax)
257                frac_max = get_pixel_fraction_square(self.y_max, qymin, qymax)
258                frac_y = frac_max - frac_min
259               
260                frac = frac_x * frac_y
261
262                y += frac * data2D.data[j][i]
263                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
264                    err_y += frac * frac * math.fabs(data2D.data[j][i])
265                else:
266                    err_y += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
267                y_counts += frac
268       
269        return y, err_y, y_counts
270     
271class Boxavg(Boxsum):
272    """
273        Perform the average of counts in a 2D region of interest.
274    """
275    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
276        super(Boxavg, self).__init__(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max)
277
278    def __call__(self, data2D):
279        """
280             Perform the sum in the region of interest
281             
282             @param data2D: Data2D object
283             @return: average counts, error on average counts
284        """
285        y, err_y, y_counts = self._sum(data2D)
286       
287        # Average the sums
288        counts = 0 if y_counts==0 else y/y_counts
289        error  = 0 if y_counts==0 else math.sqrt(err_y)/y_counts
290       
291        return counts, error
292       
293def get_pixel_fraction_square(x, xmin, xmax):
294    """
295         Return the fraction of the length
296         from xmin to x.
297         
298             A            B
299         +-----------+---------+
300         xmin        x         xmax
301         
302         @param x: x-value
303         @param xmin: minimum x for the length considered
304         @param xmax: minimum x for the length considered
305         @return: (x-xmin)/(xmax-xmin) when xmin < x < xmax
306         
307    """
308    if x<=xmin:
309        return 0.0
310    if x>xmin and x<xmax:
311        return (x-xmin)/(xmax-xmin)
312    else:
313        return 1.0
314
315
316class CircularAverage(object):
317    """
318        Perform circular averaging on 2D data
319       
320        The data returned is the distribution of counts
321        as a function of Q
322    """
323    def __init__(self, r_min=0.0, r_max=0.0, bin_width=0.001):
324        # Minimum radius included in the average [A-1]
325        self.r_min = r_min
326        # Maximum radius included in the average [A-1]
327        self.r_max = r_max
328        # Bin width (step size) [A-1]
329        self.bin_width = bin_width
330
331    def __call__(self, data2D):
332        """
333            Perform circular averaging on the data
334           
335            @param data2D: Data2D object
336            @return: Data1D object
337        """
338        if len(data2D.detector) != 1:
339            raise RuntimeError, "Circular averaging: invalid number of detectors: %g" % len(data2D.detector)
340       
341        pixel_width_x = data2D.detector[0].pixel_size.x
342        pixel_width_y = data2D.detector[0].pixel_size.y
343        det_dist    = data2D.detector[0].distance
344        wavelength  = data2D.source.wavelength
345        center_x    = data2D.detector[0].beam_center.x/pixel_width_x
346        center_y    = data2D.detector[0].beam_center.y/pixel_width_y
347       
348        # Find out the maximum Q range
349        xwidth = numpy.size(data2D.data,1)*pixel_width_x
350        dx_max = xwidth - data2D.detector[0].beam_center.x
351        if xwidth-dx_max>dx_max:
352            dx_max = xwidth-dx_max
353           
354        ywidth = numpy.size(data2D.data,0)*pixel_width_y
355        dy_max = ywidth - data2D.detector[0].beam_center.y
356        if ywidth-dy_max>dy_max:
357            dy_max = ywidth-dy_max
358       
359        qmax = get_q(dx_max, dy_max, det_dist, wavelength)
360       
361        # Build array of Q intervals
362        nbins = int(math.ceil((qmax-self.r_min)/self.bin_width))
363        qbins = self.bin_width*numpy.arange(nbins)+self.r_min
364       
365        x  = numpy.zeros(nbins)
366        y  = numpy.zeros(nbins)
367        err_y = numpy.zeros(nbins)
368        y_counts = numpy.zeros(nbins)
369       
370        for i in range(numpy.size(data2D.data,1)):
371            dx = pixel_width_x*(i+0.5 - center_x)
372           
373            # Min and max x-value for the pixel
374            minx = pixel_width_x*(i - center_x)
375            maxx = pixel_width_x*(i+1.0 - center_x)
376           
377            for j in range(numpy.size(data2D.data,0)):
378                dy = pixel_width_y*(j+0.5 - center_y)
379           
380                q_value = get_q(dx, dy, det_dist, wavelength)
381           
382                # Min and max y-value for the pixel
383                miny = pixel_width_y*(j - center_y)
384                maxy = pixel_width_y*(j+1.0 - center_y)
385               
386                # Calculate the q-value for each corner
387                # q_[x min or max][y min or max]
388                q_00 = get_q(minx, miny, det_dist, wavelength)
389                q_01 = get_q(minx, maxy, det_dist, wavelength)
390                q_10 = get_q(maxx, miny, det_dist, wavelength)
391                q_11 = get_q(maxx, maxy, det_dist, wavelength)
392               
393                # Look for intercept between each side of the pixel
394                # and the constant-q ring for qmax
395                frac_max = get_pixel_fraction(self.r_max, q_00, q_01, q_10, q_11)
396               
397                # Look for intercept between each side of the pixel
398                # and the constant-q ring for qmin
399                frac_min = get_pixel_fraction(self.r_min, q_00, q_01, q_10, q_11)
400               
401                # We are interested in the region between qmin and qmax
402                # therefore the fraction of the surface of the pixel
403                # that we will use to calculate the number of counts to
404                # include is given by:
405                frac = frac_max - frac_min
406
407                i_q = int(math.ceil((q_value-self.r_min)/self.bin_width)) - 1
408           
409                x[i_q]          = q_value
410                y[i_q]         += frac * data2D.data[j][i]
411                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
412                    err_y[i_q] += frac * frac * math.fabs(data2D.data[j][i])
413                else:
414                    err_y[i_q] += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
415                y_counts[i_q]  += frac
416       
417        # Average the sums
418        for i in range(nbins):
419            if y_counts[i]>0:
420                err_y[i] = math.sqrt(err_y[i])/y_counts[i]
421                y[i]     = y[i]/y_counts[i]
422       
423        return Data1D(x=x, y=y, dy=err_y)
424   
425
426class Ring(object):
427    """
428        Defines a ring on a 2D data set.
429        The ring is defined by r_min, r_max, and
430        the position of the center of the ring.
431       
432        The data returned is the distribution of counts
433        around the ring as a function of phi.
434       
435    """
436   
437    def __init__(self, r_min=0, r_max=0, center_x=0, center_y=0):
438        # Minimum radius
439        self.r_min = r_min
440        # Maximum radius
441        self.r_max = r_max
442        # Center of the ring in x
443        self.center_x = center_x
444        # Center of the ring in y
445        self.center_y = center_y
446        # Number of angular bins
447        self.nbins_phi = 20
448       
449    def __call__(self, data2D):
450        """
451            Apply the ring to the data set.
452            Returns the angular distribution for a given q range
453           
454            @param data2D: Data2D object
455            @return: Data1D object
456        """
457        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
458            raise RuntimeError, "Ring averaging only take plottable_2D objects"
459       
460        data = data2D.data
461        qmin = self.r_min
462        qmax = self.r_max
463       
464        if len(data2D.detector) != 1:
465            raise RuntimeError, "Ring averaging: invalid number of detectors: %g" % len(data2D.detector)
466        pixel_width_x = data2D.detector[0].pixel_size.x
467        pixel_width_y = data2D.detector[0].pixel_size.y
468        det_dist = data2D.detector[0].distance
469        wavelength = data2D.source.wavelength
470        #center_x = self.center_x/pixel_width_x
471        #center_y = self.center_y/pixel_width_y
472        center_x    = data2D.detector[0].beam_center.x/pixel_width_x
473        center_y    = data2D.detector[0].beam_center.y/pixel_width_y
474       
475       
476        phi_bins   = numpy.zeros(self.nbins_phi)
477        phi_counts = numpy.zeros(self.nbins_phi)
478        phi_values = numpy.zeros(self.nbins_phi)
479        phi_err    = numpy.zeros(self.nbins_phi)
480       
481        for i in range(numpy.size(data,1)):
482            dx = pixel_width_x*(i+0.5 - center_x)
483           
484            # Min and max x-value for the pixel
485            minx = pixel_width_x*(i - center_x)
486            maxx = pixel_width_x*(i+1.0 - center_x)
487           
488            for j in range(numpy.size(data,0)):
489                dy = pixel_width_y*(j+0.5 - center_y)
490           
491                q_value = get_q(dx, dy, det_dist, wavelength)
492           
493                # Min and max y-value for the pixel
494                miny = pixel_width_y*(j - center_y)
495                maxy = pixel_width_y*(j+1.0 - center_y)
496               
497                # Calculate the q-value for each corner
498                # q_[x min or max][y min or max]
499                q_00 = get_q(minx, miny, det_dist, wavelength)
500                q_01 = get_q(minx, maxy, det_dist, wavelength)
501                q_10 = get_q(maxx, miny, det_dist, wavelength)
502                q_11 = get_q(maxx, maxy, det_dist, wavelength)
503               
504                # Look for intercept between each side of the pixel
505                # and the constant-q ring for qmax
506                frac_max = get_pixel_fraction(qmax, q_00, q_01, q_10, q_11)
507               
508                # Look for intercept between each side of the pixel
509                # and the constant-q ring for qmin
510                frac_min = get_pixel_fraction(qmin, q_00, q_01, q_10, q_11)
511               
512                # We are interested in the region between qmin and qmax
513                # therefore the fraction of the surface of the pixel
514                # that we will use to calculate the number of counts to
515                # include is given by:
516               
517                frac = frac_max - frac_min
518
519                i_phi = int(math.ceil(self.nbins_phi*(math.atan2(dy, dx)+math.pi)/(2.0*math.pi))) - 1
520           
521                phi_bins[i_phi] += frac * data[j][i]
522               
523                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
524                    phi_err[i_phi] += frac * frac * math.fabs(data2D.data[j][i])
525                else:
526                    phi_err[i_phi] += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
527                phi_counts[i_phi] += frac
528       
529        for i in range(self.nbins_phi):
530            phi_bins[i] = phi_bins[i] / phi_counts[i]
531            phi_err[i] = math.sqrt(phi_err[i]) / phi_counts[i]
532            phi_values[i] = 2.0*math.pi/self.nbins_phi*(1.0*i + 0.5)
533           
534        return Data1D(x=phi_values, y=phi_bins, dy=phi_err)
535   
536def get_pixel_fraction(qmax, q_00, q_01, q_10, q_11):
537    """
538        Returns the fraction of the pixel defined by
539        the four corners (q_00, q_01, q_10, q_11) that
540        has q < qmax.
541       
542                q_01                q_11
543        y=1         +--------------+
544                    |              |
545                    |              |
546                    |              |
547        y=0         +--------------+
548                q_00                q_01
549       
550                    x=0            x=1
551       
552    """
553   
554    # y side for x = minx
555    x_0 = get_intercept(qmax, q_00, q_01)
556    # y side for x = maxx
557    x_1 = get_intercept(qmax, q_10, q_11)
558   
559    # x side for y = miny
560    y_0 = get_intercept(qmax, q_00, q_10)
561    # x side for y = maxy
562    y_1 = get_intercept(qmax, q_01, q_11)
563   
564    # surface fraction for a 1x1 pixel
565    frac_max = 0
566   
567    if x_0 and x_1:
568        frac_max = (x_0+x_1)/2.0
569   
570    elif y_0 and y_1:
571        frac_max = (y_0+y_1)/2.0
572   
573    elif x_0 and y_0:
574        if q_00 < q_10:
575            frac_max = x_0*y_0/2.0
576        else:
577            frac_max = 1.0-x_0*y_0/2.0
578   
579    elif x_0 and y_1:
580        if q_00 < q_10:
581            frac_max = x_0*y_1/2.0
582        else:
583            frac_max = 1.0-x_0*y_1/2.0
584   
585    elif x_1 and y_0:
586        if q_00 > q_10:
587            frac_max = x_1*y_0/2.0
588        else:
589            frac_max = 1.0-x_1*y_0/2.0
590   
591    elif x_1 and y_1:
592        if q_00 < q_10:
593            frac_max = 1.0 - (1.0-x_1)*(1.0-y_1)/2.0
594        else:
595            frac_max = (1.0-x_1)*(1.0-y_1)/2.0
596           
597    # If we make it here, there is no intercept between
598    # this pixel and the constant-q ring. We only need
599    # to know if we have to include it or exclude it.
600    elif (q_00+q_01+q_10+q_11)/4.0 < qmax:
601        frac_max = 1.0
602   
603    return frac_max
604             
605def get_intercept(q, q_0, q_1):
606    """
607        Returns the fraction of the side at which the
608        q-value intercept the pixel, None otherwise.
609        The values returned is the fraction ON THE SIDE
610        OF THE LOWEST Q.
611       
612       
613       
614                A        B   
615         +-----------+--------+
616         0                    1     <--- pixel size
617         
618        Q_0 -------- Q ----- Q_1    <--- equivalent Q range
619       
620       
621        if Q_1 > Q_0, A is returned
622        if Q_1 < Q_0, B is returned
623       
624        if Q is outside the range of [Q_0, Q_1], None is returned
625         
626    """
627    if q_1 > q_0:
628        if (q > q_0 and q <= q_1):
629            return (q-q_0)/(q_1 - q_0)   
630    else:
631        if (q > q_1 and q <= q_0):
632            return (q-q_1)/(q_0 - q_1)
633    return None
634   
635
636class _Sector:
637    """
638        Defines a sector region on a 2D data set.
639        The sector is defined by r_min, r_max, phi_min, phi_max,
640        and the position of the center of the ring.
641       
642        Phi is defined between 0 and 2pi
643    """
644    def __init__(self, r_min, r_max, phi_min, phi_max):
645        self.r_min = r_min
646        self.r_max = r_max
647        self.phi_min = phi_min
648        self.phi_max = phi_max
649        self.nbins = 20
650       
651    def _agv(self, data2D, run='phi'):
652        """
653            Perform sector averaging.
654           
655            @param data2D: Data2D object
656            @param run:  define the varying parameter ('phi' or 'q')
657            @return: Data1D object
658        """
659        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
660            raise RuntimeError, "Ring averaging only take plottable_2D objects"
661       
662        data = data2D.data
663        qmin = self.r_min
664        qmax = self.r_max
665       
666        if len(data2D.detector) != 1:
667            raise RuntimeError, "Ring averaging: invalid number of detectors: %g" % len(data2D.detector)
668        pixel_width_x = data2D.detector[0].pixel_size.x
669        pixel_width_y = data2D.detector[0].pixel_size.y
670        det_dist      = data2D.detector[0].distance
671        wavelength    = data2D.source.wavelength
672        center_x      = data2D.detector[0].beam_center.x/pixel_width_x
673        center_y      = data2D.detector[0].beam_center.y/pixel_width_y
674       
675        y        = numpy.zeros(self.nbins)
676        y_counts = numpy.zeros(self.nbins)
677        x        = numpy.zeros(self.nbins)
678        y_err    = numpy.zeros(self.nbins)
679       
680        for i in range(numpy.size(data,1)):
681            dx = pixel_width_x*(i+0.5 - center_x)
682           
683            # Min and max x-value for the pixel
684            minx = pixel_width_x*(i - center_x)
685            maxx = pixel_width_x*(i+1.0 - center_x)
686           
687            for j in range(numpy.size(data,0)):
688                dy = pixel_width_y*(j+0.5 - center_y)
689           
690                q_value = get_q(dx, dy, det_dist, wavelength)
691
692                # Min and max y-value for the pixel
693                miny = pixel_width_y*(j - center_y)
694                maxy = pixel_width_y*(j+1.0 - center_y)
695               
696                # Calculate the q-value for each corner
697                # q_[x min or max][y min or max]
698                q_00 = get_q(minx, miny, det_dist, wavelength)
699                q_01 = get_q(minx, maxy, det_dist, wavelength)
700                q_10 = get_q(maxx, miny, det_dist, wavelength)
701                q_11 = get_q(maxx, maxy, det_dist, wavelength)
702               
703                # Look for intercept between each side of the pixel
704                # and the constant-q ring for qmax
705                frac_max = get_pixel_fraction(qmax, q_00, q_01, q_10, q_11)
706               
707                # Look for intercept between each side of the pixel
708                # and the constant-q ring for qmin
709                frac_min = get_pixel_fraction(qmin, q_00, q_01, q_10, q_11)
710               
711                # We are interested in the region between qmin and qmax
712                # therefore the fraction of the surface of the pixel
713                # that we will use to calculate the number of counts to
714                # include is given by:
715               
716                frac = frac_max - frac_min
717
718                # Compute phi and check whether it's within the limits
719                phi_value = math.atan2(dy, dx)+math.pi
720                if phi_value<self.phi_min or phi_value>self.phi_max:
721                    continue
722                                                   
723                # Check which type of averaging we need
724                if run.lower()=='phi': 
725                    i_bin = int(math.ceil(self.nbins*(phi_value-self.phi_min)/(self.phi_max-self.phi_min))) - 1
726                else:
727                    # If we don't need this pixel, skip the rest of the work
728                    #TODO: an improvement here would be to compute the average
729                    # Q for the pixel from the part that is covered by
730                    # the ring defined by q_min/q_max rather than the complete
731                    # pixel
732                    if q_value<self.r_min or q_value>self.r_max:
733                        continue
734                    i_bin = int(math.ceil(self.nbins*(q_value-self.r_min)/(self.r_max-self.r_min))) - 1
735           
736                try:
737                    y[i_bin] += frac * data[j][i]
738                except:
739                    import sys
740                    print sys.exc_value
741                    print i_bin, frac
742               
743                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
744                    y_err[i_bin] += frac * frac * math.fabs(data2D.data[j][i])
745                else:
746                    y_err[i_bin] += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
747                y_counts[i_bin] += frac
748       
749        for i in range(self.nbins):
750            y[i] = y[i] / y_counts[i]
751            y_err[i] = math.sqrt(y_err[i]) / y_counts[i]
752            # Check which type of averaging we need
753            if run.lower()=='phi':
754                x[i] = (self.phi_max-self.phi_min)/self.nbins*(1.0*i + 0.5)+self.phi_min
755            else:
756                x[i] = (self.r_max-self.r_min)/self.nbins*(1.0*i + 0.5)+self.r_min
757           
758        return Data1D(x=x, y=y, dy=y_err)
759       
760       
761class SectorPhi(_Sector):
762    """
763        Sector average as a function of phi.
764        I(phi) is return and the data is averaged over Q.
765       
766        A sector is defined by r_min, r_max, phi_min, phi_max.
767        The number of bin in phi also has to be defined.
768    """
769    def __call__(self, data2D):
770        """
771            Perform sector average and return I(phi).
772           
773            @param data2D: Data2D object
774            @return: Data1D object
775        """
776        return self._agv(data2D, 'phi')
777
778class SectorQ(_Sector):
779    """
780        Sector average as a function of Q.
781        I(Q) is return and the data is averaged over phi.
782       
783        A sector is defined by r_min, r_max, phi_min, phi_max.
784        The number of bin in Q also has to be defined.
785    """
786    def __call__(self, data2D):
787        """
788            Perform sector average and return I(Q).
789           
790            @param data2D: Data2D object
791            @return: Data1D object
792        """
793        return self._agv(data2D, 'q')
794
795if __name__ == "__main__": 
796
797    from loader import Loader
798   
799
800    d = Loader().load('test/MAR07232_rest.ASC')
801    #d = Loader().load('test/MP_New.sans')
802
803   
804    r = SectorQ(r_min=.005, r_max=.01, phi_min=0.0, phi_max=math.pi/2.0)
805    o = r(d)
806   
807    s = Ring(r_min=.005, r_max=.01) 
808    p = s(d)
809   
810    for i in range(len(o.x)):
811        print o.x[i], o.y[i], o.dy[i]
812   
813 
814   
Note: See TracBrowser for help on using the repository browser.