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
28 from .command_stack import CommandStack
29 from . import experiment
32 class NotRecognized (ValueError):
33 def __init__(self, curve):
34 self.__setstate__(curve)
36 def __getstate__(self):
39 def __setstate__(self, data):
40 if isinstance(data, Curve):
41 msg = 'Not a recognizable curve format: %s' % data.path
42 super(NotRecognized, self).__init__(msg)
46 class Data (numpy.ndarray):
47 """Stores a single, continuous data set.
49 Adds :attr:`info` :class:`dict` to the standard :class:`numpy.ndarray`.
51 See :mod:`numpy.doc.subclassing` for the peculiarities of
52 subclassing :class:`numpy.ndarray`.
57 >>> d = Data(shape=(3,2), info={'columns':['distance (m)', 'force (N)']})
59 <class 'hooke.curve.Data'>
60 >>> for i in range(3): # initialize d
61 ... for j in range(2):
68 {'columns': ['distance (m)', 'force (N)']}
70 The information gets passed on to slices.
76 {'columns': ['distance (m)', 'force (N)']}
78 The data-type is also pickleable, to ensure we can move it between
79 processes with :class:`multiprocessing.Queue`\s.
82 >>> s = pickle.dumps(d)
83 >>> z = pickle.loads(s)
89 {'columns': ['distance (m)', 'force (N)']}
91 The data-type is also YAMLable (see :mod:`hooke.util.yaml`).
94 >>> print yaml.dump(d)
99 def __new__(subtype, shape, dtype=numpy.float, buffer=None, offset=0,
100 strides=None, order=None, info=None):
101 """Create the ndarray instance of our type, given the usual
102 input arguments. This will call the standard ndarray
103 constructor, but return an object of our type.
105 obj = numpy.ndarray.__new__(
106 subtype, shape, dtype, buffer, offset, strides, order)
107 # add the new attribute to the created instance
111 # Finally, we must return the newly created object:
114 def __array_finalize__(self, obj):
115 """Set any extra attributes from the original object when
116 creating a new view object."""
117 # reset the attribute from passed original object
118 self.info = getattr(obj, 'info', {})
119 # We do not need to return anything
121 def __reduce__(self):
122 """Collapse an instance for pickling.
126 reconstruct : callable
127 Called to create the initial version of the object.
129 A tuple of arguments for `reconstruct`
131 The state to be passed to __setstate__, if present.
132 iter : iterator (optional)
133 Yielded items will be appended to the reconstructed
135 dict : iterator (optional)
136 Yielded (key,value) tuples pushed back onto the
137 reconstructed object.
139 base_reduce = list(numpy.ndarray.__reduce__(self))
140 # tack our stuff onto ndarray's setstate portion.
141 base_reduce[2] = (base_reduce[2], (self.info,))
142 return tuple(base_reduce)
144 def __setstate__(self, state):
145 base_class_state,own_state = state
146 numpy.ndarray.__setstate__(self, base_class_state)
147 self.info, = own_state
150 class Curve (object):
151 """A grouped set of :class:`Data` runs from the same file with metadata.
153 For an approach/retract force spectroscopy experiment, the group
154 would consist of the approach data and the retract data. Metadata
155 would be the temperature, cantilever spring constant, etc.
157 Two important :attr:`info` settings are `filetype` and
158 `experiment`. These are two strings that can be used by Hooke
159 commands/plugins to understand what they are looking at.
161 * :attr:`info['filetype']` should contain the name of the exact
162 filetype defined by the driver (so that filetype-speciofic
163 commands can know if they're dealing with the correct filetype).
164 * :attr:`info['experiment']` should contain an instance of a
165 :class:`hooke.experiment.Experiment` subclass to identify the
166 experiment type. For example, various
167 :class:`hooke.driver.Driver`\s can read in force-clamp data, but
168 Hooke commands could like to know if they're looking at force
169 clamp data, regardless of their origin.
171 Another important attribute is :attr:`command_stack`, which holds
172 a :class:`~hooke.command_stack.CommandStack` listing the commands
173 that have been applied to the `Curve` since loading.
175 The data-type is pickleable, to ensure we can move it between
176 processes with :class:`multiprocessing.Queue`\s.
180 >>> from .engine import CommandMessage
181 >>> c = Curve(path='some/path')
183 We add a recursive reference to `c` as you would get from
184 :meth:`hooke.plugin.curve.CurveCommand._add_to_command_stack`.
186 >>> c.command_stack.append(CommandMessage('curve info', {'curve':c}))
188 >>> s = pickle.dumps(c)
189 >>> z = pickle.loads(s)
192 >>> z.command_stack[-1].arguments['curve'] == z
194 >>> print yaml.dump(c) # doctest: +REPORT_UDIFF
195 &id001 !!python/object:hooke.curve.Curve
196 command_stack: !!python/object/new:hooke.command_stack.CommandStack
198 - !!python/object:hooke.engine.CommandMessage
209 However, if we try and serialize the command stack first, we run
210 into `Python issue 1062277`_.
212 .. _Python issue 1062277: http://bugs.python.org/issue1062277
214 >>> pickle.dumps(c.command_stack)
215 Traceback (most recent call last):
217 assert id(obj) not in self.memo
220 YAML still works, though.
222 >>> print yaml.dump(c.command_stack) # doctest: +REPORT_UDIFF
223 &id001 !!python/object/new:hooke.command_stack.CommandStack
225 - !!python/object:hooke.engine.CommandMessage
227 curve: !!python/object:hooke.curve.Curve
228 command_stack: *id001
237 def __init__(self, path, info=None):
238 self.__setstate__({'path':path, 'info':info})
241 return str(self.__unicode__())
243 def __unicode__(self):
244 return u'<%s %s>' % (self.__class__.__name__, self.name)
247 return self.__str__()
249 def set_path(self, path):
251 if self.name == None and path != None:
252 self.name = os.path.basename(path)
254 def __getstate__(self):
255 state = dict(self.__dict__) # make a copy of the attribute dict.
259 def __setstate__(self, state):
260 # .data contains: {name of data: list of data sets [{[x], [y]}]
261 # ._hooke contains a Hooke instance for Curve.load()
262 self.name = self.driver = self.data = self._hooke = None
264 self.command_stack = CommandStack()
265 for key,value in state.items():
266 setattr(self, key, value)
267 if self.info == None:
269 self.set_path(state.get('path', None))
271 def set_hooke(self, hooke=None):
275 def identify(self, drivers):
276 """Identify the appropriate :class:`hooke.driver.Driver` for
277 the curve file (`.path`).
279 if 'filetype' in self.info:
280 driver = [d for d in drivers if d.name == self.info['filetype']]
283 if driver.is_me(self.path):
286 for driver in drivers:
287 if driver.is_me(self.path):
288 self.driver = driver # remember the working driver
290 raise NotRecognized(self)
292 def load(self, hooke=None):
293 """Use the driver to read the curve into memory.
295 Also runs any commands in :attr:`command_stack`. All
296 arguments are passed through to
297 :meth:`hooke.command_stack.CommandStack.execute`.
299 self.set_hooke(hooke)
300 log = logging.getLogger('hooke')
301 log.debug('loading curve %s with driver %s' % (self.name, self.driver))
302 data,info = self.driver.read(self.path, self.info)
304 for key,value in info.items():
305 self.info[key] = value
306 if self._hooke != None:
307 self.command_stack.execute(self._hooke)
308 elif len(self.command_stack) > 0:
310 'could not execute command stack for %s without Hooke instance'
314 """Release memory intensive :attr:`.data`.
316 log = logging.getLogger('hooke')
317 log.debug('unloading curve %s' % self.name)