Index: DataLoader/manipulations.py
===================================================================
--- DataLoader/manipulations.py	(revision 76e23691e12caa20c95e83c3161ff5e9ba24edb5)
+++ DataLoader/manipulations.py	(revision 76e23691e12caa20c95e83c3161ff5e9ba24edb5)
@@ -0,0 +1,379 @@
+"""
+    
+"""
+
+"""
+This software was developed by the University of Tennessee as part of the
+Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+project funded by the US National Science Foundation. 
+
+See the license text in license.txt
+
+copyright 2008, University of Tennessee
+"""
+#TODO: copy the meta data from the 2D object to the resulting 1D object
+
+from data_info import plottable_2D, Data1D
+import math
+import numpy
+
+def get_q(dx, dy, det_dist, wavelength):
+    """
+        @param dx: x-distance from beam center [mm]
+        @param dy: y-distance from beam center [mm]
+        @return: q-value at the given position
+    """
+    # Distance from beam center in the plane of detector
+    plane_dist = math.sqrt(dx*dx + dy*dy)
+    # Half of the scattering angle
+    theta      = 0.5*math.atan(plane_dist/det_dist)
+    return (4.0*math.pi/wavelength)*math.sin(theta)
+    
+
+
+class Slabs:
+    def __init__(self):
+        pass
+    
+class Boxsum:
+    pass
+
+class CircularAverage(object):
+    """
+        Perform circular averaging on 2D data
+        
+        The data returned is the distribution of counts
+        as a function of Q
+    """
+    def __init__(self, r_min=0.0, r_max=0.0, bin_width=0.001):
+        # Minimum radius included in the average [A-1]
+        self.r_min = r_min
+        # Maximum radius included in the average [A-1]
+        self.r_max = r_max
+        # Bin width (step size) [A-1]
+        self.bin_width = bin_width
+
+    def __call__(self, data2D):
+        """
+            Perform circular averaging on the data
+            
+            @param data2D: Data2D object
+            @return: Data1D object
+        """
+        if len(data2D.detector) != 1:
+            raise RuntimeError, "Circular averaging: invalid number of detectors: %g" % len(data2D.detector)
+        
+        pixel_width = data2D.detector[0].pixel_size.x
+        det_dist    = data2D.detector[0].distance
+        wavelength  = data2D.source.wavelength
+        center_x    = data2D.detector[0].beam_center.x/pixel_width
+        center_y    = data2D.detector[0].beam_center.y/pixel_width
+        
+        # Find out the maximum Q range
+        xwidth = numpy.size(data2D.data,1)*pixel_width
+        dx_max = xwidth - data2D.detector[0].beam_center.x
+        if xwidth-dx_max>dx_max:
+            dx_max = xwidth-dx_max
+            
+        ywidth = numpy.size(data2D.data,0)*pixel_width
+        dy_max = ywidth - data2D.detector[0].beam_center.y
+        if ywidth-dy_max>dy_max:
+            dy_max = ywidth-dy_max
+        
+        qmax = get_q(dx_max, dy_max, det_dist, wavelength)
+        
+        # Build array of Q intervals
+        nbins = int(math.ceil((qmax-self.r_min)/self.bin_width))
+        qbins = self.bin_width*numpy.arange(nbins)+self.r_min
+        
+        x  = numpy.zeros(nbins)
+        y  = numpy.zeros(nbins)
+        err_y = numpy.zeros(nbins)
+        y_counts = numpy.zeros(nbins)
+        
+        for i in range(len(data2D.data)):
+            dx = pixel_width*(i+0.5 - center_x)
+            
+            # Min and max x-value for the pixel
+            minx = pixel_width*(i - center_x)
+            maxx = pixel_width*(i+1.0 - center_x)
+            
+            for j in range(len(data2D.data)):
+                dy = pixel_width*(j+0.5 - center_y)
+            
+                q_value = get_q(dx, dy, det_dist, wavelength)
+            
+                # Min and max y-value for the pixel
+                miny = pixel_width*(j - center_y)
+                maxy = pixel_width*(j+1.0 - center_y)
+                
+                # Calculate the q-value for each corner
+                # q_[x min or max][y min or max]
+                q_00 = get_q(minx, miny, det_dist, wavelength)
+                q_01 = get_q(minx, maxy, det_dist, wavelength)
+                q_10 = get_q(maxx, miny, det_dist, wavelength)
+                q_11 = get_q(maxx, maxy, det_dist, wavelength)
+                
+                # Look for intercept between each side of the pixel
+                # and the constant-q ring for qmax
+                frac_max = get_pixel_fraction(self.r_max, q_00, q_01, q_10, q_11)
+                
+                # Look for intercept between each side of the pixel
+                # and the constant-q ring for qmin
+                frac_min = get_pixel_fraction(self.r_min, q_00, q_01, q_10, q_11)
+                
+                # We are interested in the region between qmin and qmax
+                # therefore the fraction of the surface of the pixel
+                # that we will use to calculate the number of counts to 
+                # include is given by:
+                frac = frac_max - frac_min
+
+                i_q = int(math.ceil((q_value-self.r_min)/self.bin_width)) - 1
+            
+                x[i_q]          = q_value
+                y[i_q]         += frac * data2D.data[j][i]
+                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
+                    err_y[i_q] += frac * frac * math.fabs(data2D.data[j][i])
+                else:
+                    err_y[i_q] += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
+                y_counts[i_q]  += frac
+        
+        # Average the sums
+        for i in range(nbins):
+            if y_counts[i]>0:
+                err_y[i] = math.sqrt(err_y[i])/y_counts[i]
+                y[i]     = y[i]/y_counts[i]
+        
+        return Data1D(x=x, y=y, dy=err_y)
+    
+
+class Ring(object):
+    """
+        Defines a ring on a 2D data set.
+        The ring is defined by r_min, r_max, and
+        the position of the center of the ring.
+        
+        The data returned is the distribution of counts
+        around the ring as a function of phi.
+        
+    """
+    
+    def __init__(self, r_min=0, r_max=0, center_x=0, center_y=0):
+        # Minimum radius
+        self.r_min = r_min
+        # Maximum radius
+        self.r_max = r_max
+        # Center of the ring in x
+        self.center_x = center_x
+        # Center of the ring in y
+        self.center_y = center_y
+        # Number of angular bins
+        self.nbins_phi = 20
+        
+    def __call__(self, data2D):
+        """
+            Apply the ring to the data set.
+            Returns the angular distribution for a given q range
+            
+            @param data2D: Data2D object
+            @return: Data1D object
+        """
+        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
+            raise RuntimeError, "Ring averaging only take plottable_2D objects"
+        
+        data = data2D.data
+        qmin = self.r_min
+        qmax = self.r_max
+        
+        if len(data2D.detector) != 1:
+            raise RuntimeError, "Ring averaging: invalid number of detectors: %g" % len(data2D.detector)
+        pixel_width = data2D.detector[0].pixel_size.x
+        det_dist = data2D.detector[0].distance
+        wavelength = data2D.source.wavelength
+        center_x = self.center_x/pixel_width
+        center_y = self.center_y/pixel_width
+        
+        phi_bins   = numpy.zeros(self.nbins_phi)
+        phi_counts = numpy.zeros(self.nbins_phi)
+        phi_values = numpy.zeros(self.nbins_phi)
+        phi_err    = numpy.zeros(self.nbins_phi)
+        
+        for i in range(len(data)):
+            dx = pixel_width*(i+0.5 - center_x)
+            
+            # Min and max x-value for the pixel
+            minx = pixel_width*(i - center_x)
+            maxx = pixel_width*(i+1.0 - center_x)
+            
+            for j in range(len(data)):
+                dy = pixel_width*(j+0.5 - center_y)
+            
+                q_value = get_q(dx, dy, det_dist, wavelength)
+            
+                # Min and max y-value for the pixel
+                miny = pixel_width*(j - center_y)
+                maxy = pixel_width*(j+1.0 - center_y)
+                
+                # Calculate the q-value for each corner
+                # q_[x min or max][y min or max]
+                q_00 = get_q(minx, miny, det_dist, wavelength)
+                q_01 = get_q(minx, maxy, det_dist, wavelength)
+                q_10 = get_q(maxx, miny, det_dist, wavelength)
+                q_11 = get_q(maxx, maxy, det_dist, wavelength)
+                
+                # Look for intercept between each side of the pixel
+                # and the constant-q ring for qmax
+                frac_max = get_pixel_fraction(qmax, q_00, q_01, q_10, q_11)
+                
+                # Look for intercept between each side of the pixel
+                # and the constant-q ring for qmin
+                frac_min = get_pixel_fraction(qmin, q_00, q_01, q_10, q_11)
+                
+                # We are interested in the region between qmin and qmax
+                # therefore the fraction of the surface of the pixel
+                # that we will use to calculate the number of counts to 
+                # include is given by:
+                
+                frac = frac_max - frac_min
+
+                i_phi = int(math.ceil(self.nbins_phi*(math.atan2(dy, dx)+math.pi)/(2.0*math.pi)) - 1)
+            
+                phi_bins[i_phi] += frac * data[j][i]
+                
+                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
+                    phi_err[i_phi] += frac * frac * math.fabs(data2D.data[j][i])
+                else:
+                    phi_err[i_phi] += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
+                phi_counts[i_phi] += frac
+        
+        for i in range(self.nbins_phi):
+            phi_bins[i] = phi_bins[i] / phi_counts[i]
+            phi_err[i] = math.sqrt(phi_err[i]) / phi_counts[i]
+            phi_values[i] = 2.0*math.pi/self.nbins_phi*(1.0*i + 0.5)
+            
+        return Data1D(x=phi_values, y=phi_bins, dy=phi_err)
+    
+def get_pixel_fraction(qmax, q_00, q_01, q_10, q_11):
+    """
+        Returns the fraction of the pixel defined by
+        the four corners (q_00, q_01, q_10, q_11) that 
+        has q < qmax.
+        
+                q_01                q_11
+        y=1         +--------------+
+                    |              |
+                    |              |
+                    |              |
+        y=0         +--------------+
+                q_00                q_01
+        
+                    x=0            x=1
+        
+    """
+    
+    # y side for x = minx
+    x_0 = get_intercept(qmax, q_00, q_01)
+    # y side for x = maxx
+    x_1 = get_intercept(qmax, q_10, q_11)
+    
+    # x side for y = miny
+    y_0 = get_intercept(qmax, q_00, q_10)
+    # x side for y = maxy
+    y_1 = get_intercept(qmax, q_01, q_11)
+    
+    # surface fraction for a 1x1 pixel
+    frac_max = 0
+    
+    if x_0 and x_1:
+        frac_max = (x_0+x_1)/2.0
+    
+    elif y_0 and y_1:
+        frac_max = (y_0+y_1)/2.0
+    
+    elif x_0 and y_0:
+        if q_00 < q_10:
+            frac_max = x_0*y_0/2.0
+        else:
+            frac_max = 1.0-x_0*y_0/2.0
+    
+    elif x_0 and y_1:
+        if q_00 < q_10:
+            frac_max = x_0*y_1/2.0
+        else:
+            frac_max = 1.0-x_0*y_1/2.0
+    
+    elif x_1 and y_0:
+        if q_00 > q_10:
+            frac_max = x_1*y_0/2.0
+        else:
+            frac_max = 1.0-x_1*y_0/2.0
+    
+    elif x_1 and y_1:
+        if q_00 < q_10:
+            frac_max = 1.0 - (1.0-x_1)*(1.0-y_1)/2.0
+        else:
+            frac_max = (1.0-x_1)*(1.0-y_1)/2.0
+            
+    # If we make it here, there is no intercept between
+    # this pixel and the constant-q ring. We only need
+    # to know if we have to include it or exclude it.
+    elif (q_00+q_01+q_10+q_11)/4.0 < qmax:
+        frac_max = 1.0
+   
+    return frac_max
+             
+def get_intercept(q, q_0, q_1):
+    """
+        Returns the fraction of the side at which the
+        q-value intercept the pixel, None otherwise.
+        The values returned is the fraction ON THE SIDE
+        OF THE LOWEST Q.
+        
+        
+        
+                A        B    
+         +-----------+--------+
+         0                    1     <--- pixel size
+         
+        Q_0 -------- Q ----- Q_1    <--- equivalent Q range
+        
+        
+        if Q_1 > Q_0, A is returned
+        if Q_1 < Q_0, B is returned
+        
+        if Q is outside the range of [Q_0, Q_1], None is returned
+         
+    """
+    if q_1 > q_0:
+        if (q > q_0 and q <= q_1):
+            return (q-q_0)/(q_1 - q_0)    
+    else:
+        if (q > q_1 and q <= q_0):
+            return (q-q_1)/(q_0 - q_1)
+    return None
+    
+
+class Sector:
+    """
+        Defines a sector region on a 2D data set.
+        The sector is defined by r_min, r_max, phi_min, phi_max,
+        and the position of the center of the ring. 
+    """
+    pass
+
+if __name__ == "__main__": 
+
+    from loader import Loader
+    
+
+    #d = Loader().load('test/MAR07232_rest.ASC')
+    d = Loader().load('test/MP_New.sans')
+
+    
+    r = CircularAverage(r_min=.0, r_max=.075,bin_width=0.0003)
+    o = r(d)
+    for i in range(len(o.x)):
+        print o.x[i], o.y[i], o.dy[i]
+    
+ 
+    
Index: DataLoader/test/avg_testdata.txt
===================================================================
--- DataLoader/test/avg_testdata.txt	(revision 76e23691e12caa20c95e83c3161ff5e9ba24edb5)
+++ DataLoader/test/avg_testdata.txt	(revision 76e23691e12caa20c95e83c3161ff5e9ba24edb5)
@@ -0,0 +1,86 @@
+87
+0.000202106299717 -0.027508775 0.082928847514
+0.000551789534483 0.0284421916667 0.0731850536881
+0.000798810493953 1.60736842105e-005 0.0590302471882
+0.00116126782404 0.0438142678571 0.0517950053242
+0.00140714188022 0.064685778125 0.0547626242937
+0.00177124497692 1.07265199091 0.156421969167
+0.00201660336116 36.810534634 0.885116020355
+0.0023850851145 92.1891982143 1.28305716256
+0.00267278050009 84.665209375 1.15017124659
+0.00298274181425 56.7100338028 0.893718563342
+0.00325127626479 37.5359731707 0.676576507407
+0.00357359498717 25.3518046512 0.542944220133
+0.00383959397654 16.5985670833 0.415814550553
+0.0041700170637 11.4996769903 0.334136722646
+0.00443382276153 7.98444159292 0.265817183716
+0.00476991662481 5.6620977193 0.22286211848
+0.00503186595945 4.25155079365 0.183691226291
+0.00539412022968 3.34649076336 0.159830319458
+0.00568671283121 2.65831783099 0.136823053999
+0.00598988289328 2.09287549655 0.120140018787
+0.00626025579568 1.742223725 0.104349883954
+0.00657095900024 1.39967185802 0.0930044848904
+0.00688926719883 1.18864367586 0.0826515909917
+0.00715686844465 0.953296011111 0.0729892127156
+0.0074801022006 0.827779119251 0.0668699693599
+0.00774651148348 0.719786980725 0.0620716112536
+0.00807379933294 0.612540272081 0.0572616304198
+0.00839302584963 0.566606194831 0.0532825833092
+0.00867886586147 0.478724006452 0.047902248102
+0.00897219569306 0.413986979545 0.0454682930801
+0.0092723038804 0.383747495339 0.0429772810607
+0.00957855263679 0.299709392875 0.0379986890102
+0.00989037086379 0.3335908208 0.0393691239425
+0.0101595073016 0.286069857255 0.0370817933172
+0.0104753452352 0.259831640906 0.0348225626354
+0.0107957306999 0.252761626236 0.0343460413844
+0.0110630176098 0.251568055396 0.0336423804744
+0.0113930077886 0.235093004317 0.0328371516903
+0.0116916257614 0.213031539933 0.0306893694272
+0.0119857184654 0.181918172852 0.0294712039203
+0.012284885515 0.209558449686 0.029654495452
+0.0125887644552 0.184974602215 0.028223277554
+0.0128970215248 0.157850908537 0.0269431518661
+0.0131668490761 0.175325451829 0.0276139587923
+0.0134784412725 0.121815444807 0.026429336777
+0.0137937807812 0.16237724087 0.0258899780789
+0.0140622641312 0.135818960632 0.0258645735537
+0.0143930943297 0.129014499722 0.0254960218544
+0.0146864095013 0.142419530685 0.0249695336221
+0.0149988047287 0.143813170789 0.0244766681401
+0.0152973754556 0.122014793995 0.0229128991842
+0.0155997665361 0.128637397727 0.0232999901007
+0.0158704888872 0.126514509848 0.0234398006234
+0.0161759543409 0.125320390268 0.022797589029
+0.0164776012943 0.109229219192 0.0222425678213
+0.0167646754651 0.124861313021 0.0232126170142
+0.0170227523446 0.12825973162 0.0231933098642
+0.0173156876264 0.0947754240896 0.0228973329864
+0.0176417418879 0.122358410056 0.0240616388086
+0.0179991119552 0.0946951700297 0.0223980227203
+0.0182538528641 0.121243864198 0.0251867566004
+0.0185211566597 0.114524376471 0.0254586169395
+0.0188004862436 0.105750747719 0.0248183671151
+0.0190913124594 0.0732775201681 0.0279438273842
+0.0193931167311 0.097700884141 0.0290122599042
+0.0197053932406 0.113561495455 0.0302481661165
+0.0200276506769 0.08421012 0.0293502263012
+0.0203594135936 0.107610352071 0.0327605964871
+0.0205287156218 0.115224884906 0.0344222268277
+0.0208738823951 0.0941588853147 0.0342119909635
+0.0212274412762 0.0870687160305 0.037120278486
+0.0215889783111 0.071684565 0.0385328986423
+0.0217726142518 0.107426831731 0.0435558726696
+0.0221453821308 0.106882672826 0.0445934315513
+0.0223344219011 0.0731600060241 0.042980047662
+0.0227175909983 0.106005602985 0.052102966241
+0.0229116347581 0.0987530476923 0.0533983398452
+0.0233044347998 0.102729822449 0.0601602327345
+0.0235031118611 0.077848535 0.066185247601
+0.0239048296539 0.0854999352941 0.0703156144831
+0.024000069863 0.1014931 0.0797968391526
+0.0243705875591 -0.00442893181818 0.0860634747975
+0.024747558388 0.0889580272727 0.132841659925
+0.024938371921 0.169803994133 0.145644771445
+
Index: DataLoader/test/ring_testdata.txt
===================================================================
--- DataLoader/test/ring_testdata.txt	(revision 76e23691e12caa20c95e83c3161ff5e9ba24edb5)
+++ DataLoader/test/ring_testdata.txt	(revision 76e23691e12caa20c95e83c3161ff5e9ba24edb5)
@@ -0,0 +1,21 @@
+<X>   <Y>   <dY>
+0.15708  0.972421  0.0817009
+0.471239  1.11139  0.0823546
+0.785398  1.09267  0.0823443
+1.09956  1.07895  0.0817159
+1.41372  1.14111  0.0831238
+1.72788  1.07949  0.0840375
+2.04204  1.10197  0.0824725
+2.35619  1.00297  0.0787024
+2.67035  1.02805  0.0804773
+2.98451  0.919877  0.0783511
+3.29867  0.973222  0.0769917
+3.61283  0.915259  0.0760745
+3.92699  0.890996  0.0751355
+4.24115  0.914385  0.0759576
+4.55531  0.980681  0.0791343
+4.86947  0.9385  0.0755147
+5.18363  0.973714  0.0781204
+5.49779  0.968017  0.0781457
+5.81195  1.01289  0.0785441
+6.12611  1.0482  0.0782739
Index: DataLoader/test/utest_abs_reader.py
===================================================================
--- DataLoader/test/utest_abs_reader.py	(revision bee885e9167ee1682eceb5e5c2805ea0f688a26c)
+++ DataLoader/test/utest_abs_reader.py	(revision 76e23691e12caa20c95e83c3161ff5e9ba24edb5)
@@ -69,4 +69,6 @@
         """
         self.assertEqual(self.data.filename, "S2-30dq.d1d")
+        self.assertEqual(len(self.data.x), 134)
+        self.assertEqual(len(self.data.y), 134)
         #          Q           I               dI          dQ  
         # Point 1: 0.003014    0.003010        0.000315    0.008249
Index: DataLoader/test/utest_averaging.py
===================================================================
--- DataLoader/test/utest_averaging.py	(revision 76e23691e12caa20c95e83c3161ff5e9ba24edb5)
+++ DataLoader/test/utest_averaging.py	(revision 76e23691e12caa20c95e83c3161ff5e9ba24edb5)
@@ -0,0 +1,48 @@
+
+import unittest
+
+from DataLoader.loader import  Loader
+from DataLoader.manipulations import Ring, CircularAverage
+ 
+import os.path
+
+class data_info_tests(unittest.TestCase):
+    
+    def setUp(self):
+        self.data = Loader().load('MAR07232_rest.ASC')
+        
+    def test_ring(self):
+        """
+            Test ring averaging
+        """
+        r = Ring(r_min=.005, r_max=.01, 
+                 center_x=self.data.detector[0].beam_center.x, 
+                 center_y=self.data.detector[0].beam_center.y)
+        r.nbins_phi = 20
+        
+        o = r(self.data)
+        answer = Loader().load('ring_testdata.txt')
+        for i in range(r.nbins_phi):
+            self.assertAlmostEqual(o.x[i], answer.x[i], 4)
+            self.assertAlmostEqual(o.y[i], answer.y[i], 4)
+            self.assertAlmostEqual(o.dy[i], answer.dy[i], 4)
+            
+    def test_circularavg(self):
+        """
+            Test circular averaging
+            The test data was not generated by IGOR.
+        """
+        r = CircularAverage(r_min=.00, r_max=.025, 
+                 bin_width=0.0003)
+        r.nbins_phi = 20
+        
+        o = r(self.data)
+        answer = Loader().load('avg_testdata.txt')
+        for i in range(r.nbins_phi):
+            self.assertAlmostEqual(o.x[i], answer.x[i], 4)
+            self.assertAlmostEqual(o.y[i], answer.y[i], 4)
+            self.assertAlmostEqual(o.dy[i], answer.dy[i], 4)
+            
+
+if __name__ == '__main__':
+    unittest.main()
