1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
13 # Public License for more details.
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/>.
19 """The `curve` module provides :class:`Curve` and :class:`Data` for
27 class NotRecognized (ValueError):
28 def __init__(self, curve):
29 self.__setstate__(curve)
31 def __getstate__(self):
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)
40 class Data (numpy.ndarray):
41 """Stores a single, continuous data set.
43 Adds :attr:`info` :class:`dict` to the standard :class:`numpy.ndarray`.
45 See :mod:`numpy.doc.subclassing` for the peculiarities of
46 subclassing :class:`numpy.ndarray`.
51 >>> d = Data(shape=(3,2), info={'columns':['distance (m)', 'force (N)']})
53 <class 'hooke.curve.Data'>
54 >>> for i in range(3): # initialize d
55 ... for j in range(2):
62 {'columns': ['distance (m)', 'force (N)']}
64 The information gets passed on to slices.
70 {'columns': ['distance (m)', 'force (N)']}
72 The data-type is also pickleable, to ensure we can move it between
73 processes with :class:`multiprocessing.Queue`\s.
76 >>> s = pickle.dumps(d)
77 >>> z = pickle.loads(s)
83 {'columns': ['distance (m)', 'force (N)']}
85 def __new__(subtype, shape, dtype=numpy.float, buffer=None, offset=0,
86 strides=None, order=None, info=None):
87 """Create the ndarray instance of our type, given the usual
88 input arguments. This will call the standard ndarray
89 constructor, but return an object of our type.
91 obj = numpy.ndarray.__new__(
92 subtype, shape, dtype, buffer, offset, strides, order)
93 # add the new attribute to the created instance
97 # Finally, we must return the newly created object:
100 def __array_finalize__(self, obj):
101 """Set any extra attributes from the original object when
102 creating a new view object."""
103 # reset the attribute from passed original object
104 self.info = getattr(obj, 'info', {})
105 # We do not need to return anything
107 def __reduce__(self):
108 """Collapse an instance for pickling.
112 reconstruct : callable
113 Called to create the initial version of the object.
115 A tuple of arguments for `reconstruct`
117 The state to be passed to __setstate__, if present.
118 iter : iterator (optional)
119 Yielded items will be appended to the reconstructed
121 dict : iterator (optional)
122 Yielded (key,value) tuples pushed back onto the
123 reconstructed object.
125 base_reduce = list(numpy.ndarray.__reduce__(self))
126 # tack our stuff onto ndarray's setstate portion.
127 base_reduce[2] = (base_reduce[2], (self.info,))
128 return tuple(base_reduce)
130 def __setstate__(self, state):
131 base_class_state,own_state = state
132 numpy.ndarray.__setstate__(self, base_class_state)
133 self.info, = own_state
136 class Curve (object):
137 """A grouped set of :class:`Data` runs from the same file with metadata.
139 For an approach/retract force spectroscopy experiment, the group
140 would consist of the approach data and the retract data. Metadata
141 would be the temperature, cantilever spring constant, etc.
143 Two important :attr:`info` settings are `filetype` and
144 `experiment`. These are two strings that can be used by Hooke
145 commands/plugins to understand what they are looking at.
147 * `.info['filetype']` should contain the name of the exact
148 filetype defined by the driver (so that filetype-speciofic
149 commands can know if they're dealing with the correct filetype).
150 * `.info['experiment']` should contain an instance of a
151 :class:`hooke.experiment.Experiment` subclass to identify the
152 experiment type. For example, various
153 :class:`hooke.driver.Driver`\s can read in force-clamp data, but
154 Hooke commands could like to know if they're looking at force
155 clamp data, regardless of their origin.
157 def __init__(self, path, info=None):
158 #the data dictionary contains: {name of data: list of data sets [{[x], [y]}]
165 self.name = os.path.basename(path)
167 def identify(self, drivers):
168 """Identify the appropriate :class:`hooke.driver.Driver` for
169 the curve file (`.path`).
171 if 'filetype' in self.info:
172 driver = [d for d in drivers if d.name == self.info['filetype']]
175 if driver.is_me(self.path):
178 for driver in drivers:
179 if driver.is_me(self.path):
180 self.driver = driver # remember the working driver
182 raise NotRecognized(self)
185 """Use the driver to read the curve into memory.
187 data,info = self.driver.read(self.path, self.info)
189 for key,value in info.items():
190 self.info[key] = value
193 """Release memory intensive :attr:`.data`.