source: sasview/DataLoader/manipulations.py @ 1f8accb

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 1f8accb was f8d0ee7, checked in by Mathieu Doucet <doucetm@…>, 16 years ago
  • Property mode set to 100644
File size: 18.0 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   
34
35
36class Slabs:
37    def __init__(self):
38        pass
39   
40       
41class Boxsum(object):
42    """
43        Perform the sum of counts in a 2D region of interest.
44    """
45    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
46        # Minimum Qx value [A-1]
47        self.x_min = x_min
48        # Maximum Qx value [A-1]
49        self.x_max = x_max
50        # Minimum Qy value [A-1]
51        self.y_min = y_min
52        # Maximum Qy value [A-1]
53        self.y_max = y_max
54
55    def __call__(self, data2D):
56        """
57             Perform the sum in the region of interest
58             
59             @param data2D: Data2D object
60             @return: number of counts, error on number of counts
61        """
62        y, err_y, y_counts = self._sum(data2D)
63       
64        # Average the sums
65        counts = 0 if y_counts==0 else y
66        error  = 0 if y_counts==0 else math.sqrt(err_y)
67       
68        return counts, error
69       
70    def _sum(self, data2D):
71        """
72             Perform the sum in the region of interest
73             @param data2D: Data2D object
74             @return: number of counts, error on number of counts, number of entries summed
75        """
76        if len(data2D.detector) != 1:
77            raise RuntimeError, "Circular averaging: invalid number of detectors: %g" % len(data2D.detector)
78       
79        pixel_width = data2D.detector[0].pixel_size.x
80        det_dist    = data2D.detector[0].distance
81        wavelength  = data2D.source.wavelength
82        center_x    = data2D.detector[0].beam_center.x/pixel_width
83        center_y    = data2D.detector[0].beam_center.y/pixel_width
84               
85        y  = 0.0
86        err_y = 0.0
87        y_counts = 0.0
88               
89        for i in range(len(data2D.data)):
90            # Min and max x-value for the pixel
91            minx = pixel_width*(i - center_x)
92            maxx = pixel_width*(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            for j in range(len(data2D.data)):
103                # Min and max y-value for the pixel
104                miny = pixel_width*(j - center_y)
105                maxy = pixel_width*(j+1.0 - center_y)
106
107                qymin = get_q(0.0, miny, det_dist, wavelength)
108                qymax = get_q(0.0, maxy, det_dist, wavelength)
109               
110                # Get the count fraction in x for that pixel
111                frac_min = get_pixel_fraction_square(self.y_min, qymin, qymax)
112                frac_max = get_pixel_fraction_square(self.y_max, qymin, qymax)
113                frac_y = frac_max - frac_min
114               
115                frac = frac_x * frac_y
116
117                y += frac * data2D.data[j][i]
118                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
119                    err_y += frac * frac * math.fabs(data2D.data[j][i])
120                else:
121                    err_y += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
122                y_counts += frac
123       
124        return y, err_y, y_counts
125        # Average the sums
126        counts = 0 if y_counts==0 else y/y_counts
127        error  = 0 if y_counts==0 else math.sqrt(err_y)/y_counts
128       
129        return counts, error
130     
131class Boxavg(Boxsum):
132    """
133        Perform the average of counts in a 2D region of interest.
134    """
135    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
136        super(Boxavg, self).__init__(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max)
137
138    def __call__(self, data2D):
139        """
140             Perform the sum in the region of interest
141             
142             @param data2D: Data2D object
143             @return: average counts, error on average counts
144        """
145        y, err_y, y_counts = self._sum(data2D)
146       
147        # Average the sums
148        counts = 0 if y_counts==0 else y/y_counts
149        error  = 0 if y_counts==0 else math.sqrt(err_y)/y_counts
150       
151        return counts, error
152       
153def get_pixel_fraction_square(x, xmin, xmax):
154    """
155         Return the fraction of the length
156         from xmin to x.
157         
158             A            B
159         +-----------+---------+
160         xmin        x         xmax
161         
162         @param x: x-value
163         @param xmin: minimum x for the length considered
164         @param xmax: minimum x for the length considered
165         @return: (x-xmin)/(xmax-xmin) when xmin < x < xmax
166         
167    """
168    if x<=xmin:
169        return 0.0
170    if x>xmin and x<xmax:
171        return (x-xmin)/(xmax-xmin)
172    else:
173        return 1.0
174
175
176class CircularAverage(object):
177    """
178        Perform circular averaging on 2D data
179       
180        The data returned is the distribution of counts
181        as a function of Q
182    """
183    def __init__(self, r_min=0.0, r_max=0.0, bin_width=0.001):
184        # Minimum radius included in the average [A-1]
185        self.r_min = r_min
186        # Maximum radius included in the average [A-1]
187        self.r_max = r_max
188        # Bin width (step size) [A-1]
189        self.bin_width = bin_width
190
191    def __call__(self, data2D):
192        """
193            Perform circular averaging on the data
194           
195            @param data2D: Data2D object
196            @return: Data1D object
197        """
198        if len(data2D.detector) != 1:
199            raise RuntimeError, "Circular averaging: invalid number of detectors: %g" % len(data2D.detector)
200       
201        pixel_width = data2D.detector[0].pixel_size.x
202        det_dist    = data2D.detector[0].distance
203        wavelength  = data2D.source.wavelength
204        center_x    = data2D.detector[0].beam_center.x/pixel_width
205        center_y    = data2D.detector[0].beam_center.y/pixel_width
206       
207        # Find out the maximum Q range
208        xwidth = numpy.size(data2D.data,1)*pixel_width
209        dx_max = xwidth - data2D.detector[0].beam_center.x
210        if xwidth-dx_max>dx_max:
211            dx_max = xwidth-dx_max
212           
213        ywidth = numpy.size(data2D.data,0)*pixel_width
214        dy_max = ywidth - data2D.detector[0].beam_center.y
215        if ywidth-dy_max>dy_max:
216            dy_max = ywidth-dy_max
217       
218        qmax = get_q(dx_max, dy_max, det_dist, wavelength)
219       
220        # Build array of Q intervals
221        nbins = int(math.ceil((qmax-self.r_min)/self.bin_width))
222        qbins = self.bin_width*numpy.arange(nbins)+self.r_min
223       
224        x  = numpy.zeros(nbins)
225        y  = numpy.zeros(nbins)
226        err_y = numpy.zeros(nbins)
227        y_counts = numpy.zeros(nbins)
228       
229        for i in range(len(data2D.data)):
230            dx = pixel_width*(i+0.5 - center_x)
231           
232            # Min and max x-value for the pixel
233            minx = pixel_width*(i - center_x)
234            maxx = pixel_width*(i+1.0 - center_x)
235           
236            for j in range(len(data2D.data)):
237                dy = pixel_width*(j+0.5 - center_y)
238           
239                q_value = get_q(dx, dy, det_dist, wavelength)
240           
241                # Min and max y-value for the pixel
242                miny = pixel_width*(j - center_y)
243                maxy = pixel_width*(j+1.0 - center_y)
244               
245                # Calculate the q-value for each corner
246                # q_[x min or max][y min or max]
247                q_00 = get_q(minx, miny, det_dist, wavelength)
248                q_01 = get_q(minx, maxy, det_dist, wavelength)
249                q_10 = get_q(maxx, miny, det_dist, wavelength)
250                q_11 = get_q(maxx, maxy, det_dist, wavelength)
251               
252                # Look for intercept between each side of the pixel
253                # and the constant-q ring for qmax
254                frac_max = get_pixel_fraction(self.r_max, q_00, q_01, q_10, q_11)
255               
256                # Look for intercept between each side of the pixel
257                # and the constant-q ring for qmin
258                frac_min = get_pixel_fraction(self.r_min, q_00, q_01, q_10, q_11)
259               
260                # We are interested in the region between qmin and qmax
261                # therefore the fraction of the surface of the pixel
262                # that we will use to calculate the number of counts to
263                # include is given by:
264                frac = frac_max - frac_min
265
266                i_q = int(math.ceil((q_value-self.r_min)/self.bin_width)) - 1
267           
268                x[i_q]          = q_value
269                y[i_q]         += frac * data2D.data[j][i]
270                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
271                    err_y[i_q] += frac * frac * math.fabs(data2D.data[j][i])
272                else:
273                    err_y[i_q] += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
274                y_counts[i_q]  += frac
275       
276        # Average the sums
277        for i in range(nbins):
278            if y_counts[i]>0:
279                err_y[i] = math.sqrt(err_y[i])/y_counts[i]
280                y[i]     = y[i]/y_counts[i]
281       
282        return Data1D(x=x, y=y, dy=err_y)
283   
284
285class Ring(object):
286    """
287        Defines a ring on a 2D data set.
288        The ring is defined by r_min, r_max, and
289        the position of the center of the ring.
290       
291        The data returned is the distribution of counts
292        around the ring as a function of phi.
293       
294    """
295   
296    def __init__(self, r_min=0, r_max=0, center_x=0, center_y=0):
297        # Minimum radius
298        self.r_min = r_min
299        # Maximum radius
300        self.r_max = r_max
301        # Center of the ring in x
302        self.center_x = center_x
303        # Center of the ring in y
304        self.center_y = center_y
305        # Number of angular bins
306        self.nbins_phi = 20
307       
308    def __call__(self, data2D):
309        """
310            Apply the ring to the data set.
311            Returns the angular distribution for a given q range
312           
313            @param data2D: Data2D object
314            @return: Data1D object
315        """
316        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
317            raise RuntimeError, "Ring averaging only take plottable_2D objects"
318       
319        data = data2D.data
320        qmin = self.r_min
321        qmax = self.r_max
322       
323        if len(data2D.detector) != 1:
324            raise RuntimeError, "Ring averaging: invalid number of detectors: %g" % len(data2D.detector)
325        pixel_width = data2D.detector[0].pixel_size.x
326        det_dist = data2D.detector[0].distance
327        wavelength = data2D.source.wavelength
328        center_x = self.center_x/pixel_width
329        center_y = self.center_y/pixel_width
330       
331        phi_bins   = numpy.zeros(self.nbins_phi)
332        phi_counts = numpy.zeros(self.nbins_phi)
333        phi_values = numpy.zeros(self.nbins_phi)
334        phi_err    = numpy.zeros(self.nbins_phi)
335       
336        for i in range(len(data)):
337            dx = pixel_width*(i+0.5 - center_x)
338           
339            # Min and max x-value for the pixel
340            minx = pixel_width*(i - center_x)
341            maxx = pixel_width*(i+1.0 - center_x)
342           
343            for j in range(len(data)):
344                dy = pixel_width*(j+0.5 - center_y)
345           
346                q_value = get_q(dx, dy, det_dist, wavelength)
347           
348                # Min and max y-value for the pixel
349                miny = pixel_width*(j - center_y)
350                maxy = pixel_width*(j+1.0 - center_y)
351               
352                # Calculate the q-value for each corner
353                # q_[x min or max][y min or max]
354                q_00 = get_q(minx, miny, det_dist, wavelength)
355                q_01 = get_q(minx, maxy, det_dist, wavelength)
356                q_10 = get_q(maxx, miny, det_dist, wavelength)
357                q_11 = get_q(maxx, maxy, det_dist, wavelength)
358               
359                # Look for intercept between each side of the pixel
360                # and the constant-q ring for qmax
361                frac_max = get_pixel_fraction(qmax, q_00, q_01, q_10, q_11)
362               
363                # Look for intercept between each side of the pixel
364                # and the constant-q ring for qmin
365                frac_min = get_pixel_fraction(qmin, q_00, q_01, q_10, q_11)
366               
367                # We are interested in the region between qmin and qmax
368                # therefore the fraction of the surface of the pixel
369                # that we will use to calculate the number of counts to
370                # include is given by:
371               
372                frac = frac_max - frac_min
373
374                i_phi = int(math.ceil(self.nbins_phi*(math.atan2(dy, dx)+math.pi)/(2.0*math.pi)) - 1)
375           
376                phi_bins[i_phi] += frac * data[j][i]
377               
378                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
379                    phi_err[i_phi] += frac * frac * math.fabs(data2D.data[j][i])
380                else:
381                    phi_err[i_phi] += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
382                phi_counts[i_phi] += frac
383       
384        for i in range(self.nbins_phi):
385            phi_bins[i] = phi_bins[i] / phi_counts[i]
386            phi_err[i] = math.sqrt(phi_err[i]) / phi_counts[i]
387            phi_values[i] = 2.0*math.pi/self.nbins_phi*(1.0*i + 0.5)
388           
389        return Data1D(x=phi_values, y=phi_bins, dy=phi_err)
390   
391def get_pixel_fraction(qmax, q_00, q_01, q_10, q_11):
392    """
393        Returns the fraction of the pixel defined by
394        the four corners (q_00, q_01, q_10, q_11) that
395        has q < qmax.
396       
397                q_01                q_11
398        y=1         +--------------+
399                    |              |
400                    |              |
401                    |              |
402        y=0         +--------------+
403                q_00                q_01
404       
405                    x=0            x=1
406       
407    """
408   
409    # y side for x = minx
410    x_0 = get_intercept(qmax, q_00, q_01)
411    # y side for x = maxx
412    x_1 = get_intercept(qmax, q_10, q_11)
413   
414    # x side for y = miny
415    y_0 = get_intercept(qmax, q_00, q_10)
416    # x side for y = maxy
417    y_1 = get_intercept(qmax, q_01, q_11)
418   
419    # surface fraction for a 1x1 pixel
420    frac_max = 0
421   
422    if x_0 and x_1:
423        frac_max = (x_0+x_1)/2.0
424   
425    elif y_0 and y_1:
426        frac_max = (y_0+y_1)/2.0
427   
428    elif x_0 and y_0:
429        if q_00 < q_10:
430            frac_max = x_0*y_0/2.0
431        else:
432            frac_max = 1.0-x_0*y_0/2.0
433   
434    elif x_0 and y_1:
435        if q_00 < q_10:
436            frac_max = x_0*y_1/2.0
437        else:
438            frac_max = 1.0-x_0*y_1/2.0
439   
440    elif x_1 and y_0:
441        if q_00 > q_10:
442            frac_max = x_1*y_0/2.0
443        else:
444            frac_max = 1.0-x_1*y_0/2.0
445   
446    elif x_1 and y_1:
447        if q_00 < q_10:
448            frac_max = 1.0 - (1.0-x_1)*(1.0-y_1)/2.0
449        else:
450            frac_max = (1.0-x_1)*(1.0-y_1)/2.0
451           
452    # If we make it here, there is no intercept between
453    # this pixel and the constant-q ring. We only need
454    # to know if we have to include it or exclude it.
455    elif (q_00+q_01+q_10+q_11)/4.0 < qmax:
456        frac_max = 1.0
457   
458    return frac_max
459             
460def get_intercept(q, q_0, q_1):
461    """
462        Returns the fraction of the side at which the
463        q-value intercept the pixel, None otherwise.
464        The values returned is the fraction ON THE SIDE
465        OF THE LOWEST Q.
466       
467       
468       
469                A        B   
470         +-----------+--------+
471         0                    1     <--- pixel size
472         
473        Q_0 -------- Q ----- Q_1    <--- equivalent Q range
474       
475       
476        if Q_1 > Q_0, A is returned
477        if Q_1 < Q_0, B is returned
478       
479        if Q is outside the range of [Q_0, Q_1], None is returned
480         
481    """
482    if q_1 > q_0:
483        if (q > q_0 and q <= q_1):
484            return (q-q_0)/(q_1 - q_0)   
485    else:
486        if (q > q_1 and q <= q_0):
487            return (q-q_1)/(q_0 - q_1)
488    return None
489   
490
491class Sector:
492    """
493        Defines a sector region on a 2D data set.
494        The sector is defined by r_min, r_max, phi_min, phi_max,
495        and the position of the center of the ring.
496    """
497    pass
498
499if __name__ == "__main__": 
500
501    from loader import Loader
502   
503
504    d = Loader().load('test/MAR07232_rest.ASC')
505    #d = Loader().load('test/MP_New.sans')
506
507   
508    #r = Boxsum(x_min=.2, x_max=.4, y_min=0.2, y_max=0.4)
509    r = Boxsum(x_min=.01, x_max=.015, y_min=0.01, y_max=0.015)
510    o = r(d)
511    print o
512   
513    r = Boxavg(x_min=.01, x_max=.015, y_min=0.01, y_max=0.015)
514    o = r(d)
515    print o
516   
517 
518   
Note: See TracBrowser for help on using the repository browser.