Add Curve loading to hooke.playlist.Playlist.
[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     def __new__(self, subtype, shape, dtype=numpy.float, buffer=None, offset=0,
49                 strides=None, order=None, info=None):
50         """Create the ndarray instance of our type, given the usual
51         input arguments.  This will call the standard ndarray
52         constructor, but return an object of our type.
53         """
54         obj = np.ndarray.__new__(subtype=subtype, shape=shape, dtype=dtype,
55                                  buffer=buffer, offset=offset, strides=strides,
56                                  order=order)
57         # add the new attribute to the created instance
58         if info == None:
59             info = {}
60         obj.info = info
61         # Finally, we must return the newly created object:
62         return obj
63
64     def __array_finalize__(self, obj):
65         """Set any extra attributes from the original object when
66         creating a new view object."""
67         # reset the attribute from passed original object
68         self.info = getattr(obj, 'info', {})
69         # We do not need to return anything
70
71
72 class Curve (object):
73     """A grouped set of :class:`Data` runs from the same file with metadata.
74
75     For an approach/retract force spectroscopy experiment, the group
76     would consist of the approach data and the retract data.  Metadata
77     would be the temperature, cantilever spring constant, etc.
78
79     Two important :attr:`info` settings are `filetype` and
80     `experiment`.  These are two strings that can be used by Hooke
81     commands/plugins to understand what they are looking at.
82
83     * `.info['filetype']` should contain the name of the exact
84       filetype defined by the driver (so that filetype-speciofic
85       commands can know if they're dealing with the correct filetype).
86     * `.info['experiment']` should contain an instance of a
87       :class:`hooke.experiment.Experiment` subclass to identify the
88       experiment type.  For example, various
89       :class:`hooke.driver.Driver`\s can read in force-clamp data, but
90       Hooke commands could like to know if they're looking at force
91       clamp data, regardless of their origin.
92     """
93     def __init__(self, path, info=None):
94         #the data dictionary contains: {name of data: list of data sets [{[x], [y]}]
95         self.path = path
96         self.driver = None
97         self.data = None
98         if info == None:
99             info = {}
100         self.info = info
101         self.name = os.path.basename(path)
102
103     def identify(self, drivers):
104         """Identify the appropriate :class:`hooke.driver.Driver` for
105         the curve file (`.path`).
106         """
107         for driver in drivers:
108             if driver.is_me(self.path):
109                 self.driver = driver # remember the working driver
110                 return
111         raise NotRecognized(self)
112
113     def load(self):
114         """Use the driver to read the curve into memory.
115         """
116         data,info = self.driver.read(self.path)
117         self.data = data
118         for key,value in info.items():
119             self.info[key] = value
120
121     def unload(self):
122         """Release memory intensive :attr:`.data`.
123         """
124         self.data = None