source: sasmodels/sasmodels/data.py @ 3b4243d

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

split data units from bumps model

  • 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
202    detector = Detector()
203    detector.pixel_size.x = 5 # mm
204    detector.pixel_size.y = 5 # mm
205    detector.distance = 4 # m
206    data.detector.append(detector)
207    data.xbins = qx
208    data.ybins = qy
209    data.source.wavelength = 5 # angstroms
210    data.source.wavelength_unit = "A"
211    data.Q_unit = "1/A"
212    data.I_unit = "1/cm"
213    data.q_data = np.sqrt(Qx ** 2 + Qy ** 2)
214    data.xaxis("Q_x", "A^{-1}")
215    data.yaxis("Q_y", "A^{-1}")
216    data.zaxis("Intensity", r"\text{cm}^{-1}")
217    return data
218
219
220def plot_data(data, view='log'):
221    """
222    Plot data loaded by the sasview loader.
223    """
224    # Note: kind of weird using the plot result functions to plot just the
225    # data, but they already handle the masking and graph markup already, so
226    # do not repeat.
227    if hasattr(data, 'lam'):
228        _plot_result_sesans(data, None, None, plot_data=True)
229    elif hasattr(data, 'qx_data'):
230        _plot_result2D(data, None, None, view, plot_data=True)
231    else:
232        _plot_result1D(data, None, None, view, plot_data=True)
233
234
235def plot_theory(data, theory, resid=None, view='log', plot_data=True):
236    if hasattr(data, 'lam'):
237        _plot_result_sesans(data, theory, resid, plot_data=True)
238    elif hasattr(data, 'qx_data'):
239        _plot_result2D(data, theory, resid, view, plot_data)
240    else:
241        _plot_result1D(data, theory, resid, view, plot_data)
242
243
244def protect(fn):
245    def wrapper(*args, **kw):
246        try:
247            return fn(*args, **kw)
248        except:
249            traceback.print_exc()
250            pass
251
252    return wrapper
253
254
255@protect
256def _plot_result1D(data, theory, resid, view, plot_data):
257    """
258    Plot the data and residuals for 1D data.
259    """
260    import matplotlib.pyplot as plt
261    from numpy.ma import masked_array, masked
262
263    plot_theory = theory is not None
264    plot_resid = resid is not None
265
266    if data.y is None:
267        plot_data = False
268    scale = data.x**4 if view == 'q4' else 1.0
269
270    if plot_data or plot_theory:
271        if plot_resid:
272            plt.subplot(121)
273
274        #print vmin, vmax
275        positive = False
276        if plot_data:
277            mdata = masked_array(data.y, data.mask)
278            mdata[~np.isfinite(mdata)] = masked
279            if view is 'log':
280                mdata[mdata <= 0] = masked
281            plt.errorbar(data.x, scale*mdata, yerr=data.dy, fmt='.')
282            positive = positive or (mdata>0).any()
283
284        if plot_theory:
285            mtheory = masked_array(theory, data.mask)
286            if view is 'log':
287                mtheory[mtheory<= 0] = masked
288            plt.plot(data.x, scale*mtheory, '-', hold=True)
289            positive = positive or (mtheory>0).any()
290
291        plt.xscale(view)
292        plt.yscale('linear' if view == 'q4' or not positive else view)
293        plt.xlabel('Q')
294        plt.ylabel('I(Q)')
295
296    if plot_resid:
297        if plot_data or plot_theory:
298            plt.subplot(122)
299
300        mresid = masked_array(resid, data.mask)
301        plt.plot(data.x, mresid, '-')
302        plt.ylabel('residuals')
303        plt.xlabel('Q')
304        plt.xscale(view)
305
306
307@protect
308def _plot_result_sesans(data, theory, resid, plot_data):
309    import matplotlib.pyplot as plt
310    if data.y is None:
311        plot_data = False
312    plot_theory = theory is not None
313    plot_resid = resid is not None
314
315    if plot_data or plot_theory:
316        if plot_resid:
317            plt.subplot(121)
318        if plot_data:
319            plt.errorbar(data.x, data.y, yerr=data.dy)
320        if theory is not None:
321            plt.plot(data.x, theory, '-', hold=True)
322        plt.xlabel('spin echo length (nm)')
323        plt.ylabel('polarization (P/P0)')
324
325    if resid is not None:
326        if plot_data or plot_theory:
327            plt.subplot(122)
328
329        plt.plot(data.x, resid, 'x')
330        plt.xlabel('spin echo length (nm)')
331        plt.ylabel('residuals (P/P0)')
332
333
334@protect
335def _plot_result2D(data, theory, resid, view, plot_data):
336    """
337    Plot the data and residuals for 2D data.
338    """
339    import matplotlib.pyplot as plt
340    if data.data is None:
341        plot_data = False
342    plot_theory = theory is not None
343    plot_resid = resid is not None
344
345    # Put theory and data on a common colormap scale
346    vmin, vmax = np.inf, -np.inf
347    if plot_data:
348        target = data.data[~data.mask]
349        datamin = target[target>0].min() if view == 'log' else target.min()
350        datamax = target.max()
351        vmin = min(vmin, datamin)
352        vmax = max(vmax, datamax)
353    if plot_theory:
354        theorymin = theory[theory>0].min() if view == 'log' else theory.min()
355        theorymax = theory.max()
356        vmin = min(vmin, theorymin)
357        vmax = max(vmax, theorymax)
358
359    if plot_data:
360        if plot_theory and plot_resid:
361            plt.subplot(131)
362        elif plot_theory or plot_resid:
363            plt.subplot(121)
364        _plot_2d_signal(data, target, view=view, vmin=vmin, vmax=vmax)
365        plt.title('data')
366        plt.colorbar()
367
368    if plot_theory:
369        if plot_data and plot_resid:
370            plt.subplot(132)
371        elif plot_data:
372            plt.subplot(122)
373        elif plot_resid:
374            plt.subplot(121)
375        _plot_2d_signal(data, theory, view=view, vmin=vmin, vmax=vmax)
376        plt.title('theory')
377        plt.colorbar()
378
379    #if plot_data or plot_theory:
380    #    plt.colorbar()
381
382    if plot_resid:
383        if plot_data and plot_theory:
384            plt.subplot(133)
385        elif plot_data or plot_theory:
386            plt.subplot(122)
387        _plot_2d_signal(data, resid, view='linear')
388        plt.colorbar()
389        plt.title('residuals')
390
391
392@protect
393def _plot_2d_signal(data, signal, vmin=None, vmax=None, view='log'):
394    """
395    Plot the target value for the data.  This could be the data itself,
396    the theory calculation, or the residuals.
397
398    *scale* can be 'log' for log scale data, or 'linear'.
399    """
400    import matplotlib.pyplot as plt
401    from numpy.ma import masked_array
402
403    image = np.zeros_like(data.qx_data)
404    image[~data.mask] = signal
405    valid = np.isfinite(image)
406    if view == 'log':
407        valid[valid] = (image[valid] > 0)
408        image[valid] = np.log10(image[valid])
409    elif view == 'q4':
410        image[valid] *= (data.qx_data[valid]**2+data.qy_data[valid]**2)**2
411    image[~valid | data.mask] = 0
412    #plottable = Iq
413    plottable = masked_array(image, ~valid | data.mask)
414    xmin, xmax = min(data.qx_data), max(data.qx_data)
415    ymin, ymax = min(data.qy_data), max(data.qy_data)
416    # TODO: fix vmin, vmax so it is shared for theory/resid
417    vmin = vmax = None
418    try:
419        if vmin is None: vmin = image[valid & ~data.mask].min()
420        if vmax is None: vmax = image[valid & ~data.mask].max()
421    except:
422        vmin, vmax = 0, 1
423    plt.imshow(plottable.reshape(128, 128),
424               interpolation='nearest', aspect=1, origin='upper',
425               extent=[xmin, xmax, ymin, ymax], vmin=vmin, vmax=vmax)
426
427
428def demo():
429    data = load_data('DEC07086.DAT')
430    set_beam_stop(data, 0.004)
431    plot_data(data)
432    import matplotlib.pyplot as plt; plt.show()
433
434
435if __name__ == "__main__":
436    demo()
Note: See TracBrowser for help on using the repository browser.