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

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 e0ebd56 was e0ebd56, checked in by krzywon, 7 years ago

Revert changes to manipulations.py to minimize conflicts with log_binning branch.

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