Merged my unitary FFT wrappers (FFT_tools) as hooke.util.fft.
[hooke.git] / hooke / curve.py
1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of Hooke.
4 #
5 # Hooke is free software: you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation, either
8 # version 3 of the License, or (at your option) any later version.
9 #
10 # Hooke is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with Hooke.  If not, see
17 # <http://www.gnu.org/licenses/>.
18
19 """The `curve` module provides :class:`Curve` and :class:`Data` for
20 storing force curves.
21 """
22
23 import os.path
24 import numpy
25
26
27 class NotRecognized (ValueError):
28     def __init__(self, curve):
29         self.__setstate__(curve)
30
31     def __getstate__(self):
32         return self.curve
33
34     def __setstate__(self, data):
35         if isinstance(data, Curve):
36             msg = 'Not a recognizable curve format: %s' % data.path
37             super(NotRecognized, self).__init__(msg)
38             self.curve = data
39
40 class Data (numpy.ndarray):
41     """Stores a single, continuous data set.
42
43     Adds :attr:`info` :class:`dict` to the standard :class:`numpy.ndarray`.
44
45     See :mod:`numpy.doc.subclassing` for the peculiarities of
46     subclassing :class:`numpy.ndarray`.
47
48     Examples
49     --------
50
51     >>> d = Data(shape=(3,2), info={'columns':['distance (m)', 'force (N)']})
52     >>> type(d)
53     <class 'hooke.curve.Data'>
54     >>> for i in range(3): # initialize d
55     ...    for j in range(2):
56     ...        d[i,j] = i*10 + j
57     >>> d
58     Data([[  0.,   1.],
59            [ 10.,  11.],
60            [ 20.,  21.]])
61     >>> d.info
62     {'columns': ['distance (m)', 'force (N)']}
63     >>> row_a = d[:,0]
64     >>> row_a
65     Data([  0.,  10.,  20.])
66     >>> row_a.info
67     {'columns': ['distance (m)', 'force (N)']}
68     """
69     def __new__(subtype, shape, dtype=numpy.float, buffer=None, offset=0,
70                 strides=None, order=None, info=None):
71         """Create the ndarray instance of our type, given the usual
72         input arguments.  This will call the standard ndarray
73         constructor, but return an object of our type.
74         """
75         obj = numpy.ndarray.__new__(
76             subtype, shape, dtype, buffer, offset, strides, order)
77         # add the new attribute to the created instance
78         if info == None:
79             info = {}
80         obj.info = info
81         # Finally, we must return the newly created object:
82         return obj
83
84     def __array_finalize__(self, obj):
85         """Set any extra attributes from the original object when
86         creating a new view object."""
87         # reset the attribute from passed original object
88         self.info = getattr(obj, 'info', {})
89         # We do not need to return anything
90
91
92 class Curve (object):
93     """A grouped set of :class:`Data` runs from the same file with metadata.
94
95     For an approach/retract force spectroscopy experiment, the group
96     would consist of the approach data and the retract data.  Metadata
97     would be the temperature, cantilever spring constant, etc.
98
99     Two important :attr:`info` settings are `filetype` and
100     `experiment`.  These are two strings that can be used by Hooke
101     commands/plugins to understand what they are looking at.
102
103     * `.info['filetype']` should contain the name of the exact
104       filetype defined by the driver (so that filetype-speciofic
105       commands can know if they're dealing with the correct filetype).
106     * `.info['experiment']` should contain an instance of a
107       :class:`hooke.experiment.Experiment` subclass to identify the
108       experiment type.  For example, various
109       :class:`hooke.driver.Driver`\s can read in force-clamp data, but
110       Hooke commands could like to know if they're looking at force
111       clamp data, regardless of their origin.
112     """
113     def __init__(self, path, info=None):
114         #the data dictionary contains: {name of data: list of data sets [{[x], [y]}]
115         self.path = path
116         self.driver = None
117         self.data = None
118         if info == None:
119             info = {}
120         self.info = info
121         self.name = os.path.basename(path)
122
123     def identify(self, drivers):
124         """Identify the appropriate :class:`hooke.driver.Driver` for
125         the curve file (`.path`).
126         """
127         for driver in drivers:
128             if driver.is_me(self.path):
129                 self.driver = driver # remember the working driver
130                 return
131         raise NotRecognized(self)
132
133     def load(self):
134         """Use the driver to read the curve into memory.
135         """
136         data,info = self.driver.read(self.path)
137         self.data = data
138         for key,value in info.items():
139             self.info[key] = value
140
141     def unload(self):
142         """Release memory intensive :attr:`.data`.
143         """
144         self.data = None