source: sasview/src/sas/sascalc/dataloader/manipulations.py @ ccc7192

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.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since ccc7192 was ccc7192, checked in by Ricardo Ferraz Leal <ricleal@…>, 7 years ago

Cleaning and Refactoring

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