source: sasview/src/sas/dataloader/manipulations.py @ c8a6c3d7

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 c8a6c3d7 was c8a6c3d7, checked in by Doucet, Mathieu <doucetm@…>, 9 years ago

pylint fixes

  • Property mode set to 100644
File size: 38.5 KB
Line 
1"""
2Data manipulations for 2D data sets.
3Using the meta data information, various types of averaging
4are performed in Q-space
5"""
6#####################################################################
7#This software was developed by the University of Tennessee as part of the
8#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
9#project funded by the US National Science Foundation.
10#See the license text in license.txt
11#copyright 2008, University of Tennessee
12######################################################################
13
14#TODO: copy the meta data from the 2D object to the resulting 1D object
15import math
16import numpy
17
18#from data_info import plottable_2D
19from data_info import Data1D
20
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
35def get_q_compo(dx, dy, det_dist, wavelength, compo=None):
36    """
37    This reduces tiny error at very large q.
38    Implementation of this func is not started yet.<--ToDo
39    """
40    if dy == 0:
41        if dx >= 0:
42            angle_xy = 0
43        else:
44            angle_xy = math.pi
45    else:
46        angle_xy = math.atan(dx / dy)
47
48    if compo == "x":
49        out = get_q(dx, dy, det_dist, wavelength) * math.cos(angle_xy)
50    elif compo == "y":
51        out = get_q(dx, dy, det_dist, wavelength) * math.sin(angle_xy)
52    else:
53        out = get_q(dx, dy, det_dist, wavelength)
54    return out
55
56
57def flip_phi(phi):
58    """
59    Correct phi to within the 0 <= to <= 2pi range
60
61    :return: phi in >=0 and <=2Pi
62    """
63    Pi = math.pi
64    if phi < 0:
65        phi_out = phi + (2 * Pi)
66    elif phi > (2 * Pi):
67        phi_out = phi - (2 * Pi)
68    else:
69        phi_out = phi
70    return phi_out
71
72
73def reader2D_converter(data2d=None):
74    """
75    convert old 2d format opened by IhorReader or danse_reader
76    to new Data2D format
77
78    :param data2d: 2d array of Data2D object
79    :return: 1d arrays of Data2D object
80
81    """
82    if data2d.data == None or data2d.x_bins == None or data2d.y_bins == None:
83        raise ValueError, "Can't convert this data: data=None..."
84    new_x = numpy.tile(data2d.x_bins, (len(data2d.y_bins), 1))
85    new_y = numpy.tile(data2d.y_bins, (len(data2d.x_bins), 1))
86    new_y = new_y.swapaxes(0, 1)
87
88    new_data = data2d.data.flatten()
89    qx_data = new_x.flatten()
90    qy_data = new_y.flatten()
91    q_data = numpy.sqrt(qx_data * qx_data + qy_data * qy_data)
92    if data2d.err_data == None or numpy.any(data2d.err_data <= 0):
93        new_err_data = numpy.sqrt(numpy.abs(new_data))
94    else:
95        new_err_data = data2d.err_data.flatten()
96    mask = numpy.ones(len(new_data), dtype=bool)
97
98    #TODO: make sense of the following two lines...
99    #from sas.dataloader.data_info import Data2D
100    #output = Data2D()
101    output = data2d
102    output.data = new_data
103    output.err_data = new_err_data
104    output.qx_data = qx_data
105    output.qy_data = qy_data
106    output.q_data = q_data
107    output.mask = mask
108
109    return output
110
111
112class _Slab(object):
113    """
114    Compute average I(Q) for a region of interest
115    """
116    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0,
117                 y_max=0.0, bin_width=0.001):
118        # Minimum Qx value [A-1]
119        self.x_min = x_min
120        # Maximum Qx value [A-1]
121        self.x_max = x_max
122        # Minimum Qy value [A-1]
123        self.y_min = y_min
124        # Maximum Qy value [A-1]
125        self.y_max = y_max
126        # Bin width (step size) [A-1]
127        self.bin_width = bin_width
128        # If True, I(|Q|) will be return, otherwise,
129        # negative q-values are allowed
130        self.fold = False
131
132    def __call__(self, data2D):
133        return NotImplemented
134
135    def _avg(self, data2D, maj):
136        """
137        Compute average I(Q_maj) for a region of interest.
138        The major axis is defined as the axis of Q_maj.
139        The minor axis is the axis that we average over.
140
141        :param data2D: Data2D object
142        :param maj_min: min value on the major axis
143        :return: Data1D object
144        """
145        if len(data2D.detector) != 1:
146            msg = "_Slab._avg: invalid number of "
147            msg += " detectors: %g" % len(data2D.detector)
148            raise RuntimeError, msg
149
150        # Get data
151        data = data2D.data[numpy.isfinite(data2D.data)]
152        err_data = data2D.err_data[numpy.isfinite(data2D.data)]
153        qx_data = data2D.qx_data[numpy.isfinite(data2D.data)]
154        qy_data = data2D.qy_data[numpy.isfinite(data2D.data)]
155
156        # Build array of Q intervals
157        if maj == 'x':
158            if self.fold:
159                x_min = 0
160            else:
161                x_min = self.x_min
162            nbins = int(math.ceil((self.x_max - x_min) / self.bin_width))
163        elif maj == 'y':
164            if self.fold:
165                y_min = 0
166            else:
167                y_min = self.y_min
168            nbins = int(math.ceil((self.y_max - y_min) / self.bin_width))
169        else:
170            raise RuntimeError, "_Slab._avg: unrecognized axis %s" % str(maj)
171
172        x = numpy.zeros(nbins)
173        y = numpy.zeros(nbins)
174        err_y = numpy.zeros(nbins)
175        y_counts = numpy.zeros(nbins)
176
177        # Average pixelsize in q space
178        for npts in range(len(data)):
179            # default frac
180            frac_x = 0
181            frac_y = 0
182            # get ROI
183            if self.x_min <= qx_data[npts] and self.x_max > qx_data[npts]:
184                frac_x = 1
185            if self.y_min <= qy_data[npts] and self.y_max > qy_data[npts]:
186                frac_y = 1
187            frac = frac_x * frac_y
188
189            if frac == 0:
190                continue
191            # binning: find axis of q
192            if maj == 'x':
193                q_value = qx_data[npts]
194                min_value = x_min
195            if maj == 'y':
196                q_value = qy_data[npts]
197                min_value = y_min
198            if self.fold and q_value < 0:
199                q_value = -q_value
200            # bin
201            i_q = int(math.ceil((q_value - min_value) / self.bin_width)) - 1
202
203            # skip outside of max bins
204            if i_q < 0 or i_q >= nbins:
205                continue
206
207            #TODO: find better definition of x[i_q] based on q_data
208            x[i_q] += frac * q_value  # min_value + (i_q + 1) * self.bin_width / 2.0
209            y[i_q] += frac * data[npts]
210
211            if err_data == None or err_data[npts] == 0.0:
212                if data[npts] < 0:
213                    data[npts] = -data[npts]
214                err_y[i_q] += frac * frac * data[npts]
215            else:
216                err_y[i_q] += frac * frac * err_data[npts] * err_data[npts]
217            y_counts[i_q] += frac
218
219        # Average the sums
220        for n in range(nbins):
221            err_y[n] = math.sqrt(err_y[n])
222
223        err_y = err_y / y_counts
224        y = y / y_counts
225        x = x / y_counts
226        idx = (numpy.isfinite(y) & numpy.isfinite(x))
227
228        if not idx.any():
229            msg = "Average Error: No points inside ROI to average..."
230            raise ValueError, msg
231        return Data1D(x=x[idx], y=y[idx], dy=err_y[idx])
232
233
234class SlabY(_Slab):
235    """
236    Compute average I(Qy) for a region of interest
237    """
238    def __call__(self, data2D):
239        """
240        Compute average I(Qy) for a region of interest
241
242        :param data2D: Data2D object
243        :return: Data1D object
244        """
245        return self._avg(data2D, 'y')
246
247
248class SlabX(_Slab):
249    """
250    Compute average I(Qx) for a region of interest
251    """
252    def __call__(self, data2D):
253        """
254        Compute average I(Qx) for a region of interest
255        :param data2D: Data2D object
256        :return: Data1D object
257        """
258        return self._avg(data2D, 'x')
259
260
261class Boxsum(object):
262    """
263    Perform the sum of counts in a 2D region of interest.
264    """
265    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
266        # Minimum Qx value [A-1]
267        self.x_min = x_min
268        # Maximum Qx value [A-1]
269        self.x_max = x_max
270        # Minimum Qy value [A-1]
271        self.y_min = y_min
272        # Maximum Qy value [A-1]
273        self.y_max = y_max
274
275    def __call__(self, data2D):
276        """
277        Perform the sum in the region of interest
278
279        :param data2D: Data2D object
280        :return: number of counts, error on number of counts,
281            number of points summed
282        """
283        y, err_y, y_counts = self._sum(data2D)
284
285        # Average the sums
286        counts = 0 if y_counts == 0 else y
287        error = 0 if y_counts == 0 else math.sqrt(err_y)
288
289        # Added y_counts to return, SMK & PDB, 04/03/2013
290        return counts, error, y_counts
291
292    def _sum(self, data2D):
293        """
294        Perform the sum in the region of interest
295
296        :param data2D: Data2D object
297        :return: number of counts,
298            error on number of counts, number of entries summed
299        """
300        if len(data2D.detector) != 1:
301            msg = "Circular averaging: invalid number "
302            msg += "of detectors: %g" % len(data2D.detector)
303            raise RuntimeError, msg
304        # Get data
305        data = data2D.data[numpy.isfinite(data2D.data)]
306        err_data = data2D.err_data[numpy.isfinite(data2D.data)]
307        qx_data = data2D.qx_data[numpy.isfinite(data2D.data)]
308        qy_data = data2D.qy_data[numpy.isfinite(data2D.data)]
309
310        y = 0.0
311        err_y = 0.0
312        y_counts = 0.0
313
314        # Average pixelsize in q space
315        for npts in range(len(data)):
316            # default frac
317            frac_x = 0
318            frac_y = 0
319
320            # get min and max at each points
321            qx = qx_data[npts]
322            qy = qy_data[npts]
323
324            # get the ROI
325            if self.x_min <= qx and self.x_max > qx:
326                frac_x = 1
327            if self.y_min <= qy and self.y_max > qy:
328                frac_y = 1
329            #Find the fraction along each directions
330            frac = frac_x * frac_y
331            if frac == 0:
332                continue
333            y += frac * data[npts]
334            if err_data == None or err_data[npts] == 0.0:
335                if data[npts] < 0:
336                    data[npts] = -data[npts]
337                err_y += frac * frac * data[npts]
338            else:
339                err_y += frac * frac * err_data[npts] * err_data[npts]
340            y_counts += frac
341        return y, err_y, y_counts
342
343
344class Boxavg(Boxsum):
345    """
346    Perform the average of counts in a 2D region of interest.
347    """
348    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
349        super(Boxavg, self).__init__(x_min=x_min, x_max=x_max,
350                                     y_min=y_min, y_max=y_max)
351
352    def __call__(self, data2D):
353        """
354        Perform the sum in the region of interest
355
356        :param data2D: Data2D object
357        :return: average counts, error on average counts
358
359        """
360        y, err_y, y_counts = self._sum(data2D)
361
362        # Average the sums
363        counts = 0 if y_counts == 0 else y / y_counts
364        error = 0 if y_counts == 0 else math.sqrt(err_y) / y_counts
365
366        return counts, error
367
368
369def get_pixel_fraction_square(x, xmin, xmax):
370    """
371    Return the fraction of the length
372    from xmin to x.::
373
374           A            B
375       +-----------+---------+
376       xmin        x         xmax
377
378    :param x: x-value
379    :param xmin: minimum x for the length considered
380    :param xmax: minimum x for the length considered
381    :return: (x-xmin)/(xmax-xmin) when xmin < x < xmax
382
383    """
384    if x <= xmin:
385        return 0.0
386    if x > xmin and x < xmax:
387        return (x - xmin) / (xmax - xmin)
388    else:
389        return 1.0
390
391
392class CircularAverage(object):
393    """
394    Perform circular averaging on 2D data
395
396    The data returned is the distribution of counts
397    as a function of Q
398    """
399    def __init__(self, r_min=0.0, r_max=0.0, bin_width=0.0005):
400        # Minimum radius included in the average [A-1]
401        self.r_min = r_min
402        # Maximum radius included in the average [A-1]
403        self.r_max = r_max
404        # Bin width (step size) [A-1]
405        self.bin_width = bin_width
406
407    def __call__(self, data2D, ismask=False):
408        """
409        Perform circular averaging on the data
410
411        :param data2D: Data2D object
412        :return: Data1D object
413        """
414        # Get data W/ finite values
415        data = data2D.data[numpy.isfinite(data2D.data)]
416        q_data = data2D.q_data[numpy.isfinite(data2D.data)]
417        err_data = data2D.err_data[numpy.isfinite(data2D.data)]
418        mask_data = data2D.mask[numpy.isfinite(data2D.data)]
419
420        dq_data = None
421
422        # Get the dq for resolution averaging
423        if data2D.dqx_data != None and data2D.dqy_data != None:
424            # The pinholes and det. pix contribution present
425            # in both direction of the 2D which must be subtracted when
426            # converting to 1D: dq_overlap should calculated ideally at
427            # q = 0. Note This method works on only pinhole geometry.
428            # Extrapolate dqx(r) and dqy(phi) at q = 0, and take an average.
429            z_max = max(data2D.q_data)
430            z_min = min(data2D.q_data)
431            x_max = data2D.dqx_data[data2D.q_data[z_max]]
432            x_min = data2D.dqx_data[data2D.q_data[z_min]]
433            y_max = data2D.dqy_data[data2D.q_data[z_max]]
434            y_min = data2D.dqy_data[data2D.q_data[z_min]]
435            # Find qdx at q = 0
436            dq_overlap_x = (x_min * z_max - x_max * z_min) / (z_max - z_min)
437            # when extrapolation goes wrong
438            if dq_overlap_x > min(data2D.dqx_data):
439                dq_overlap_x = min(data2D.dqx_data)
440            dq_overlap_x *= dq_overlap_x
441            # Find qdx at q = 0
442            dq_overlap_y = (y_min * z_max - y_max * z_min) / (z_max - z_min)
443            # when extrapolation goes wrong
444            if dq_overlap_y > min(data2D.dqy_data):
445                dq_overlap_y = min(data2D.dqy_data)
446            # get dq at q=0.
447            dq_overlap_y *= dq_overlap_y
448
449            dq_overlap = numpy.sqrt((dq_overlap_x + dq_overlap_y) / 2.0)
450            # Final protection of dq
451            if dq_overlap < 0:
452                dq_overlap = y_min
453            dqx_data = data2D.dqx_data[numpy.isfinite(data2D.data)]
454            dqy_data = data2D.dqy_data[numpy.isfinite(data2D.data)] - dq_overlap
455            # def; dqx_data = dq_r dqy_data = dq_phi
456            # Convert dq 2D to 1D here
457            dqx = dqx_data * dqx_data
458            dqy = dqy_data * dqy_data
459            dq_data = numpy.add(dqx, dqy)
460            dq_data = numpy.sqrt(dq_data)
461
462        #q_data_max = numpy.max(q_data)
463        if len(data2D.q_data) == None:
464            msg = "Circular averaging: invalid q_data: %g" % data2D.q_data
465            raise RuntimeError, msg
466
467        # Build array of Q intervals
468        nbins = int(math.ceil((self.r_max - self.r_min) / self.bin_width))
469
470        x = numpy.zeros(nbins)
471        y = numpy.zeros(nbins)
472        err_y = numpy.zeros(nbins)
473        err_x = numpy.zeros(nbins)
474        y_counts = numpy.zeros(nbins)
475
476        for npt in range(len(data)):
477
478            if ismask and not mask_data[npt]:
479                continue
480
481            frac = 0
482
483            # q-value at the pixel (j,i)
484            q_value = q_data[npt]
485            data_n = data[npt]
486
487            ## No need to calculate the frac when all data are within range
488            if self.r_min >= self.r_max:
489                raise ValueError, "Limit Error: min > max"
490
491            if self.r_min <= q_value and q_value <= self.r_max:
492                frac = 1
493            if frac == 0:
494                continue
495            i_q = int(math.floor((q_value - self.r_min) / self.bin_width))
496
497            # Take care of the edge case at phi = 2pi.
498            if i_q == nbins:
499                i_q = nbins - 1
500            y[i_q] += frac * data_n
501            # Take dqs from data to get the q_average
502            x[i_q] += frac * q_value
503            if err_data == None or err_data[npt] == 0.0:
504                if data_n < 0:
505                    data_n = -data_n
506                err_y[i_q] += frac * frac * data_n
507            else:
508                err_y[i_q] += frac * frac * err_data[npt] * err_data[npt]
509            if dq_data != None:
510                # To be consistent with dq calculation in 1d reduction,
511                # we need just the averages (not quadratures) because
512                # it should not depend on the number of the q points
513                # in the qr bins.
514                err_x[i_q] += frac * dq_data[npt]
515            else:
516                err_x = None
517            y_counts[i_q] += frac
518
519        # Average the sums
520        for n in range(nbins):
521            if err_y[n] < 0:
522                err_y[n] = -err_y[n]
523            err_y[n] = math.sqrt(err_y[n])
524            #if err_x != None:
525            #    err_x[n] = math.sqrt(err_x[n])
526
527        err_y = err_y / y_counts
528        err_y[err_y == 0] = numpy.average(err_y)
529        y = y / y_counts
530        x = x / y_counts
531        idx = (numpy.isfinite(y)) & (numpy.isfinite(x))
532
533        if err_x != None:
534            d_x = err_x[idx] / y_counts[idx]
535        else:
536            d_x = None
537
538        if not idx.any():
539            msg = "Average Error: No points inside ROI to average..."
540            raise ValueError, msg
541
542        return Data1D(x=x[idx], y=y[idx], dy=err_y[idx], dx=d_x)
543
544
545class Ring(object):
546    """
547    Defines a ring on a 2D data set.
548    The ring is defined by r_min, r_max, and
549    the position of the center of the ring.
550
551    The data returned is the distribution of counts
552    around the ring as a function of phi.
553
554    Phi_min and phi_max should be defined between 0 and 2*pi
555    in anti-clockwise starting from the x- axis on the left-hand side
556    """
557    #Todo: remove center.
558    def __init__(self, r_min=0, r_max=0, center_x=0, center_y=0, nbins=36):
559        # Minimum radius
560        self.r_min = r_min
561        # Maximum radius
562        self.r_max = r_max
563        # Center of the ring in x
564        self.center_x = center_x
565        # Center of the ring in y
566        self.center_y = center_y
567        # Number of angular bins
568        self.nbins_phi = nbins
569
570
571    def __call__(self, data2D):
572        """
573        Apply the ring to the data set.
574        Returns the angular distribution for a given q range
575       
576        :param data2D: Data2D object
577       
578        :return: Data1D object
579        """
580        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
581            raise RuntimeError, "Ring averaging only take plottable_2D objects"
582
583        Pi = math.pi
584
585        # Get data
586        data = data2D.data[numpy.isfinite(data2D.data)]
587        q_data = data2D.q_data[numpy.isfinite(data2D.data)]
588        err_data = data2D.err_data[numpy.isfinite(data2D.data)]
589        qx_data = data2D.qx_data[numpy.isfinite(data2D.data)]
590        qy_data = data2D.qy_data[numpy.isfinite(data2D.data)]
591
592        # Set space for 1d outputs
593        phi_bins = numpy.zeros(self.nbins_phi)
594        phi_counts = numpy.zeros(self.nbins_phi)
595        phi_values = numpy.zeros(self.nbins_phi)
596        phi_err = numpy.zeros(self.nbins_phi)
597
598        # Shift to apply to calculated phi values in order to center first bin at zero
599        phi_shift = Pi / self.nbins_phi
600
601        for npt in range(len(data)):
602            frac = 0
603            # q-value at the point (npt)
604            q_value = q_data[npt]
605            data_n = data[npt]
606
607            # phi-value at the point (npt)
608            phi_value = math.atan2(qy_data[npt], qx_data[npt]) + Pi
609
610            if self.r_min <= q_value and q_value <= self.r_max:
611                frac = 1
612            if frac == 0:
613                continue
614            # binning           
615            i_phi = int(math.floor((self.nbins_phi) * (phi_value + phi_shift) / (2 * Pi)))
616
617            # Take care of the edge case at phi = 2pi.
618            if i_phi >= self.nbins_phi:
619                i_phi = 0
620            phi_bins[i_phi] += frac * data[npt]
621
622            if err_data == None or err_data[npt] == 0.0:
623                if data_n < 0:
624                    data_n = -data_n
625                phi_err[i_phi] += frac * frac * math.fabs(data_n)
626            else:
627                phi_err[i_phi] += frac * frac * err_data[npt] * err_data[npt]
628            phi_counts[i_phi] += frac
629
630        for i in range(self.nbins_phi):
631            phi_bins[i] = phi_bins[i] / phi_counts[i]
632            phi_err[i] = math.sqrt(phi_err[i]) / phi_counts[i]
633            phi_values[i] = 2.0 * math.pi / self.nbins_phi * (1.0 * i)
634
635        idx = (numpy.isfinite(phi_bins))
636
637        if not idx.any():
638            msg = "Average Error: No points inside ROI to average..."
639            raise ValueError, msg
640        #elif len(phi_bins[idx])!= self.nbins_phi:
641        #    print "resulted",self.nbins_phi- len(phi_bins[idx])
642        #,"empty bin(s) due to tight binning..."
643        return Data1D(x=phi_values[idx], y=phi_bins[idx], dy=phi_err[idx])
644
645
646def get_pixel_fraction(qmax, q_00, q_01, q_10, q_11):
647    """
648    Returns the fraction of the pixel defined by
649    the four corners (q_00, q_01, q_10, q_11) that
650    has q < qmax.::
651   
652                q_01                q_11
653        y=1         +--------------+
654                    |              |
655                    |              |
656                    |              |
657        y=0         +--------------+
658                q_00                q_10
659       
660                    x=0            x=1
661   
662    """
663    # y side for x = minx
664    x_0 = get_intercept(qmax, q_00, q_01)
665    # y side for x = maxx
666    x_1 = get_intercept(qmax, q_10, q_11)
667
668    # x side for y = miny
669    y_0 = get_intercept(qmax, q_00, q_10)
670    # x side for y = maxy
671    y_1 = get_intercept(qmax, q_01, q_11)
672
673    # surface fraction for a 1x1 pixel
674    frac_max = 0
675
676    if x_0 and x_1:
677        frac_max = (x_0 + x_1) / 2.0
678    elif y_0 and y_1:
679        frac_max = (y_0 + y_1) / 2.0
680    elif x_0 and y_0:
681        if q_00 < q_10:
682            frac_max = x_0 * y_0 / 2.0
683        else:
684            frac_max = 1.0 - x_0 * y_0 / 2.0
685    elif x_0 and y_1:
686        if q_00 < q_10:
687            frac_max = x_0 * y_1 / 2.0
688        else:
689            frac_max = 1.0 - x_0 * y_1 / 2.0
690    elif x_1 and y_0:
691        if q_00 > q_10:
692            frac_max = x_1 * y_0 / 2.0
693        else:
694            frac_max = 1.0 - x_1 * y_0 / 2.0
695    elif x_1 and y_1:
696        if q_00 < q_10:
697            frac_max = 1.0 - (1.0 - x_1) * (1.0 - y_1) / 2.0
698        else:
699            frac_max = (1.0 - x_1) * (1.0 - y_1) / 2.0
700
701    # If we make it here, there is no intercept between
702    # this pixel and the constant-q ring. We only need
703    # to know if we have to include it or exclude it.
704    elif (q_00 + q_01 + q_10 + q_11) / 4.0 < qmax:
705        frac_max = 1.0
706
707    return frac_max
708
709
710def get_intercept(q, q_0, q_1):
711    """
712    Returns the fraction of the side at which the
713    q-value intercept the pixel, None otherwise.
714    The values returned is the fraction ON THE SIDE
715    OF THE LOWEST Q. ::
716   
717   
718            A           B
719        +-----------+--------+    <--- pixel size
720        0                    1
721        Q_0 -------- Q ----- Q_1   <--- equivalent Q range
722        if Q_1 > Q_0, A is returned
723        if Q_1 < Q_0, B is returned
724        if Q is outside the range of [Q_0, Q_1], None is returned
725         
726    """
727    if q_1 > q_0:
728        if (q > q_0 and q <= q_1):
729            return (q - q_0) / (q_1 - q_0)
730    else:
731        if (q > q_1 and q <= q_0):
732            return (q - q_1) / (q_0 - q_1)
733    return None
734
735
736class _Sector:
737    """
738    Defines a sector region on a 2D data set.
739    The sector is defined by r_min, r_max, phi_min, phi_max,
740    and the position of the center of the ring
741    where phi_min and phi_max are defined by the right
742    and left lines wrt central line
743    and phi_max could be less than phi_min.
744   
745    Phi is defined between 0 and 2*pi in anti-clockwise
746    starting from the x- axis on the left-hand side
747    """
748    def __init__(self, r_min, r_max, phi_min=0, phi_max=2 * math.pi, nbins=20):
749        self.r_min = r_min
750        self.r_max = r_max
751        self.phi_min = phi_min
752        self.phi_max = phi_max
753        self.nbins = nbins
754
755    def _agv(self, data2D, run='phi'):
756        """
757        Perform sector averaging.
758       
759        :param data2D: Data2D object
760        :param run:  define the varying parameter ('phi' , 'q' , or 'q2')
761       
762        :return: Data1D object
763        """
764        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
765            raise RuntimeError, "Ring averaging only take plottable_2D objects"
766        Pi = math.pi
767
768        # Get the all data & info
769        data = data2D.data[numpy.isfinite(data2D.data)]
770        q_data = data2D.q_data[numpy.isfinite(data2D.data)]
771        err_data = data2D.err_data[numpy.isfinite(data2D.data)]
772        qx_data = data2D.qx_data[numpy.isfinite(data2D.data)]
773        qy_data = data2D.qy_data[numpy.isfinite(data2D.data)]
774        dq_data = None
775
776        # Get the dq for resolution averaging
777        if data2D.dqx_data != None and data2D.dqy_data != None:
778            # The pinholes and det. pix contribution present
779            # in both direction of the 2D which must be subtracted when
780            # converting to 1D: dq_overlap should calculated ideally at
781            # q = 0.
782            # Extrapolate dqy(perp) at q = 0
783            z_max = max(data2D.q_data)
784            z_min = min(data2D.q_data)
785            x_max = data2D.dqx_data[data2D.q_data[z_max]]
786            x_min = data2D.dqx_data[data2D.q_data[z_min]]
787            y_max = data2D.dqy_data[data2D.q_data[z_max]]
788            y_min = data2D.dqy_data[data2D.q_data[z_min]]
789            # Find qdx at q = 0
790            dq_overlap_x = (x_min * z_max - x_max * z_min) / (z_max - z_min)
791            # when extrapolation goes wrong
792            if dq_overlap_x > min(data2D.dqx_data):
793                dq_overlap_x = min(data2D.dqx_data)
794            dq_overlap_x *= dq_overlap_x
795            # Find qdx at q = 0
796            dq_overlap_y = (y_min * z_max - y_max * z_min) / (z_max - z_min)
797            # when extrapolation goes wrong
798            if dq_overlap_y > min(data2D.dqy_data):
799                dq_overlap_y = min(data2D.dqy_data)
800            # get dq at q=0.
801            dq_overlap_y *= dq_overlap_y
802
803            dq_overlap = numpy.sqrt((dq_overlap_x + dq_overlap_y) / 2.0)
804            if dq_overlap < 0:
805                dq_overlap = y_min
806            dqx_data = data2D.dqx_data[numpy.isfinite(data2D.data)]
807            dqy_data = data2D.dqy_data[numpy.isfinite(data2D.data)] - dq_overlap
808            # def; dqx_data = dq_r dqy_data = dq_phi
809            # Convert dq 2D to 1D here
810            dqx = dqx_data * dqx_data
811            dqy = dqy_data * dqy_data
812            dq_data = numpy.add(dqx, dqy)
813            dq_data = numpy.sqrt(dq_data)
814
815        #set space for 1d outputs
816        x = numpy.zeros(self.nbins)
817        y = numpy.zeros(self.nbins)
818        y_err = numpy.zeros(self.nbins)
819        x_err = numpy.zeros(self.nbins)
820        y_counts = numpy.zeros(self.nbins)
821
822        # Get the min and max into the region: 0 <= phi < 2Pi
823        phi_min = flip_phi(self.phi_min)
824        phi_max = flip_phi(self.phi_max)
825
826        for n in range(len(data)):
827            frac = 0
828
829            # q-value at the pixel (j,i)
830            q_value = q_data[n]
831            data_n = data[n]
832
833            # Is pixel within range?
834            is_in = False
835
836            # phi-value of the pixel (j,i)
837            phi_value = math.atan2(qy_data[n], qx_data[n]) + Pi
838
839            ## No need to calculate the frac when all data are within range
840            if self.r_min <= q_value and q_value <= self.r_max:
841                frac = 1
842            if frac == 0:
843                continue
844            #In case of two ROIs (symmetric major and minor regions)(for 'q2')
845            if run.lower() == 'q2':
846                ## For minor sector wing
847                # Calculate the minor wing phis
848                phi_min_minor = flip_phi(phi_min - Pi)
849                phi_max_minor = flip_phi(phi_max - Pi)
850                # Check if phis of the minor ring is within 0 to 2pi
851                if phi_min_minor > phi_max_minor:
852                    is_in = (phi_value > phi_min_minor or \
853                              phi_value < phi_max_minor)
854                else:
855                    is_in = (phi_value > phi_min_minor and \
856                              phi_value < phi_max_minor)
857
858            #For all cases(i.e.,for 'q', 'q2', and 'phi')
859            #Find pixels within ROI
860            if phi_min > phi_max:
861                is_in = is_in or (phi_value > phi_min or \
862                                   phi_value < phi_max)
863            else:
864                is_in = is_in or (phi_value >= phi_min  and \
865                                    phi_value < phi_max)
866
867            if not is_in:
868                frac = 0
869            if frac == 0:
870                continue
871            # Check which type of averaging we need
872            if run.lower() == 'phi':
873                temp_x = (self.nbins) * (phi_value - self.phi_min)
874                temp_y = (self.phi_max - self.phi_min)
875                i_bin = int(math.floor(temp_x / temp_y))
876            else:
877                temp_x = (self.nbins) * (q_value - self.r_min)
878                temp_y = (self.r_max - self.r_min)
879                i_bin = int(math.floor(temp_x / temp_y))
880
881            # Take care of the edge case at phi = 2pi.
882            if i_bin == self.nbins:
883                i_bin = self.nbins - 1
884
885            ## Get the total y
886            y[i_bin] += frac * data_n
887            x[i_bin] += frac * q_value
888            if err_data[n] == None or err_data[n] == 0.0:
889                if data_n < 0:
890                    data_n = -data_n
891                y_err[i_bin] += frac * frac * data_n
892            else:
893                y_err[i_bin] += frac * frac * err_data[n] * err_data[n]
894
895            if dq_data != None:
896                # To be consistent with dq calculation in 1d reduction,
897                # we need just the averages (not quadratures) because
898                # it should not depend on the number of the q points
899                # in the qr bins.
900                x_err[i_bin] += frac * dq_data[n]
901            else:
902                x_err = None
903            y_counts[i_bin] += frac
904
905        # Organize the results
906        for i in range(self.nbins):
907            y[i] = y[i] / y_counts[i]
908            y_err[i] = math.sqrt(y_err[i]) / y_counts[i]
909
910            # The type of averaging: phi,q2, or q
911            # Calculate x[i]should be at the center of the bin
912            if run.lower() == 'phi':
913                x[i] = (self.phi_max - self.phi_min) / self.nbins * \
914                    (1.0 * i + 0.5) + self.phi_min
915            else:
916                # We take the center of ring area, not radius.
917                # This is more accurate than taking the radial center of ring.
918                #delta_r = (self.r_max - self.r_min) / self.nbins
919                #r_inner = self.r_min + delta_r * i
920                #r_outer = r_inner + delta_r
921                #x[i] = math.sqrt((r_inner * r_inner + r_outer * r_outer) / 2)
922                x[i] = x[i] / y_counts[i]
923        y_err[y_err == 0] = numpy.average(y_err)
924        idx = (numpy.isfinite(y) & numpy.isfinite(y_err))
925        if x_err != None:
926            d_x = x_err[idx] / y_counts[idx]
927        else:
928            d_x = None
929        if not idx.any():
930            msg = "Average Error: No points inside sector of ROI to average..."
931            raise ValueError, msg
932        #elif len(y[idx])!= self.nbins:
933        #    print "resulted",self.nbins- len(y[idx]),
934        #"empty bin(s) due to tight binning..."
935        return Data1D(x=x[idx], y=y[idx], dy=y_err[idx], dx=d_x)
936
937
938class SectorPhi(_Sector):
939    """
940    Sector average as a function of phi.
941    I(phi) is return and the data is averaged over Q.
942   
943    A sector is defined by r_min, r_max, phi_min, phi_max.
944    The number of bin in phi also has to be defined.
945    """
946    def __call__(self, data2D):
947        """
948        Perform sector average and return I(phi).
949       
950        :param data2D: Data2D object
951        :return: Data1D object
952        """
953        return self._agv(data2D, 'phi')
954
955
956class SectorQ(_Sector):
957    """
958    Sector average as a function of Q for both symatric wings.
959    I(Q) is return and the data is averaged over phi.
960   
961    A sector is defined by r_min, r_max, phi_min, phi_max.
962    r_min, r_max, phi_min, phi_max >0.
963    The number of bin in Q also has to be defined.
964    """
965    def __call__(self, data2D):
966        """
967        Perform sector average and return I(Q).
968       
969        :param data2D: Data2D object
970       
971        :return: Data1D object
972        """
973        return self._agv(data2D, 'q2')
974
975
976class Ringcut(object):
977    """
978    Defines a ring on a 2D data set.
979    The ring is defined by r_min, r_max, and
980    the position of the center of the ring.
981   
982    The data returned is the region inside the ring
983   
984    Phi_min and phi_max should be defined between 0 and 2*pi
985    in anti-clockwise starting from the x- axis on the left-hand side
986    """
987    def __init__(self, r_min=0, r_max=0, center_x=0, center_y=0):
988        # Minimum radius
989        self.r_min = r_min
990        # Maximum radius
991        self.r_max = r_max
992        # Center of the ring in x
993        self.center_x = center_x
994        # Center of the ring in y
995        self.center_y = center_y
996
997    def __call__(self, data2D):
998        """
999        Apply the ring to the data set.
1000        Returns the angular distribution for a given q range
1001       
1002        :param data2D: Data2D object
1003       
1004        :return: index array in the range
1005        """
1006        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
1007            raise RuntimeError, "Ring cut only take plottable_2D objects"
1008
1009        # Get data
1010        qx_data = data2D.qx_data
1011        qy_data = data2D.qy_data
1012        q_data = numpy.sqrt(qx_data * qx_data + qy_data * qy_data)
1013
1014        # check whether or not the data point is inside ROI
1015        out = (self.r_min <= q_data) & (self.r_max >= q_data)
1016
1017        return (out)
1018
1019
1020class Boxcut(object):
1021    """
1022    Find a rectangular 2D region of interest.
1023    """
1024    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
1025        # Minimum Qx value [A-1]
1026        self.x_min = x_min
1027        # Maximum Qx value [A-1]
1028        self.x_max = x_max
1029        # Minimum Qy value [A-1]
1030        self.y_min = y_min
1031        # Maximum Qy value [A-1]
1032        self.y_max = y_max
1033
1034    def __call__(self, data2D):
1035        """
1036       Find a rectangular 2D region of interest.
1037         
1038       :param data2D: Data2D object
1039       :return: mask, 1d array (len = len(data))
1040           with Trues where the data points are inside ROI, otherwise False
1041        """
1042        mask = self._find(data2D)
1043
1044        return mask
1045
1046    def _find(self, data2D):
1047        """
1048        Find a rectangular 2D region of interest.
1049       
1050        :param data2D: Data2D object
1051       
1052        :return: out, 1d array (length = len(data))
1053           with Trues where the data points are inside ROI, otherwise Falses
1054        """
1055        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
1056            raise RuntimeError, "Boxcut take only plottable_2D objects"
1057        # Get qx_ and qy_data
1058        qx_data = data2D.qx_data
1059        qy_data = data2D.qy_data
1060
1061        # check whether or not the data point is inside ROI
1062        outx = (self.x_min <= qx_data) & (self.x_max > qx_data)
1063        outy = (self.y_min <= qy_data) & (self.y_max > qy_data)
1064
1065        return (outx & outy)
1066
1067
1068class Sectorcut(object):
1069    """
1070    Defines a sector (major + minor) region on a 2D data set.
1071    The sector is defined by phi_min, phi_max,
1072    where phi_min and phi_max are defined by the right
1073    and left lines wrt central line.
1074   
1075    Phi_min and phi_max are given in units of radian
1076    and (phi_max-phi_min) should not be larger than pi
1077    """
1078    def __init__(self, phi_min=0, phi_max=math.pi):
1079        self.phi_min = phi_min
1080        self.phi_max = phi_max
1081
1082    def __call__(self, data2D):
1083        """
1084        Find a rectangular 2D region of interest.
1085       
1086        :param data2D: Data2D object
1087       
1088        :return: mask, 1d array (len = len(data))
1089       
1090        with Trues where the data points are inside ROI, otherwise False
1091        """
1092        mask = self._find(data2D)
1093
1094        return mask
1095
1096    def _find(self, data2D):
1097        """
1098        Find a rectangular 2D region of interest.
1099       
1100        :param data2D: Data2D object
1101       
1102        :return: out, 1d array (length = len(data))
1103       
1104        with Trues where the data points are inside ROI, otherwise Falses
1105        """
1106        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
1107            raise RuntimeError, "Sectorcut take only plottable_2D objects"
1108        Pi = math.pi
1109        # Get data
1110        qx_data = data2D.qx_data
1111        qy_data = data2D.qy_data
1112
1113        # get phi from data
1114        phi_data = numpy.arctan2(qy_data, qx_data)
1115
1116        # Get the min and max into the region: -pi <= phi < Pi
1117        phi_min_major = flip_phi(self.phi_min + Pi) - Pi
1118        phi_max_major = flip_phi(self.phi_max + Pi) - Pi
1119        # check for major sector
1120        if phi_min_major > phi_max_major:
1121            out_major = (phi_min_major <= phi_data) + (phi_max_major > phi_data)
1122        else:
1123            out_major = (phi_min_major <= phi_data) & (phi_max_major > phi_data)
1124
1125        # minor sector
1126        # Get the min and max into the region: -pi <= phi < Pi
1127        phi_min_minor = flip_phi(self.phi_min) - Pi
1128        phi_max_minor = flip_phi(self.phi_max) - Pi
1129
1130        # check for minor sector
1131        if phi_min_minor > phi_max_minor:
1132            out_minor = (phi_min_minor <= phi_data) + \
1133                            (phi_max_minor >= phi_data)
1134        else:
1135            out_minor = (phi_min_minor <= phi_data) & \
1136                            (phi_max_minor >= phi_data)
1137        out = out_major + out_minor
1138
1139        return out
Note: See TracBrowser for help on using the repository browser.