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
26 from .command_stack import CommandStack
29 class NotRecognized (ValueError):
30 def __init__(self, curve):
31 self.__setstate__(curve)
33 def __getstate__(self):
36 def __setstate__(self, data):
37 if isinstance(data, Curve):
38 msg = 'Not a recognizable curve format: %s' % data.path
39 super(NotRecognized, self).__init__(msg)
42 class Data (numpy.ndarray):
43 """Stores a single, continuous data set.
45 Adds :attr:`info` :class:`dict` to the standard :class:`numpy.ndarray`.
47 See :mod:`numpy.doc.subclassing` for the peculiarities of
48 subclassing :class:`numpy.ndarray`.
53 >>> d = Data(shape=(3,2), info={'columns':['distance (m)', 'force (N)']})
55 <class 'hooke.curve.Data'>
56 >>> for i in range(3): # initialize d
57 ... for j in range(2):
64 {'columns': ['distance (m)', 'force (N)']}
66 The information gets passed on to slices.
72 {'columns': ['distance (m)', 'force (N)']}
74 The data-type is also pickleable, to ensure we can move it between
75 processes with :class:`multiprocessing.Queue`\s.
78 >>> s = pickle.dumps(d)
79 >>> z = pickle.loads(s)
85 {'columns': ['distance (m)', 'force (N)']}
87 def __new__(subtype, shape, dtype=numpy.float, buffer=None, offset=0,
88 strides=None, order=None, info=None):
89 """Create the ndarray instance of our type, given the usual
90 input arguments. This will call the standard ndarray
91 constructor, but return an object of our type.
93 obj = numpy.ndarray.__new__(
94 subtype, shape, dtype, buffer, offset, strides, order)
95 # add the new attribute to the created instance
99 # Finally, we must return the newly created object:
102 def __array_finalize__(self, obj):
103 """Set any extra attributes from the original object when
104 creating a new view object."""
105 # reset the attribute from passed original object
106 self.info = getattr(obj, 'info', {})
107 # We do not need to return anything
109 def __reduce__(self):
110 """Collapse an instance for pickling.
114 reconstruct : callable
115 Called to create the initial version of the object.
117 A tuple of arguments for `reconstruct`
119 The state to be passed to __setstate__, if present.
120 iter : iterator (optional)
121 Yielded items will be appended to the reconstructed
123 dict : iterator (optional)
124 Yielded (key,value) tuples pushed back onto the
125 reconstructed object.
127 base_reduce = list(numpy.ndarray.__reduce__(self))
128 # tack our stuff onto ndarray's setstate portion.
129 base_reduce[2] = (base_reduce[2], (self.info,))
130 return tuple(base_reduce)
132 def __setstate__(self, state):
133 base_class_state,own_state = state
134 numpy.ndarray.__setstate__(self, base_class_state)
135 self.info, = own_state
138 class Curve (object):
139 """A grouped set of :class:`Data` runs from the same file with metadata.
141 For an approach/retract force spectroscopy experiment, the group
142 would consist of the approach data and the retract data. Metadata
143 would be the temperature, cantilever spring constant, etc.
145 Two important :attr:`info` settings are `filetype` and
146 `experiment`. These are two strings that can be used by Hooke
147 commands/plugins to understand what they are looking at.
149 * :attr:`info['filetype']` should contain the name of the exact
150 filetype defined by the driver (so that filetype-speciofic
151 commands can know if they're dealing with the correct filetype).
152 * :attr:`info['experiment']` should contain an instance of a
153 :class:`hooke.experiment.Experiment` subclass to identify the
154 experiment type. For example, various
155 :class:`hooke.driver.Driver`\s can read in force-clamp data, but
156 Hooke commands could like to know if they're looking at force
157 clamp data, regardless of their origin.
159 Another important attribute is :attr:`command_stack`, which holds
160 a :class:`~hooke.command_stack.CommandStack` listing the commands
161 that have been applied to the `Curve` since loading.
163 def __init__(self, path, info=None):
164 #the data dictionary contains: {name of data: list of data sets [{[x], [y]}]
171 self.name = os.path.basename(path)
172 self.command_stack = CommandStack()
173 self._hooke = None # Hooke instance for Curve.load()
175 def set_hooke(self, hooke=None):
179 def identify(self, drivers):
180 """Identify the appropriate :class:`hooke.driver.Driver` for
181 the curve file (`.path`).
183 if 'filetype' in self.info:
184 driver = [d for d in drivers if d.name == self.info['filetype']]
187 if driver.is_me(self.path):
190 for driver in drivers:
191 if driver.is_me(self.path):
192 self.driver = driver # remember the working driver
194 raise NotRecognized(self)
196 def load(self, hooke=None):
197 """Use the driver to read the curve into memory.
199 Also runs any commands in :attr:`command_stack`. All
200 arguments are passed through to
201 :meth:`hooke.command_stack.CommandStack.execute`.
203 self.set_hooke(hooke)
204 data,info = self.driver.read(self.path, self.info)
206 for key,value in info.items():
207 self.info[key] = value
208 if self.hooke != None:
209 # TODO: set 'curve' argument explicitly for CurveCommands
210 self.command_stack.execute(self.hooke)
213 """Release memory intensive :attr:`.data`.