source: sasview/DataLoader/manipulations.py @ 70975f3

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.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 70975f3 was 70975f3, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Added slab averaging (X and Y)

  • Property mode set to 100644
File size: 23.8 KB
Line 
1"""
2    Data manipulations for 2D data sets.
3    Using the meta data information, various types of averaging
4    are performed in Q-space
5"""
6
7"""
8This software was developed by the University of Tennessee as part of the
9Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
10project funded by the US National Science Foundation.
11
12See the license text in license.txt
13
14copyright 2008, University of Tennessee
15"""
16#TODO: copy the meta data from the 2D object to the resulting 1D object
17#TODO: Don't assume square pixels
18
19from data_info import plottable_2D, Data1D
20import math
21import numpy
22
23def get_q(dx, dy, det_dist, wavelength):
24    """
25        @param dx: x-distance from beam center [mm]
26        @param dy: y-distance from beam center [mm]
27        @return: q-value at the given position
28    """
29    # Distance from beam center in the plane of detector
30    plane_dist = math.sqrt(dx*dx + dy*dy)
31    # Half of the scattering angle
32    theta      = 0.5*math.atan(plane_dist/det_dist)
33    return (4.0*math.pi/wavelength)*math.sin(theta)
34   
35class _Slab(object):
36    """
37        Compute average I(Q) for a region of interest
38    """
39    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0, bin_width=0.001):
40        # Minimum Qx value [A-1]
41        self.x_min = x_min
42        # Maximum Qx value [A-1]
43        self.x_max = x_max
44        # Minimum Qy value [A-1]
45        self.y_min = y_min
46        # Maximum Qy value [A-1]
47        self.y_max = y_max
48        # Bin width (step size) [A-1]
49        self.bin_width = bin_width
50        # If True, I(|Q|) will be return, otherwise, negative q-values are allowed
51        self.fold = False
52       
53    def __call__(self, data2D): return NotImplemented
54       
55    def _avg(self, data2D, maj):
56        """
57             Compute average I(Q_maj) for a region of interest.
58             The major axis is defined as the axis of Q_maj.
59             The minor axis is the axis that we average over.
60             
61             @param data2D: Data2D object
62             @param maj_min: min value on the major axis
63             @return: Data1D object
64        """
65        if len(data2D.detector) != 1:
66            raise RuntimeError, "_Slab._avg: invalid number of detectors: %g" % len(data2D.detector)
67       
68        pixel_width_x = data2D.detector[0].pixel_size.x
69        pixel_width_y = data2D.detector[0].pixel_size.y
70        det_dist    = data2D.detector[0].distance
71        wavelength  = data2D.source.wavelength
72        center_x    = data2D.detector[0].beam_center.x/pixel_width_x
73        center_y    = data2D.detector[0].beam_center.y/pixel_width_y
74               
75        # Build array of Q intervals
76        if maj=='x':
77            nbins = int(math.ceil((self.x_max-self.x_min)/self.bin_width))
78            qbins = self.bin_width*numpy.arange(nbins)+self.x_min
79        elif maj=='y':
80            nbins = int(math.ceil((self.y_max-self.y_min)/self.bin_width))
81            qbins = self.bin_width*numpy.arange(nbins)+self.y_min           
82        else:
83            raise RuntimeError, "_Slab._avg: unrecognized axis %s" % str(maj)
84                               
85        x  = numpy.zeros(nbins)
86        y  = numpy.zeros(nbins)
87        err_y = numpy.zeros(nbins)
88        y_counts = numpy.zeros(nbins)
89                                               
90        for i in range(numpy.size(data2D.data,1)):
91            # Min and max x-value for the pixel
92            minx = pixel_width_x*(i - center_x)
93            maxx = pixel_width_x*(i+1.0 - center_x)
94           
95            qxmin = get_q(minx, 0.0, det_dist, wavelength)
96            qxmax = get_q(maxx, 0.0, det_dist, wavelength)
97           
98            # Get the count fraction in x for that pixel
99            frac_min = get_pixel_fraction_square(self.x_min, qxmin, qxmax)
100            frac_max = get_pixel_fraction_square(self.x_max, qxmin, qxmax)
101            frac_x = frac_max - frac_min
102           
103            if frac_x == 0: 
104                continue
105           
106            if maj=='x':
107                dx = pixel_width_x*(i+0.5 - center_x)
108                q_value = get_q(dx, 0.0, det_dist, wavelength)
109                if self.fold==False and dx<0:
110                    q_value = -q_value
111                i_q = int(math.ceil((q_value-self.x_min)/self.bin_width)) - 1
112               
113                if i_q<0 or i_q>=nbins:
114                    continue
115                       
116            for j in range(numpy.size(data2D.data,0)):
117                # Min and max y-value for the pixel
118                miny = pixel_width_y*(j - center_y)
119                maxy = pixel_width_y*(j+1.0 - center_y)
120
121                qymin = get_q(0.0, miny, det_dist, wavelength)
122                qymax = get_q(0.0, maxy, det_dist, wavelength)
123               
124                # Get the count fraction in x for that pixel
125                frac_min = get_pixel_fraction_square(self.y_min, qymin, qymax)
126                frac_max = get_pixel_fraction_square(self.y_max, qymin, qymax)
127                frac_y = frac_max - frac_min
128               
129                frac = frac_x * frac_y
130               
131                if frac == 0:
132                    continue
133
134                if maj=='y':
135                    dy = pixel_width_y*(j+0.5 - center_y)
136                    q_value = get_q(0.0, dy, det_dist, wavelength)
137                    if self.fold==False and dy<0:
138                        q_value = -q_value
139                    i_q = int(math.ceil((q_value-self.y_min)/self.bin_width)) - 1
140                   
141                    if i_q<0 or i_q>=nbins:
142                        continue
143           
144                x[i_q]          = q_value
145                y[i_q]         += frac * data2D.data[j][i]
146                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
147                    err_y[i_q] += frac * frac * math.fabs(data2D.data[j][i])
148                else:
149                    err_y[i_q] += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
150                y_counts[i_q]  += frac
151
152        # Average the sums
153        for i in range(nbins):
154            if y_counts[i]>0:
155                err_y[i] = math.sqrt(err_y[i])/y_counts[i]
156                y[i]     = y[i]/y_counts[i]
157       
158        return Data1D(x=x, y=y, dy=err_y)
159       
160class SlabY(_Slab):
161    """
162        Compute average I(Qy) for a region of interest
163    """
164    def __call__(self, data2D):
165        """
166             Compute average I(Qy) for a region of interest
167             
168             @param data2D: Data2D object
169             @return: Data1D object
170        """
171        return self._avg(data2D, 'y')
172       
173class SlabX(_Slab):
174    """
175        Compute average I(Qx) for a region of interest
176    """
177    def __call__(self, data2D):
178        """
179             Compute average I(Qx) for a region of interest
180             
181             @param data2D: Data2D object
182             @return: Data1D object
183        """
184        return self._avg(data2D, 'x') 
185       
186class Boxsum(object):
187    """
188        Perform the sum of counts in a 2D region of interest.
189    """
190    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
191        # Minimum Qx value [A-1]
192        self.x_min = x_min
193        # Maximum Qx value [A-1]
194        self.x_max = x_max
195        # Minimum Qy value [A-1]
196        self.y_min = y_min
197        # Maximum Qy value [A-1]
198        self.y_max = y_max
199
200    def __call__(self, data2D):
201        """
202             Perform the sum in the region of interest
203             
204             @param data2D: Data2D object
205             @return: number of counts, error on number of counts
206        """
207        y, err_y, y_counts = self._sum(data2D)
208       
209        # Average the sums
210        counts = 0 if y_counts==0 else y
211        error  = 0 if y_counts==0 else math.sqrt(err_y)
212       
213        return counts, error
214       
215    def _sum(self, data2D):
216        """
217             Perform the sum in the region of interest
218             @param data2D: Data2D object
219             @return: number of counts, error on number of counts, number of entries summed
220        """
221        if len(data2D.detector) != 1:
222            raise RuntimeError, "Circular averaging: invalid number of detectors: %g" % len(data2D.detector)
223       
224        pixel_width = data2D.detector[0].pixel_size.x
225        det_dist    = data2D.detector[0].distance
226        wavelength  = data2D.source.wavelength
227        center_x    = data2D.detector[0].beam_center.x/pixel_width
228        center_y    = data2D.detector[0].beam_center.y/pixel_width
229               
230        y  = 0.0
231        err_y = 0.0
232        y_counts = 0.0
233               
234        for i in range(len(data2D.data)):
235            # Min and max x-value for the pixel
236            minx = pixel_width*(i - center_x)
237            maxx = pixel_width*(i+1.0 - center_x)
238           
239            qxmin = get_q(minx, 0.0, det_dist, wavelength)
240            qxmax = get_q(maxx, 0.0, det_dist, wavelength)
241           
242            # Get the count fraction in x for that pixel
243            frac_min = get_pixel_fraction_square(self.x_min, qxmin, qxmax)
244            frac_max = get_pixel_fraction_square(self.x_max, qxmin, qxmax)
245            frac_x = frac_max - frac_min
246           
247            for j in range(len(data2D.data)):
248                # Min and max y-value for the pixel
249                miny = pixel_width*(j - center_y)
250                maxy = pixel_width*(j+1.0 - center_y)
251
252                qymin = get_q(0.0, miny, det_dist, wavelength)
253                qymax = get_q(0.0, maxy, det_dist, wavelength)
254               
255                # Get the count fraction in x for that pixel
256                frac_min = get_pixel_fraction_square(self.y_min, qymin, qymax)
257                frac_max = get_pixel_fraction_square(self.y_max, qymin, qymax)
258                frac_y = frac_max - frac_min
259               
260                frac = frac_x * frac_y
261
262                y += frac * data2D.data[j][i]
263                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
264                    err_y += frac * frac * math.fabs(data2D.data[j][i])
265                else:
266                    err_y += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
267                y_counts += frac
268       
269        return y, err_y, y_counts
270     
271class Boxavg(Boxsum):
272    """
273        Perform the average of counts in a 2D region of interest.
274    """
275    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
276        super(Boxavg, self).__init__(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max)
277
278    def __call__(self, data2D):
279        """
280             Perform the sum in the region of interest
281             
282             @param data2D: Data2D object
283             @return: average counts, error on average counts
284        """
285        y, err_y, y_counts = self._sum(data2D)
286       
287        # Average the sums
288        counts = 0 if y_counts==0 else y/y_counts
289        error  = 0 if y_counts==0 else math.sqrt(err_y)/y_counts
290       
291        return counts, error
292       
293def get_pixel_fraction_square(x, xmin, xmax):
294    """
295         Return the fraction of the length
296         from xmin to x.
297         
298             A            B
299         +-----------+---------+
300         xmin        x         xmax
301         
302         @param x: x-value
303         @param xmin: minimum x for the length considered
304         @param xmax: minimum x for the length considered
305         @return: (x-xmin)/(xmax-xmin) when xmin < x < xmax
306         
307    """
308    if x<=xmin:
309        return 0.0
310    if x>xmin and x<xmax:
311        return (x-xmin)/(xmax-xmin)
312    else:
313        return 1.0
314
315
316class CircularAverage(object):
317    """
318        Perform circular averaging on 2D data
319       
320        The data returned is the distribution of counts
321        as a function of Q
322    """
323    def __init__(self, r_min=0.0, r_max=0.0, bin_width=0.001):
324        # Minimum radius included in the average [A-1]
325        self.r_min = r_min
326        # Maximum radius included in the average [A-1]
327        self.r_max = r_max
328        # Bin width (step size) [A-1]
329        self.bin_width = bin_width
330
331    def __call__(self, data2D):
332        """
333            Perform circular averaging on the data
334           
335            @param data2D: Data2D object
336            @return: Data1D object
337        """
338        if len(data2D.detector) != 1:
339            raise RuntimeError, "Circular averaging: invalid number of detectors: %g" % len(data2D.detector)
340       
341        pixel_width = data2D.detector[0].pixel_size.x
342        det_dist    = data2D.detector[0].distance
343        wavelength  = data2D.source.wavelength
344        center_x    = data2D.detector[0].beam_center.x/pixel_width
345        center_y    = data2D.detector[0].beam_center.y/pixel_width
346       
347        # Find out the maximum Q range
348        xwidth = numpy.size(data2D.data,1)*pixel_width
349        dx_max = xwidth - data2D.detector[0].beam_center.x
350        if xwidth-dx_max>dx_max:
351            dx_max = xwidth-dx_max
352           
353        ywidth = numpy.size(data2D.data,0)*pixel_width
354        dy_max = ywidth - data2D.detector[0].beam_center.y
355        if ywidth-dy_max>dy_max:
356            dy_max = ywidth-dy_max
357       
358        qmax = get_q(dx_max, dy_max, det_dist, wavelength)
359       
360        # Build array of Q intervals
361        nbins = int(math.ceil((qmax-self.r_min)/self.bin_width))
362        qbins = self.bin_width*numpy.arange(nbins)+self.r_min
363       
364        x  = numpy.zeros(nbins)
365        y  = numpy.zeros(nbins)
366        err_y = numpy.zeros(nbins)
367        y_counts = numpy.zeros(nbins)
368       
369        for i in range(len(data2D.data)):
370            dx = pixel_width*(i+0.5 - center_x)
371           
372            # Min and max x-value for the pixel
373            minx = pixel_width*(i - center_x)
374            maxx = pixel_width*(i+1.0 - center_x)
375           
376            for j in range(len(data2D.data)):
377                dy = pixel_width*(j+0.5 - center_y)
378           
379                q_value = get_q(dx, dy, det_dist, wavelength)
380           
381                # Min and max y-value for the pixel
382                miny = pixel_width*(j - center_y)
383                maxy = pixel_width*(j+1.0 - center_y)
384               
385                # Calculate the q-value for each corner
386                # q_[x min or max][y min or max]
387                q_00 = get_q(minx, miny, det_dist, wavelength)
388                q_01 = get_q(minx, maxy, det_dist, wavelength)
389                q_10 = get_q(maxx, miny, det_dist, wavelength)
390                q_11 = get_q(maxx, maxy, det_dist, wavelength)
391               
392                # Look for intercept between each side of the pixel
393                # and the constant-q ring for qmax
394                frac_max = get_pixel_fraction(self.r_max, q_00, q_01, q_10, q_11)
395               
396                # Look for intercept between each side of the pixel
397                # and the constant-q ring for qmin
398                frac_min = get_pixel_fraction(self.r_min, q_00, q_01, q_10, q_11)
399               
400                # We are interested in the region between qmin and qmax
401                # therefore the fraction of the surface of the pixel
402                # that we will use to calculate the number of counts to
403                # include is given by:
404                frac = frac_max - frac_min
405
406                i_q = int(math.ceil((q_value-self.r_min)/self.bin_width)) - 1
407           
408                x[i_q]          = q_value
409                y[i_q]         += frac * data2D.data[j][i]
410                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
411                    err_y[i_q] += frac * frac * math.fabs(data2D.data[j][i])
412                else:
413                    err_y[i_q] += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
414                y_counts[i_q]  += frac
415       
416        # Average the sums
417        for i in range(nbins):
418            if y_counts[i]>0:
419                err_y[i] = math.sqrt(err_y[i])/y_counts[i]
420                y[i]     = y[i]/y_counts[i]
421       
422        return Data1D(x=x, y=y, dy=err_y)
423   
424
425class Ring(object):
426    """
427        Defines a ring on a 2D data set.
428        The ring is defined by r_min, r_max, and
429        the position of the center of the ring.
430       
431        The data returned is the distribution of counts
432        around the ring as a function of phi.
433       
434    """
435   
436    def __init__(self, r_min=0, r_max=0, center_x=0, center_y=0):
437        # Minimum radius
438        self.r_min = r_min
439        # Maximum radius
440        self.r_max = r_max
441        # Center of the ring in x
442        self.center_x = center_x
443        # Center of the ring in y
444        self.center_y = center_y
445        # Number of angular bins
446        self.nbins_phi = 20
447       
448    def __call__(self, data2D):
449        """
450            Apply the ring to the data set.
451            Returns the angular distribution for a given q range
452           
453            @param data2D: Data2D object
454            @return: Data1D object
455        """
456        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
457            raise RuntimeError, "Ring averaging only take plottable_2D objects"
458       
459        data = data2D.data
460        qmin = self.r_min
461        qmax = self.r_max
462       
463        if len(data2D.detector) != 1:
464            raise RuntimeError, "Ring averaging: invalid number of detectors: %g" % len(data2D.detector)
465        pixel_width = data2D.detector[0].pixel_size.x
466        det_dist = data2D.detector[0].distance
467        wavelength = data2D.source.wavelength
468        center_x = self.center_x/pixel_width
469        center_y = self.center_y/pixel_width
470       
471        phi_bins   = numpy.zeros(self.nbins_phi)
472        phi_counts = numpy.zeros(self.nbins_phi)
473        phi_values = numpy.zeros(self.nbins_phi)
474        phi_err    = numpy.zeros(self.nbins_phi)
475       
476        for i in range(len(data)):
477            dx = pixel_width*(i+0.5 - center_x)
478           
479            # Min and max x-value for the pixel
480            minx = pixel_width*(i - center_x)
481            maxx = pixel_width*(i+1.0 - center_x)
482           
483            for j in range(len(data)):
484                dy = pixel_width*(j+0.5 - center_y)
485           
486                q_value = get_q(dx, dy, det_dist, wavelength)
487           
488                # Min and max y-value for the pixel
489                miny = pixel_width*(j - center_y)
490                maxy = pixel_width*(j+1.0 - center_y)
491               
492                # Calculate the q-value for each corner
493                # q_[x min or max][y min or max]
494                q_00 = get_q(minx, miny, det_dist, wavelength)
495                q_01 = get_q(minx, maxy, det_dist, wavelength)
496                q_10 = get_q(maxx, miny, det_dist, wavelength)
497                q_11 = get_q(maxx, maxy, det_dist, wavelength)
498               
499                # Look for intercept between each side of the pixel
500                # and the constant-q ring for qmax
501                frac_max = get_pixel_fraction(qmax, q_00, q_01, q_10, q_11)
502               
503                # Look for intercept between each side of the pixel
504                # and the constant-q ring for qmin
505                frac_min = get_pixel_fraction(qmin, q_00, q_01, q_10, q_11)
506               
507                # We are interested in the region between qmin and qmax
508                # therefore the fraction of the surface of the pixel
509                # that we will use to calculate the number of counts to
510                # include is given by:
511               
512                frac = frac_max - frac_min
513
514                i_phi = int(math.ceil(self.nbins_phi*(math.atan2(dy, dx)+math.pi)/(2.0*math.pi)) - 1)
515           
516                phi_bins[i_phi] += frac * data[j][i]
517               
518                if data2D.err_data == None or data2D.err_data[j][i]==0.0:
519                    phi_err[i_phi] += frac * frac * math.fabs(data2D.data[j][i])
520                else:
521                    phi_err[i_phi] += frac * frac * data2D.err_data[j][i] * data2D.err_data[j][i]
522                phi_counts[i_phi] += frac
523       
524        for i in range(self.nbins_phi):
525            phi_bins[i] = phi_bins[i] / phi_counts[i]
526            phi_err[i] = math.sqrt(phi_err[i]) / phi_counts[i]
527            phi_values[i] = 2.0*math.pi/self.nbins_phi*(1.0*i + 0.5)
528           
529        return Data1D(x=phi_values, y=phi_bins, dy=phi_err)
530   
531def get_pixel_fraction(qmax, q_00, q_01, q_10, q_11):
532    """
533        Returns the fraction of the pixel defined by
534        the four corners (q_00, q_01, q_10, q_11) that
535        has q < qmax.
536       
537                q_01                q_11
538        y=1         +--------------+
539                    |              |
540                    |              |
541                    |              |
542        y=0         +--------------+
543                q_00                q_01
544       
545                    x=0            x=1
546       
547    """
548   
549    # y side for x = minx
550    x_0 = get_intercept(qmax, q_00, q_01)
551    # y side for x = maxx
552    x_1 = get_intercept(qmax, q_10, q_11)
553   
554    # x side for y = miny
555    y_0 = get_intercept(qmax, q_00, q_10)
556    # x side for y = maxy
557    y_1 = get_intercept(qmax, q_01, q_11)
558   
559    # surface fraction for a 1x1 pixel
560    frac_max = 0
561   
562    if x_0 and x_1:
563        frac_max = (x_0+x_1)/2.0
564   
565    elif y_0 and y_1:
566        frac_max = (y_0+y_1)/2.0
567   
568    elif x_0 and y_0:
569        if q_00 < q_10:
570            frac_max = x_0*y_0/2.0
571        else:
572            frac_max = 1.0-x_0*y_0/2.0
573   
574    elif x_0 and y_1:
575        if q_00 < q_10:
576            frac_max = x_0*y_1/2.0
577        else:
578            frac_max = 1.0-x_0*y_1/2.0
579   
580    elif x_1 and y_0:
581        if q_00 > q_10:
582            frac_max = x_1*y_0/2.0
583        else:
584            frac_max = 1.0-x_1*y_0/2.0
585   
586    elif x_1 and y_1:
587        if q_00 < q_10:
588            frac_max = 1.0 - (1.0-x_1)*(1.0-y_1)/2.0
589        else:
590            frac_max = (1.0-x_1)*(1.0-y_1)/2.0
591           
592    # If we make it here, there is no intercept between
593    # this pixel and the constant-q ring. We only need
594    # to know if we have to include it or exclude it.
595    elif (q_00+q_01+q_10+q_11)/4.0 < qmax:
596        frac_max = 1.0
597   
598    return frac_max
599             
600def get_intercept(q, q_0, q_1):
601    """
602        Returns the fraction of the side at which the
603        q-value intercept the pixel, None otherwise.
604        The values returned is the fraction ON THE SIDE
605        OF THE LOWEST Q.
606       
607       
608       
609                A        B   
610         +-----------+--------+
611         0                    1     <--- pixel size
612         
613        Q_0 -------- Q ----- Q_1    <--- equivalent Q range
614       
615       
616        if Q_1 > Q_0, A is returned
617        if Q_1 < Q_0, B is returned
618       
619        if Q is outside the range of [Q_0, Q_1], None is returned
620         
621    """
622    if q_1 > q_0:
623        if (q > q_0 and q <= q_1):
624            return (q-q_0)/(q_1 - q_0)   
625    else:
626        if (q > q_1 and q <= q_0):
627            return (q-q_1)/(q_0 - q_1)
628    return None
629   
630
631class Sector:
632    """
633        Defines a sector region on a 2D data set.
634        The sector is defined by r_min, r_max, phi_min, phi_max,
635        and the position of the center of the ring.
636    """
637    pass
638
639if __name__ == "__main__": 
640
641    from loader import Loader
642   
643
644    d = Loader().load('test/MAR07232_rest.ASC')
645    #d = Loader().load('test/MP_New.sans')
646
647   
648    #r = Boxsum(x_min=.2, x_max=.4, y_min=0.2, y_max=0.4)
649    r = SlabX(x_min=-.01, x_max=.01, y_min=-0.0002, y_max=0.0002, bin_width=0.0004)
650    r.fold = False
651    o = r(d)
652   
653    #s = SlabY(x_min=-.01, x_max=.01, y_min=-0.0002, y_max=0.0002, bin_width=0.0004)
654    #s.fold = False
655    #p = s(d)
656   
657    for i in range(len(o.x)):
658        print o.x[i], o.y[i], o.dy[i]
659   
660 
661   
Note: See TracBrowser for help on using the repository browser.