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

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 dd11014 was dd11014, checked in by ajj, 7 years ago

Fixing PR #55 broken merge

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