source: sasmodels/sasmodels/data.py @ cf404cb

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since cf404cb was 9404dd3, checked in by Paul Kienzle <pkienzle@…>, 8 years ago

python 3.x support

  • Property mode set to 100644
File size: 12.1 KB
Line 
1"""
2SAS data representations.
3
4Plotting functions for data sets:
5
6    :func:`plot_data` plots the data file.
7
8    :func:`plot_theory` plots a calculated result from the model.
9
10Wrappers for the sasview data loader and data manipulations:
11
12    :func:`load_data` loads a sasview data file.
13
14    :func:`set_beam_stop` masks the beam stop from the data.
15
16    :func:`set_half` selects the right or left half of the data, which can
17    be useful for shear measurements which have not been properly corrected
18    for path length and reflections.
19
20    :func:`set_top` cuts the top part off the data.
21
22
23Empty data sets for evaluating models without data:
24
25    :func:`empty_data1D` creates an empty dataset, which is useful for plotting
26    a theory function before the data is measured.
27
28    :func:`empty_data2D` creates an empty 2D dataset.
29
30Note that the empty datasets use a minimal representation of the SasView
31objects so that models can be run without SasView on the path.  You could
32also use these for your own data loader.
33
34"""
35import traceback
36
37import numpy as np
38
39def load_data(filename):
40    """
41    Load data using a sasview loader.
42    """
43    from sas.dataloader.loader import Loader
44    loader = Loader()
45    data = loader.load(filename)
46    if data is None:
47        raise IOError("Data %r could not be loaded" % filename)
48    return data
49
50
51def set_beam_stop(data, radius, outer=None):
52    """
53    Add a beam stop of the given *radius*.  If *outer*, make an annulus.
54    """
55    from sas.dataloader.manipulations import Ringcut
56    if hasattr(data, 'qx_data'):
57        data.mask = Ringcut(0, radius)(data)
58        if outer is not None:
59            data.mask += Ringcut(outer, np.inf)(data)
60    else:
61        data.mask = (data.x < radius)
62        if outer is not None:
63            data.mask |= (data.x >= outer)
64
65
66def set_half(data, half):
67    """
68    Select half of the data, either "right" or "left".
69    """
70    from sas.dataloader.manipulations import Boxcut
71    if half == 'right':
72        data.mask += \
73            Boxcut(x_min=-np.inf, x_max=0.0, y_min=-np.inf, y_max=np.inf)(data)
74    if half == 'left':
75        data.mask += \
76            Boxcut(x_min=0.0, x_max=np.inf, y_min=-np.inf, y_max=np.inf)(data)
77
78
79def set_top(data, cutoff):
80    """
81    Chop the top off the data, above *cutoff*.
82    """
83    from sas.dataloader.manipulations import Boxcut
84    data.mask += \
85        Boxcut(x_min=-np.inf, x_max=np.inf, y_min=-np.inf, y_max=cutoff)(data)
86
87
88class Data1D(object):
89    def __init__(self, x=None, y=None, dx=None, dy=None):
90        self.x, self.y, self.dx, self.dy = x, y, dx, dy
91        self.dxl = None
92
93    def xaxis(self, label, unit):
94        """
95        set the x axis label and unit
96        """
97        self._xaxis = label
98        self._xunit = unit
99
100    def yaxis(self, label, unit):
101        """
102        set the y axis label and unit
103        """
104        self._yaxis = label
105        self._yunit = unit
106
107
108
109class Data2D(object):
110    def __init__(self):
111        self.detector = []
112        self.source = Source()
113
114    def xaxis(self, label, unit):
115        """
116        set the x axis label and unit
117        """
118        self._xaxis = label
119        self._xunit = unit
120
121    def yaxis(self, label, unit):
122        """
123        set the y axis label and unit
124        """
125        self._yaxis = label
126        self._yunit = unit
127
128    def zaxis(self, label, unit):
129        """
130        set the y axis label and unit
131        """
132        self._zaxis = label
133        self._zunit = unit
134
135
136class Vector(object):
137    def __init__(self, x=None, y=None, z=None):
138        self.x, self.y, self.z = x, y, z
139
140class Detector(object):
141    def __init__(self):
142        self.pixel_size = Vector()
143
144class Source(object):
145    pass
146
147
148def empty_data1D(q, resolution=0.05):
149    """
150    Create empty 1D data using the given *q* as the x value.
151
152    *resolution* dq/q defaults to 5%.
153    """
154
155    #Iq = 100 * np.ones_like(q)
156    #dIq = np.sqrt(Iq)
157    Iq, dIq = None, None
158    data = Data1D(q, Iq, dx=resolution * q, dy=dIq)
159    data.filename = "fake data"
160    data.qmin, data.qmax = q.min(), q.max()
161    data.mask = np.zeros(len(q), dtype='bool')
162    return data
163
164
165def empty_data2D(qx, qy=None, resolution=0.05):
166    """
167    Create empty 2D data using the given mesh.
168
169    If *qy* is missing, create a square mesh with *qy=qx*.
170
171    *resolution* dq/q defaults to 5%.
172    """
173    if qy is None:
174        qy = qx
175    Qx, Qy = np.meshgrid(qx, qy)
176    Qx, Qy = Qx.flatten(), Qy.flatten()
177    Iq = 100 * np.ones_like(Qx)
178    dIq = np.sqrt(Iq)
179    mask = np.ones(len(Iq), dtype='bool')
180
181    data = Data2D()
182    data.filename = "fake data"
183    data.qx_data = Qx
184    data.qy_data = Qy
185    data.data = Iq
186    data.err_data = dIq
187    data.mask = mask
188    data.qmin = 1e-16
189    data.qmax = np.inf
190
191    # 5% dQ/Q resolution
192    if resolution != 0:
193        # https://www.ncnr.nist.gov/staff/hammouda/distance_learning/chapter_15.pdf
194        # Should have an additional constant which depends on distances and
195        # radii of the aperture, pixel dimensions and wavelength spread
196        # Instead, assume radial dQ/Q is constant, and perpendicular matches
197        # radial (which instead it should be inverse).
198        Q = np.sqrt(Qx**2 + Qy**2)
199        data.dqx_data = resolution * Q
200        data.dqy_data = resolution * Q
201    else:
202        data.dqx_data = data.dqy_data = None
203
204    detector = Detector()
205    detector.pixel_size.x = 5 # mm
206    detector.pixel_size.y = 5 # mm
207    detector.distance = 4 # m
208    data.detector.append(detector)
209    data.xbins = qx
210    data.ybins = qy
211    data.source.wavelength = 5 # angstroms
212    data.source.wavelength_unit = "A"
213    data.Q_unit = "1/A"
214    data.I_unit = "1/cm"
215    data.q_data = np.sqrt(Qx ** 2 + Qy ** 2)
216    data.xaxis("Q_x", "A^{-1}")
217    data.yaxis("Q_y", "A^{-1}")
218    data.zaxis("Intensity", r"\text{cm}^{-1}")
219    return data
220
221
222def plot_data(data, view='log'):
223    """
224    Plot data loaded by the sasview loader.
225    """
226    # Note: kind of weird using the plot result functions to plot just the
227    # data, but they already handle the masking and graph markup already, so
228    # do not repeat.
229    if hasattr(data, 'lam'):
230        _plot_result_sesans(data, None, None, plot_data=True)
231    elif hasattr(data, 'qx_data'):
232        _plot_result2D(data, None, None, view, plot_data=True)
233    else:
234        _plot_result1D(data, None, None, view, plot_data=True)
235
236
237def plot_theory(data, theory, resid=None, view='log', plot_data=True):
238    if hasattr(data, 'lam'):
239        _plot_result_sesans(data, theory, resid, plot_data=True)
240    elif hasattr(data, 'qx_data'):
241        _plot_result2D(data, theory, resid, view, plot_data)
242    else:
243        _plot_result1D(data, theory, resid, view, plot_data)
244
245
246def protect(fn):
247    def wrapper(*args, **kw):
248        try:
249            return fn(*args, **kw)
250        except:
251            traceback.print_exc()
252            pass
253
254    return wrapper
255
256
257@protect
258def _plot_result1D(data, theory, resid, view, plot_data):
259    """
260    Plot the data and residuals for 1D data.
261    """
262    import matplotlib.pyplot as plt
263    from numpy.ma import masked_array, masked
264
265    plot_theory = theory is not None
266    plot_resid = resid is not None
267
268    if data.y is None:
269        plot_data = False
270    scale = data.x**4 if view == 'q4' else 1.0
271
272    if plot_data or plot_theory:
273        if plot_resid:
274            plt.subplot(121)
275
276        #print(vmin, vmax)
277        positive = False
278        if plot_data:
279            mdata = masked_array(data.y, data.mask)
280            mdata[~np.isfinite(mdata)] = masked
281            if view is 'log':
282                mdata[mdata <= 0] = masked
283            plt.errorbar(data.x, scale*mdata, yerr=data.dy, fmt='.')
284            positive = positive or (mdata>0).any()
285
286        if plot_theory:
287            mtheory = masked_array(theory, data.mask)
288            if view is 'log':
289                mtheory[mtheory<= 0] = masked
290            plt.plot(data.x, scale*mtheory, '-', hold=True)
291            positive = positive or (mtheory>0).any()
292
293        plt.xscale(view)
294        plt.yscale('linear' if view == 'q4' or not positive else view)
295        plt.xlabel('Q')
296        plt.ylabel('I(Q)')
297
298    if plot_resid:
299        if plot_data or plot_theory:
300            plt.subplot(122)
301
302        mresid = masked_array(resid, data.mask)
303        plt.plot(data.x, mresid, '-')
304        plt.ylabel('residuals')
305        plt.xlabel('Q')
306        plt.xscale(view)
307
308
309@protect
310def _plot_result_sesans(data, theory, resid, plot_data):
311    import matplotlib.pyplot as plt
312    if data.y is None:
313        plot_data = False
314    plot_theory = theory is not None
315    plot_resid = resid is not None
316
317    if plot_data or plot_theory:
318        if plot_resid:
319            plt.subplot(121)
320        if plot_data:
321            plt.errorbar(data.x, data.y, yerr=data.dy)
322        if theory is not None:
323            plt.plot(data.x, theory, '-', hold=True)
324        plt.xlabel('spin echo length (nm)')
325        plt.ylabel('polarization (P/P0)')
326
327    if resid is not None:
328        if plot_data or plot_theory:
329            plt.subplot(122)
330
331        plt.plot(data.x, resid, 'x')
332        plt.xlabel('spin echo length (nm)')
333        plt.ylabel('residuals (P/P0)')
334
335
336@protect
337def _plot_result2D(data, theory, resid, view, plot_data):
338    """
339    Plot the data and residuals for 2D data.
340    """
341    import matplotlib.pyplot as plt
342    if data.data is None:
343        plot_data = False
344    plot_theory = theory is not None
345    plot_resid = resid is not None
346
347    # Put theory and data on a common colormap scale
348    vmin, vmax = np.inf, -np.inf
349    if plot_data:
350        target = data.data[~data.mask]
351        datamin = target[target>0].min() if view == 'log' else target.min()
352        datamax = target.max()
353        vmin = min(vmin, datamin)
354        vmax = max(vmax, datamax)
355    if plot_theory:
356        theorymin = theory[theory>0].min() if view == 'log' else theory.min()
357        theorymax = theory.max()
358        vmin = min(vmin, theorymin)
359        vmax = max(vmax, theorymax)
360
361    if plot_data:
362        if plot_theory and plot_resid:
363            plt.subplot(131)
364        elif plot_theory or plot_resid:
365            plt.subplot(121)
366        _plot_2d_signal(data, target, view=view, vmin=vmin, vmax=vmax)
367        plt.title('data')
368        plt.colorbar()
369
370    if plot_theory:
371        if plot_data and plot_resid:
372            plt.subplot(132)
373        elif plot_data:
374            plt.subplot(122)
375        elif plot_resid:
376            plt.subplot(121)
377        _plot_2d_signal(data, theory, view=view, vmin=vmin, vmax=vmax)
378        plt.title('theory')
379        plt.colorbar()
380
381    #if plot_data or plot_theory:
382    #    plt.colorbar()
383
384    if plot_resid:
385        if plot_data and plot_theory:
386            plt.subplot(133)
387        elif plot_data or plot_theory:
388            plt.subplot(122)
389        _plot_2d_signal(data, resid, view='linear')
390        plt.colorbar()
391        plt.title('residuals')
392
393
394@protect
395def _plot_2d_signal(data, signal, vmin=None, vmax=None, view='log'):
396    """
397    Plot the target value for the data.  This could be the data itself,
398    the theory calculation, or the residuals.
399
400    *scale* can be 'log' for log scale data, or 'linear'.
401    """
402    import matplotlib.pyplot as plt
403    from numpy.ma import masked_array
404
405    image = np.zeros_like(data.qx_data)
406    image[~data.mask] = signal
407    valid = np.isfinite(image)
408    if view == 'log':
409        valid[valid] = (image[valid] > 0)
410        image[valid] = np.log10(image[valid])
411    elif view == 'q4':
412        image[valid] *= (data.qx_data[valid]**2+data.qy_data[valid]**2)**2
413    image[~valid | data.mask] = 0
414    #plottable = Iq
415    plottable = masked_array(image, ~valid | data.mask)
416    xmin, xmax = min(data.qx_data), max(data.qx_data)
417    ymin, ymax = min(data.qy_data), max(data.qy_data)
418    # TODO: fix vmin, vmax so it is shared for theory/resid
419    vmin = vmax = None
420    try:
421        if vmin is None: vmin = image[valid & ~data.mask].min()
422        if vmax is None: vmax = image[valid & ~data.mask].max()
423    except:
424        vmin, vmax = 0, 1
425    plt.imshow(plottable.reshape(128, 128),
426               interpolation='nearest', aspect=1, origin='upper',
427               extent=[xmin, xmax, ymin, ymax], vmin=vmin, vmax=vmax)
428
429
430def demo():
431    data = load_data('DEC07086.DAT')
432    set_beam_stop(data, 0.004)
433    plot_data(data)
434    import matplotlib.pyplot as plt; plt.show()
435
436
437if __name__ == "__main__":
438    demo()
Note: See TracBrowser for help on using the repository browser.