Ignore:
Timestamp:
May 18, 2017 3:21:33 PM (8 years ago)
Author:
krzywon
Branches:
master, ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc, costrafo411, magnetic_scatt, release-4.2.2, ticket-1009, ticket-1094-headless, ticket-1242-2d-resolution, ticket-1243, ticket-1249, ticket885, unittest-saveload
Children:
8de66b6
Parents:
e0ebd56 (diff), 7132e49 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge branch 'master' into batch_slicer

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/sas/sascalc/dataloader/manipulations.py

    re0ebd56 r46cd1c3  
     1from __future__ import division 
    12""" 
    23Data manipulations for 2D data sets. 
    34Using the meta data information, various types of averaging 
    45are performed in Q-space 
     6 
     7To test this module use: 
     8``` 
     9cd test 
     10PYTHONPATH=../src/ python2  -m sasdataloader.test.utest_averaging DataInfoTests.test_sectorphi_quarter 
     11``` 
    512""" 
    613##################################################################### 
    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 
     14# This software was developed by the University of Tennessee as part of the 
     15# Distributed Data Analysis of Neutron Scattering Experiments (DANSE) 
     16# project funded by the US National Science Foundation. 
     17# See the license text in license.txt 
     18# copyright 2008, University of Tennessee 
    1219###################################################################### 
    1320 
    14 #TODO: copy the meta data from the 2D object to the resulting 1D object 
     21 
     22# TODO: copy the meta data from the 2D object to the resulting 1D object 
    1523import math 
    1624import numpy as np 
     25import sys 
    1726 
    1827#from data_info import plottable_2D 
     
    7079    return phi_out 
    7180 
    72  
    73 def 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  
    112 class _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  
    235 class 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  
    249 class 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  
    262 class 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  
    345 class 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  
    37081def get_pixel_fraction_square(x, xmin, xmax): 
    37182    """ 
     
    390101        return 1.0 
    391102 
    392  
    393 class 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  
    548 class 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  
     103def get_intercept(q, q_0, q_1): 
     104    """ 
     105    Returns the fraction of the side at which the 
     106    q-value intercept the pixel, None otherwise. 
     107    The values returned is the fraction ON THE SIDE 
     108    OF THE LOWEST Q. :: 
     109 
     110            A           B 
     111        +-----------+--------+    <--- pixel size 
     112        0                    1 
     113        Q_0 -------- Q ----- Q_1   <--- equivalent Q range 
     114        if Q_1 > Q_0, A is returned 
     115        if Q_1 < Q_0, B is returned 
     116        if Q is outside the range of [Q_0, Q_1], None is returned 
     117 
     118    """ 
     119    if q_1 > q_0: 
     120        if q > q_0 and q <= q_1: 
     121            return (q - q_0) / (q_1 - q_0) 
     122    else: 
     123        if q > q_1 and q <= q_0: 
     124            return (q - q_1) / (q_0 - q_1) 
     125    return None 
    650126 
    651127def get_pixel_fraction(qmax, q_00, q_01, q_10, q_11): 
     
    712188    return frac_max 
    713189 
    714  
    715 def 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) 
     190def get_dq_data(data2D): 
     191    ''' 
     192    Get the dq for resolution averaging 
     193    The pinholes and det. pix contribution present 
     194    in both direction of the 2D which must be subtracted when 
     195    converting to 1D: dq_overlap should calculated ideally at 
     196    q = 0. Note This method works on only pinhole geometry. 
     197    Extrapolate dqx(r) and dqy(phi) at q = 0, and take an average. 
     198    ''' 
     199    z_max = max(data2D.q_data) 
     200    z_min = min(data2D.q_data) 
     201    x_max = data2D.dqx_data[data2D.q_data[z_max]] 
     202    x_min = data2D.dqx_data[data2D.q_data[z_min]] 
     203    y_max = data2D.dqy_data[data2D.q_data[z_max]] 
     204    y_min = data2D.dqy_data[data2D.q_data[z_min]] 
     205    # Find qdx at q = 0 
     206    dq_overlap_x = (x_min * z_max - x_max * z_min) / (z_max - z_min) 
     207    # when extrapolation goes wrong 
     208    if dq_overlap_x > min(data2D.dqx_data): 
     209        dq_overlap_x = min(data2D.dqx_data) 
     210    dq_overlap_x *= dq_overlap_x 
     211    # Find qdx at q = 0 
     212    dq_overlap_y = (y_min * z_max - y_max * z_min) / (z_max - z_min) 
     213    # when extrapolation goes wrong 
     214    if dq_overlap_y > min(data2D.dqy_data): 
     215        dq_overlap_y = min(data2D.dqy_data) 
     216    # get dq at q=0. 
     217    dq_overlap_y *= dq_overlap_y 
     218 
     219    dq_overlap = np.sqrt((dq_overlap_x + dq_overlap_y) / 2.0) 
     220    # Final protection of dq 
     221    if dq_overlap < 0: 
     222        dq_overlap = y_min 
     223    dqx_data = data2D.dqx_data[np.isfinite(data2D.data)] 
     224    dqy_data = data2D.dqy_data[np.isfinite( 
     225        data2D.data)] - dq_overlap 
     226    # def; dqx_data = dq_r dqy_data = dq_phi 
     227    # Convert dq 2D to 1D here 
     228    dq_data = np.sqrt(dqx_data**2 + dqx_data**2) 
     229    return dq_data 
     230 
     231################################################################################ 
     232 
     233def reader2D_converter(data2d=None): 
     234    """ 
     235    convert old 2d format opened by IhorReader or danse_reader 
     236    to new Data2D format 
     237    This is mainly used by the Readers 
     238 
     239    :param data2d: 2d array of Data2D object 
     240    :return: 1d arrays of Data2D object 
     241 
     242    """ 
     243    if data2d.data is None or data2d.x_bins is None or data2d.y_bins is None: 
     244        raise ValueError("Can't convert this data: data=None...") 
     245    new_x = np.tile(data2d.x_bins, (len(data2d.y_bins), 1)) 
     246    new_y = np.tile(data2d.y_bins, (len(data2d.x_bins), 1)) 
     247    new_y = new_y.swapaxes(0, 1) 
     248 
     249    new_data = data2d.data.flatten() 
     250    qx_data = new_x.flatten() 
     251    qy_data = new_y.flatten() 
     252    q_data = np.sqrt(qx_data * qx_data + qy_data * qy_data) 
     253    if data2d.err_data is None or np.any(data2d.err_data <= 0): 
     254        new_err_data = np.sqrt(np.abs(new_data)) 
    734255    else: 
    735         if q > q_1 and q <= q_0: 
    736             return (q - q_1) / (q_0 - q_1) 
    737     return None 
     256        new_err_data = data2d.err_data.flatten() 
     257    mask = np.ones(len(new_data), dtype=bool) 
     258 
     259    # TODO: make sense of the following two lines... 
     260    #from sas.sascalc.dataloader.data_info import Data2D 
     261    #output = Data2D() 
     262    output = data2d 
     263    output.data = new_data 
     264    output.err_data = new_err_data 
     265    output.qx_data = qx_data 
     266    output.qy_data = qy_data 
     267    output.q_data = q_data 
     268    output.mask = mask 
     269 
     270    return output 
     271 
     272################################################################################ 
     273 
     274class Binning(object): 
     275    ''' 
     276    This class just creates a binning object 
     277    either linear or log 
     278    ''' 
     279 
     280    def __init__(self, min_value, max_value, n_bins, base=None): 
     281        ''' 
     282        if base is None: Linear binning 
     283        ''' 
     284        self.min = min_value if min_value > 0 else 0.0001 
     285        self.max = max_value 
     286        self.n_bins = n_bins 
     287        self.base = base 
     288 
     289    def get_bin_index(self, value): 
     290        ''' 
     291        The general formula logarithm binning is: 
     292        bin = floor(N * (log(x) - log(min)) / (log(max) - log(min))) 
     293        ''' 
     294        if self.base: 
     295            temp_x = self.n_bins * (math.log(value, self.base) - math.log(self.min, self.base)) 
     296            temp_y = math.log(self.max, self.base) - math.log(self.min, self.base) 
     297        else: 
     298            temp_x = self.n_bins * (value - self.min) 
     299            temp_y = self.max - self.min 
     300        # Bin index calulation 
     301        return int(math.floor(temp_x / temp_y)) 
     302 
     303 
     304################################################################################ 
     305 
     306class _Slab(object): 
     307    """ 
     308    Compute average I(Q) for a region of interest 
     309    """ 
     310 
     311    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, 
     312                 y_max=0.0, bin_width=0.001): 
     313        # Minimum Qx value [A-1] 
     314        self.x_min = x_min 
     315        # Maximum Qx value [A-1] 
     316        self.x_max = x_max 
     317        # Minimum Qy value [A-1] 
     318        self.y_min = y_min 
     319        # Maximum Qy value [A-1] 
     320        self.y_max = y_max 
     321        # Bin width (step size) [A-1] 
     322        self.bin_width = bin_width 
     323        # If True, I(|Q|) will be return, otherwise, 
     324        # negative q-values are allowed 
     325        self.fold = False 
     326 
     327    def __call__(self, data2D): 
     328        return NotImplemented 
     329 
     330    def _avg(self, data2D, maj): 
     331        """ 
     332        Compute average I(Q_maj) for a region of interest. 
     333        The major axis is defined as the axis of Q_maj. 
     334        The minor axis is the axis that we average over. 
     335 
     336        :param data2D: Data2D object 
     337        :param maj_min: min value on the major axis 
     338        :return: Data1D object 
     339        """ 
     340        if len(data2D.detector) > 1: 
     341            msg = "_Slab._avg: invalid number of " 
     342            msg += " detectors: %g" % len(data2D.detector) 
     343            raise RuntimeError(msg) 
     344 
     345        # Get data 
     346        data = data2D.data[np.isfinite(data2D.data)] 
     347        err_data = data2D.err_data[np.isfinite(data2D.data)] 
     348        qx_data = data2D.qx_data[np.isfinite(data2D.data)] 
     349        qy_data = data2D.qy_data[np.isfinite(data2D.data)] 
     350 
     351        # Build array of Q intervals 
     352        if maj == 'x': 
     353            if self.fold: 
     354                x_min = 0 
     355            else: 
     356                x_min = self.x_min 
     357            nbins = int(math.ceil((self.x_max - x_min) / self.bin_width)) 
     358        elif maj == 'y': 
     359            if self.fold: 
     360                y_min = 0 
     361            else: 
     362                y_min = self.y_min 
     363            nbins = int(math.ceil((self.y_max - y_min) / self.bin_width)) 
     364        else: 
     365            raise RuntimeError("_Slab._avg: unrecognized axis %s" % str(maj)) 
     366 
     367        x = np.zeros(nbins) 
     368        y = np.zeros(nbins) 
     369        err_y = np.zeros(nbins) 
     370        y_counts = np.zeros(nbins) 
     371 
     372        # Average pixelsize in q space 
     373        for npts in range(len(data)): 
     374            # default frac 
     375            frac_x = 0 
     376            frac_y = 0 
     377            # get ROI 
     378            if self.x_min <= qx_data[npts] and self.x_max > qx_data[npts]: 
     379                frac_x = 1 
     380            if self.y_min <= qy_data[npts] and self.y_max > qy_data[npts]: 
     381                frac_y = 1 
     382            frac = frac_x * frac_y 
     383 
     384            if frac == 0: 
     385                continue 
     386            # binning: find axis of q 
     387            if maj == 'x': 
     388                q_value = qx_data[npts] 
     389                min_value = x_min 
     390            if maj == 'y': 
     391                q_value = qy_data[npts] 
     392                min_value = y_min 
     393            if self.fold and q_value < 0: 
     394                q_value = -q_value 
     395            # bin 
     396            i_q = int(math.ceil((q_value - min_value) / self.bin_width)) - 1 
     397 
     398            # skip outside of max bins 
     399            if i_q < 0 or i_q >= nbins: 
     400                continue 
     401 
     402            # TODO: find better definition of x[i_q] based on q_data 
     403            # min_value + (i_q + 1) * self.bin_width / 2.0 
     404            x[i_q] += frac * q_value 
     405            y[i_q] += frac * data[npts] 
     406 
     407            if err_data is None or err_data[npts] == 0.0: 
     408                if data[npts] < 0: 
     409                    data[npts] = -data[npts] 
     410                err_y[i_q] += frac * frac * data[npts] 
     411            else: 
     412                err_y[i_q] += frac * frac * err_data[npts] * err_data[npts] 
     413            y_counts[i_q] += frac 
     414 
     415        # Average the sums 
     416        for n in range(nbins): 
     417            err_y[n] = math.sqrt(err_y[n]) 
     418 
     419        err_y = err_y / y_counts 
     420        y = y / y_counts 
     421        x = x / y_counts 
     422        idx = (np.isfinite(y) & np.isfinite(x)) 
     423 
     424        if not idx.any(): 
     425            msg = "Average Error: No points inside ROI to average..." 
     426            raise ValueError(msg) 
     427        return Data1D(x=x[idx], y=y[idx], dy=err_y[idx]) 
     428 
     429 
     430class SlabY(_Slab): 
     431    """ 
     432    Compute average I(Qy) for a region of interest 
     433    """ 
     434 
     435    def __call__(self, data2D): 
     436        """ 
     437        Compute average I(Qy) for a region of interest 
     438 
     439        :param data2D: Data2D object 
     440        :return: Data1D object 
     441        """ 
     442        return self._avg(data2D, 'y') 
     443 
     444 
     445class SlabX(_Slab): 
     446    """ 
     447    Compute average I(Qx) for a region of interest 
     448    """ 
     449 
     450    def __call__(self, data2D): 
     451        """ 
     452        Compute average I(Qx) for a region of interest 
     453        :param data2D: Data2D object 
     454        :return: Data1D object 
     455        """ 
     456        return self._avg(data2D, 'x') 
     457 
     458################################################################################ 
     459 
     460class Boxsum(object): 
     461    """ 
     462    Perform the sum of counts in a 2D region of interest. 
     463    """ 
     464 
     465    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0): 
     466        # Minimum Qx value [A-1] 
     467        self.x_min = x_min 
     468        # Maximum Qx value [A-1] 
     469        self.x_max = x_max 
     470        # Minimum Qy value [A-1] 
     471        self.y_min = y_min 
     472        # Maximum Qy value [A-1] 
     473        self.y_max = y_max 
     474 
     475    def __call__(self, data2D): 
     476        """ 
     477        Perform the sum in the region of interest 
     478 
     479        :param data2D: Data2D object 
     480        :return: number of counts, error on number of counts, 
     481            number of points summed 
     482        """ 
     483        y, err_y, y_counts = self._sum(data2D) 
     484 
     485        # Average the sums 
     486        counts = 0 if y_counts == 0 else y 
     487        error = 0 if y_counts == 0 else math.sqrt(err_y) 
     488 
     489        # Added y_counts to return, SMK & PDB, 04/03/2013 
     490        return counts, error, y_counts 
     491 
     492    def _sum(self, data2D): 
     493        """ 
     494        Perform the sum in the region of interest 
     495 
     496        :param data2D: Data2D object 
     497        :return: number of counts, 
     498            error on number of counts, number of entries summed 
     499        """ 
     500        if len(data2D.detector) > 1: 
     501            msg = "Circular averaging: invalid number " 
     502            msg += "of detectors: %g" % len(data2D.detector) 
     503            raise RuntimeError(msg) 
     504        # Get data 
     505        data = data2D.data[np.isfinite(data2D.data)] 
     506        err_data = data2D.err_data[np.isfinite(data2D.data)] 
     507        qx_data = data2D.qx_data[np.isfinite(data2D.data)] 
     508        qy_data = data2D.qy_data[np.isfinite(data2D.data)] 
     509 
     510        y = 0.0 
     511        err_y = 0.0 
     512        y_counts = 0.0 
     513 
     514        # Average pixelsize in q space 
     515        for npts in range(len(data)): 
     516            # default frac 
     517            frac_x = 0 
     518            frac_y = 0 
     519 
     520            # get min and max at each points 
     521            qx = qx_data[npts] 
     522            qy = qy_data[npts] 
     523 
     524            # get the ROI 
     525            if self.x_min <= qx and self.x_max > qx: 
     526                frac_x = 1 
     527            if self.y_min <= qy and self.y_max > qy: 
     528                frac_y = 1 
     529            # Find the fraction along each directions 
     530            frac = frac_x * frac_y 
     531            if frac == 0: 
     532                continue 
     533            y += frac * data[npts] 
     534            if err_data is None or err_data[npts] == 0.0: 
     535                if data[npts] < 0: 
     536                    data[npts] = -data[npts] 
     537                err_y += frac * frac * data[npts] 
     538            else: 
     539                err_y += frac * frac * err_data[npts] * err_data[npts] 
     540            y_counts += frac 
     541        return y, err_y, y_counts 
     542 
     543 
     544class Boxavg(Boxsum): 
     545    """ 
     546    Perform the average of counts in a 2D region of interest. 
     547    """ 
     548 
     549    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0): 
     550        super(Boxavg, self).__init__(x_min=x_min, x_max=x_max, 
     551                                     y_min=y_min, y_max=y_max) 
     552 
     553    def __call__(self, data2D): 
     554        """ 
     555        Perform the sum in the region of interest 
     556 
     557        :param data2D: Data2D object 
     558        :return: average counts, error on average counts 
     559 
     560        """ 
     561        y, err_y, y_counts = self._sum(data2D) 
     562 
     563        # Average the sums 
     564        counts = 0 if y_counts == 0 else y / y_counts 
     565        error = 0 if y_counts == 0 else math.sqrt(err_y) / y_counts 
     566 
     567        return counts, error 
     568 
     569################################################################################ 
     570 
     571class CircularAverage(object): 
     572    """ 
     573    Perform circular averaging on 2D data 
     574 
     575    The data returned is the distribution of counts 
     576    as a function of Q 
     577    """ 
     578 
     579    def __init__(self, r_min=0.0, r_max=0.0, bin_width=0.0005): 
     580        # Minimum radius included in the average [A-1] 
     581        self.r_min = r_min 
     582        # Maximum radius included in the average [A-1] 
     583        self.r_max = r_max 
     584        # Bin width (step size) [A-1] 
     585        self.bin_width = bin_width 
     586 
     587    def __call__(self, data2D, ismask=False): 
     588        """ 
     589        Perform circular averaging on the data 
     590 
     591        :param data2D: Data2D object 
     592        :return: Data1D object 
     593        """ 
     594        # Get data W/ finite values 
     595        data = data2D.data[np.isfinite(data2D.data)] 
     596        q_data = data2D.q_data[np.isfinite(data2D.data)] 
     597        err_data = data2D.err_data[np.isfinite(data2D.data)] 
     598        mask_data = data2D.mask[np.isfinite(data2D.data)] 
     599 
     600        dq_data = None 
     601        if data2D.dqx_data is not None and data2D.dqy_data is not None: 
     602            dq_data = get_dq_data(data2D) 
     603 
     604        if len(q_data) == 0: 
     605            msg = "Circular averaging: invalid q_data: %g" % data2D.q_data 
     606            raise RuntimeError(msg) 
     607 
     608        # Build array of Q intervals 
     609        nbins = int(math.ceil((self.r_max - self.r_min) / self.bin_width)) 
     610 
     611        x = np.zeros(nbins) 
     612        y = np.zeros(nbins) 
     613        err_y = np.zeros(nbins) 
     614        err_x = np.zeros(nbins) 
     615        y_counts = np.zeros(nbins) 
     616 
     617        for npt in range(len(data)): 
     618 
     619            if ismask and not mask_data[npt]: 
     620                continue 
     621 
     622            frac = 0 
     623 
     624            # q-value at the pixel (j,i) 
     625            q_value = q_data[npt] 
     626            data_n = data[npt] 
     627 
     628            # No need to calculate the frac when all data are within range 
     629            if self.r_min >= self.r_max: 
     630                raise ValueError("Limit Error: min > max") 
     631 
     632            if self.r_min <= q_value and q_value <= self.r_max: 
     633                frac = 1 
     634            if frac == 0: 
     635                continue 
     636            i_q = int(math.floor((q_value - self.r_min) / self.bin_width)) 
     637 
     638            # Take care of the edge case at phi = 2pi. 
     639            if i_q == nbins: 
     640                i_q = nbins - 1 
     641            y[i_q] += frac * data_n 
     642            # Take dqs from data to get the q_average 
     643            x[i_q] += frac * q_value 
     644            if err_data is None or err_data[npt] == 0.0: 
     645                if data_n < 0: 
     646                    data_n = -data_n 
     647                err_y[i_q] += frac * frac * data_n 
     648            else: 
     649                err_y[i_q] += frac * frac * err_data[npt] * err_data[npt] 
     650            if dq_data is not None: 
     651                # To be consistent with dq calculation in 1d reduction, 
     652                # we need just the averages (not quadratures) because 
     653                # it should not depend on the number of the q points 
     654                # in the qr bins. 
     655                err_x[i_q] += frac * dq_data[npt] 
     656            else: 
     657                err_x = None 
     658            y_counts[i_q] += frac 
     659 
     660        # Average the sums 
     661        for n in range(nbins): 
     662            if err_y[n] < 0: 
     663                err_y[n] = -err_y[n] 
     664            err_y[n] = math.sqrt(err_y[n]) 
     665            # if err_x is not None: 
     666            #    err_x[n] = math.sqrt(err_x[n]) 
     667 
     668        err_y = err_y / y_counts 
     669        err_y[err_y == 0] = np.average(err_y) 
     670        y = y / y_counts 
     671        x = x / y_counts 
     672        idx = (np.isfinite(y)) & (np.isfinite(x)) 
     673 
     674        if err_x is not None: 
     675            d_x = err_x[idx] / y_counts[idx] 
     676        else: 
     677            d_x = None 
     678 
     679        if not idx.any(): 
     680            msg = "Average Error: No points inside ROI to average..." 
     681            raise ValueError(msg) 
     682 
     683        return Data1D(x=x[idx], y=y[idx], dy=err_y[idx], dx=d_x) 
     684 
     685################################################################################ 
     686 
     687class Ring(object): 
     688    """ 
     689    Defines a ring on a 2D data set. 
     690    The ring is defined by r_min, r_max, and 
     691    the position of the center of the ring. 
     692 
     693    The data returned is the distribution of counts 
     694    around the ring as a function of phi. 
     695 
     696    Phi_min and phi_max should be defined between 0 and 2*pi 
     697    in anti-clockwise starting from the x- axis on the left-hand side 
     698    """ 
     699    # Todo: remove center. 
     700 
     701    def __init__(self, r_min=0, r_max=0, center_x=0, center_y=0, nbins=36): 
     702        # Minimum radius 
     703        self.r_min = r_min 
     704        # Maximum radius 
     705        self.r_max = r_max 
     706        # Center of the ring in x 
     707        self.center_x = center_x 
     708        # Center of the ring in y 
     709        self.center_y = center_y 
     710        # Number of angular bins 
     711        self.nbins_phi = nbins 
     712 
     713    def __call__(self, data2D): 
     714        """ 
     715        Apply the ring to the data set. 
     716        Returns the angular distribution for a given q range 
     717 
     718        :param data2D: Data2D object 
     719 
     720        :return: Data1D object 
     721        """ 
     722        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]: 
     723            raise RuntimeError("Ring averaging only take plottable_2D objects") 
     724 
     725        Pi = math.pi 
     726 
     727        # Get data 
     728        data = data2D.data[np.isfinite(data2D.data)] 
     729        q_data = data2D.q_data[np.isfinite(data2D.data)] 
     730        err_data = data2D.err_data[np.isfinite(data2D.data)] 
     731        qx_data = data2D.qx_data[np.isfinite(data2D.data)] 
     732        qy_data = data2D.qy_data[np.isfinite(data2D.data)] 
     733 
     734        # Set space for 1d outputs 
     735        phi_bins = np.zeros(self.nbins_phi) 
     736        phi_counts = np.zeros(self.nbins_phi) 
     737        phi_values = np.zeros(self.nbins_phi) 
     738        phi_err = np.zeros(self.nbins_phi) 
     739 
     740        # Shift to apply to calculated phi values in order 
     741        # to center first bin at zero 
     742        phi_shift = Pi / self.nbins_phi 
     743 
     744        for npt in range(len(data)): 
     745            frac = 0 
     746            # q-value at the point (npt) 
     747            q_value = q_data[npt] 
     748            data_n = data[npt] 
     749 
     750            # phi-value at the point (npt) 
     751            phi_value = math.atan2(qy_data[npt], qx_data[npt]) + Pi 
     752 
     753            if self.r_min <= q_value and q_value <= self.r_max: 
     754                frac = 1 
     755            if frac == 0: 
     756                continue 
     757            # binning 
     758            i_phi = int(math.floor((self.nbins_phi) * 
     759                                   (phi_value + phi_shift) / (2 * Pi))) 
     760 
     761            # Take care of the edge case at phi = 2pi. 
     762            if i_phi >= self.nbins_phi: 
     763                i_phi = 0 
     764            phi_bins[i_phi] += frac * data[npt] 
     765 
     766            if err_data is None or err_data[npt] == 0.0: 
     767                if data_n < 0: 
     768                    data_n = -data_n 
     769                phi_err[i_phi] += frac * frac * math.fabs(data_n) 
     770            else: 
     771                phi_err[i_phi] += frac * frac * err_data[npt] * err_data[npt] 
     772            phi_counts[i_phi] += frac 
     773 
     774        for i in range(self.nbins_phi): 
     775            phi_bins[i] = phi_bins[i] / phi_counts[i] 
     776            phi_err[i] = math.sqrt(phi_err[i]) / phi_counts[i] 
     777            phi_values[i] = 2.0 * math.pi / self.nbins_phi * (1.0 * i) 
     778 
     779        idx = (np.isfinite(phi_bins)) 
     780 
     781        if not idx.any(): 
     782            msg = "Average Error: No points inside ROI to average..." 
     783            raise ValueError(msg) 
     784        # elif len(phi_bins[idx])!= self.nbins_phi: 
     785        #    print "resulted",self.nbins_phi- len(phi_bins[idx]) 
     786        #,"empty bin(s) due to tight binning..." 
     787        return Data1D(x=phi_values[idx], y=phi_bins[idx], dy=phi_err[idx]) 
    738788 
    739789 
     
    750800    starting from the x- axis on the left-hand side 
    751801    """ 
    752     def __init__(self, r_min, r_max, phi_min=0, phi_max=2 * math.pi, nbins=20): 
     802 
     803    def __init__(self, r_min, r_max, phi_min=0, phi_max=2 * math.pi, nbins=20, 
     804                 base=None): 
     805        ''' 
     806        :param base: must be a valid base for an algorithm, i.e., 
     807        a positive number 
     808        ''' 
    753809        self.r_min = r_min 
    754810        self.r_max = r_max 
     
    756812        self.phi_max = phi_max 
    757813        self.nbins = nbins 
     814        self.base = base 
    758815 
    759816    def _agv(self, data2D, run='phi'): 
     
    767824        """ 
    768825        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]: 
    769             raise RuntimeError, "Ring averaging only take plottable_2D objects" 
    770         Pi = math.pi 
     826            raise RuntimeError("Ring averaging only take plottable_2D objects") 
    771827 
    772828        # Get the all data & info 
     
    776832        qx_data = data2D.qx_data[np.isfinite(data2D.data)] 
    777833        qy_data = data2D.qy_data[np.isfinite(data2D.data)] 
     834 
    778835        dq_data = None 
    779  
    780         # Get the dq for resolution averaging 
    781836        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 
     837            dq_data = get_dq_data(data2D) 
     838 
     839        # set space for 1d outputs 
    822840        x = np.zeros(self.nbins) 
    823841        y = np.zeros(self.nbins) 
    824842        y_err = np.zeros(self.nbins) 
    825843        x_err = np.zeros(self.nbins) 
    826         y_counts = np.zeros(self.nbins) 
     844        y_counts = np.zeros(self.nbins)  # Cycle counts (for the mean) 
    827845 
    828846        # Get the min and max into the region: 0 <= phi < 2Pi 
     
    830848        phi_max = flip_phi(self.phi_max) 
    831849 
     850        #  binning object 
     851        if run.lower() == 'phi': 
     852            binning = Binning(self.phi_min, self.phi_max, self.nbins, self.base) 
     853        else: 
     854            binning = Binning(self.r_min, self.r_max, self.nbins, self.base) 
     855 
    832856        for n in range(len(data)): 
    833             frac = 0 
    834857 
    835858            # q-value at the pixel (j,i) 
     
    841864 
    842865            # 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: 
     866            phi_value = math.atan2(qy_data[n], qx_data[n]) + math.pi 
     867 
     868            # No need to calculate: data outside of the radius 
     869            if self.r_min > q_value or q_value > self.r_max: 
    849870                continue 
    850             #In case of two ROIs (symmetric major and minor regions)(for 'q2') 
     871 
     872            # In case of two ROIs (symmetric major and minor regions)(for 'q2') 
    851873            if run.lower() == 'q2': 
    852                 ## For minor sector wing 
     874                # For minor sector wing 
    853875                # Calculate the minor wing phis 
    854                 phi_min_minor = flip_phi(phi_min - Pi) 
    855                 phi_max_minor = flip_phi(phi_max - Pi) 
     876                phi_min_minor = flip_phi(phi_min - math.pi) 
     877                phi_max_minor = flip_phi(phi_max - math.pi) 
    856878                # Check if phis of the minor ring is within 0 to 2pi 
    857879                if phi_min_minor > phi_max_minor: 
    858                     is_in = (phi_value > phi_min_minor or \ 
    859                               phi_value < phi_max_minor) 
     880                    is_in = (phi_value > phi_min_minor or 
     881                             phi_value < phi_max_minor) 
    860882                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 
     883                    is_in = (phi_value > phi_min_minor and 
     884                             phi_value < phi_max_minor) 
     885 
     886            # For all cases(i.e.,for 'q', 'q2', and 'phi') 
     887            # Find pixels within ROI 
    866888            if phi_min > phi_max: 
    867                 is_in = is_in or (phi_value > phi_min or \ 
    868                                    phi_value < phi_max) 
     889                is_in = is_in or (phi_value > phi_min or 
     890                                  phi_value < phi_max) 
    869891            else: 
    870                 is_in = is_in or (phi_value >= phi_min  and \ 
    871                                     phi_value < phi_max) 
    872  
     892                is_in = is_in or (phi_value >= phi_min and 
     893                                  phi_value < phi_max) 
     894 
     895            # data oustide of the phi range 
    873896            if not is_in: 
    874                 frac = 0 
    875             if frac == 0: 
    876897                continue 
    877             # Check which type of averaging we need 
     898 
     899            # Get the binning index 
    878900            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)) 
     901                i_bin = binning.get_bin_index(phi_value) 
    882902            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)) 
     903                i_bin = binning.get_bin_index(q_value) 
    886904 
    887905            # Take care of the edge case at phi = 2pi. 
     
    889907                i_bin = self.nbins - 1 
    890908 
    891             ## Get the total y 
    892             y[i_bin] += frac * data_n 
    893             x[i_bin] += frac * q_value 
     909            # Get the total y 
     910            y[i_bin] += data_n 
     911            x[i_bin] += q_value 
    894912            if err_data[n] is None or err_data[n] == 0.0: 
    895913                if data_n < 0: 
    896914                    data_n = -data_n 
    897                 y_err[i_bin] += frac * frac * data_n 
     915                y_err[i_bin] += data_n 
    898916            else: 
    899                 y_err[i_bin] += frac * frac * err_data[n] * err_data[n] 
     917                y_err[i_bin] += err_data[n]**2 
    900918 
    901919            if dq_data is not None: 
     
    904922                # it should not depend on the number of the q points 
    905923                # in the qr bins. 
    906                 x_err[i_bin] += frac * dq_data[n] 
     924                x_err[i_bin] += dq_data[n] 
    907925            else: 
    908926                x_err = None 
    909             y_counts[i_bin] += frac 
     927            y_counts[i_bin] += 1 
    910928 
    911929        # Organize the results 
     
    922940                # We take the center of ring area, not radius. 
    923941                # 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) 
     942                # delta_r = (self.r_max - self.r_min) / self.nbins 
     943                # r_inner = self.r_min + delta_r * i 
     944                # r_outer = r_inner + delta_r 
     945                # x[i] = math.sqrt((r_inner * r_inner + r_outer * r_outer) / 2) 
    928946                x[i] = x[i] / y_counts[i] 
    929947        y_err[y_err == 0] = np.average(y_err) 
     
    935953        if not idx.any(): 
    936954            msg = "Average Error: No points inside sector of ROI to average..." 
    937             raise ValueError, msg 
    938         #elif len(y[idx])!= self.nbins: 
     955            raise ValueError(msg) 
     956        # elif len(y[idx])!= self.nbins: 
    939957        #    print "resulted",self.nbins- len(y[idx]), 
    940         #"empty bin(s) due to tight binning..." 
     958        # "empty bin(s) due to tight binning..." 
    941959        return Data1D(x=x[idx], y=y[idx], dy=y_err[idx], dx=d_x) 
    942960 
     
    950968    The number of bin in phi also has to be defined. 
    951969    """ 
     970 
    952971    def __call__(self, data2D): 
    953972        """ 
     
    969988    The number of bin in Q also has to be defined. 
    970989    """ 
     990 
    971991    def __call__(self, data2D): 
    972992        """ 
     
    979999        return self._agv(data2D, 'q2') 
    9801000 
     1001################################################################################ 
    9811002 
    9821003class Ringcut(object): 
     
    9911012    in anti-clockwise starting from the x- axis on the left-hand side 
    9921013    """ 
     1014 
    9931015    def __init__(self, r_min=0, r_max=0, center_x=0, center_y=0): 
    9941016        # Minimum radius 
     
    10111033        """ 
    10121034        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]: 
    1013             raise RuntimeError, "Ring cut only take plottable_2D objects" 
     1035            raise RuntimeError("Ring cut only take plottable_2D objects") 
    10141036 
    10151037        # Get data 
     
    10221044        return out 
    10231045 
     1046################################################################################ 
    10241047 
    10251048class Boxcut(object): 
     
    10271050    Find a rectangular 2D region of interest. 
    10281051    """ 
     1052 
    10291053    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0): 
    10301054        # Minimum Qx value [A-1] 
     
    10591083        """ 
    10601084        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]: 
    1061             raise RuntimeError, "Boxcut take only plottable_2D objects" 
     1085            raise RuntimeError("Boxcut take only plottable_2D objects") 
    10621086        # Get qx_ and qy_data 
    10631087        qx_data = data2D.qx_data 
     
    10701094        return outx & outy 
    10711095 
     1096################################################################################ 
    10721097 
    10731098class Sectorcut(object): 
     
    10811106    and (phi_max-phi_min) should not be larger than pi 
    10821107    """ 
     1108 
    10831109    def __init__(self, phi_min=0, phi_max=math.pi): 
    10841110        self.phi_min = phi_min 
     
    11101136        """ 
    11111137        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]: 
    1112             raise RuntimeError, "Sectorcut take only plottable_2D objects" 
     1138            raise RuntimeError("Sectorcut take only plottable_2D objects") 
    11131139        Pi = math.pi 
    11141140        # Get data 
     
    11241150        # check for major sector 
    11251151        if phi_min_major > phi_max_major: 
    1126             out_major = (phi_min_major <= phi_data) + (phi_max_major > phi_data) 
     1152            out_major = (phi_min_major <= phi_data) + \ 
     1153                (phi_max_major > phi_data) 
    11271154        else: 
    1128             out_major = (phi_min_major <= phi_data) & (phi_max_major > phi_data) 
     1155            out_major = (phi_min_major <= phi_data) & ( 
     1156                phi_max_major > phi_data) 
    11291157 
    11301158        # minor sector 
     
    11361164        if phi_min_minor > phi_max_minor: 
    11371165            out_minor = (phi_min_minor <= phi_data) + \ 
    1138                             (phi_max_minor >= phi_data) 
     1166                (phi_max_minor >= phi_data) 
    11391167        else: 
    11401168            out_minor = (phi_min_minor <= phi_data) & \ 
    1141                             (phi_max_minor >= phi_data) 
     1169                (phi_max_minor >= phi_data) 
    11421170        out = out_major + out_minor 
    11431171 
Note: See TracChangeset for help on using the changeset viewer.