+"""The curve module provides :class:`Curve` and :class:`Data` for storing
+force curves.
+"""
+
import os.path
import numpy
from .driver import NotRecognized
+
class Data (numpy.ndarray):
"""Stores a single, continuous data set.
For an approach/retract force spectroscopy experiment, the group
would consist of the approach data and the retract data. Metadata
would be the temperature, cantilever spring constant, etc.
+
+ Two important :attr:`info` settings are `filetype` and
+ `experiment`. These are two strings that can be used by Hooke
+ commands/plugins to understand what they are looking at.
+
+ * `.info['filetype']` should contain the name of the exact
+ filetype defined by the driver (so that filetype-speciofic
+ commands can know if they're dealing with the correct filetype).
+ * `.info['experiment']` should contain an instance of a
+ :class:`hooke.experiment.Experiment` subclass to identify the
+ experiment type. For example, various
+ :class:`hooke.driver.Driver`\s can read in force-clamp data, but
+ Hooke commands could like to know if they're looking at force
+ clamp data, regardless of their origin.
"""
def __init__(self, path):
#the data dictionary contains: {name of data: list of data sets [{[x], [y]}]
the curve file (`.path`).
"""
for driver in drivers:
- current_driver = driver(self.path)
- if current_driver.is_me():
- self.driver = current_driver # remember the working driver
+ if driver.is_me(self.path):
+ self.driver = driver # remember the working driver
return
raise NotRecognized(self.path)
+"""The driver module provides :class:`Driver`\s for identifying and
+reading data files.
+
+This allows Hooke to be data-file agnostic. Drivers for various
+commercial force spectroscopy microscopes are provided, and it's easy
+to write your own to handle your lab's specific format.
+"""
+
+from ..config import Setting
+from ..util.graph import Node, Graph
+
+
+DRIVER_MODULES = [
+# ('csvdriver', True),
+# ('hdf5', True),
+# ('hemingclamp', True),
+# ('jpk', True),
+# ('mcs', True),
+# ('mfp1dexport', True),
+# ('mfp3d', True),
+# ('picoforce', True),
+# ('picoforcealt', True),
+ ('tutorial', True),
+]
+"""List of driver modules and whether they should be included by
+default. TODO: autodiscovery
+"""
+
class NotRecognized (ValueError):
def __init__(self, path):
msg = 'Not a recognizable curve format: %s' % self.path
self.path = path
class Driver(object):
- '''
- Base class for file format drivers.
-
- To be overridden
- '''
- def __init__(self):
- self.experiment = ''
- self.filetype = ''
-
- def is_me(self):
- '''
- This method must read the file and return True if the filetype can be managed by the driver, False if not.
- '''
+ """Base class for file format drivers.
+
+ :attr:`name` identifies your driver, and should match the module
+ name.
+ """
+ def __init__(self, name):
+ self.name = name
+
+ def dependencies(self):
+ """Return a list of :class:`Driver`\s we require."""
+ return []
+
+ def default_settings(self):
+ """Return a list of :class:`hooke.config.Setting`\s for any
+ configurable driver settings.
+
+ The suggested section setting is::
+
+ Setting(section='%s driver' % self.name, help=self.__doc__)
+ """
+ return []
+
+ def is_me(self, path):
+ """Read the file and return True if the filetype can be
+ managed by the driver. Otherwise return False.
+ """
return False
- def close_all(self):
- '''
- This method must close all the open files of the driver, explicitly.
- '''
- return None
+ def read(self, path):
+ """Read data from `path` and return a
+ (:class:`hooke.curve.Data`, `info`) tuple.
+
+ The `info` :class:`dict` must contain values for the keys:
+ 'filetype' and 'experiment'. See :class:`hooke.curve.Curve`
+ for details.
+ """
+ raise NotImplementedError
+
+# Construct driver dependency graph and load default drivers.
+
+DRIVERS = {}
+"""(name, instance) :class:`dict` of all possible :class:`Driver`\s.
+"""
+
+for driver_modname,default_include in DRIVER_MODULES:
+ assert len([mod_name for mod_name,di in DRIVER_MODULES]) == 1, \
+ 'Multiple %s entries in DRIVER_MODULES' % mod_name
+ this_mod = __import__(__name__, fromlist=[driver_modname])
+ driver_mod = getattr(this_mod, driver_modname)
+ for objname in dir(driver_mod):
+ obj = getattr(driver_mod, objname)
+ try:
+ subclass = issubclass(obj, Driver)
+ except TypeError:
+ continue
+ if subclass == True and obj != Driver:
+ d = obj()
+ if d.name != driver_modname:
+ raise Exception('Driver name %s does not match module name %s'
+ % (d.name, driver_modname))
+ DRIVERS[d.name] = d
+
+DRIVER_GRAPH = Graph([Node([DRIVERS[name] for name in d.dependencies()],
+ data=d)
+ for d in DRIVERS.values()])
+DRIVER_GRAPH.topological_sort()
+
- def default_plots(self):
- dummy_default = PlotObject()
- dummy_default.vectors.append([[[0]],[[0]]])
- return [dummy_default]
+def default_settings():
+ settings = [Setting(
+ 'drivers', help='Enable/disable default drivers.')]
+ for dnode in DRIVER_GRAPH:
+ driver = dnode.data
+ default_include = [di for mod_name,di in DRIVER_MODULES
+ if mod_name == driver.name][0]
+ help = driver.__doc__.split('\n', 1)[0]
+ settings.append(Setting(
+ section='drivers',
+ option=driver.name,
+ value=str(default_include),
+ help=help,
+ ))
+ for dnode in DRIVER_GRAPH:
+ driver = dnode.data
+ settings.extend(driver.default_settings())
+ return settings
--- /dev/null
+# Copyright (c) 2008 Massimo Sandal
+# 2010 W. Trevor King
+
+"""Tutorial driver for Hooke.
+
+This example driver explains driver construction.
+"""
+
+"""
+Here we define a simple file format that is read by this driver. The
+file format is as following::
+
+ TUTORIAL_FILE
+ PLOT1
+ X1
+ n1 <- ?
+ n2
+ ...
+ nN
+ Y1
+ n1
+ n2
+ ...
+ nN
+ X2
+ n1
+ n2
+ ..
+ nN
+ Y2
+ n1
+ n2
+ ..
+ nN
+ PLOT2
+ X1
+ ...
+ Y1
+ ...
+ X2
+ ...
+ Y2
+ ...
+ END
+
+that is, two plots with two datasets each.
+"""
+
+# The following are relative imports. See PEP 328 for details
+# http://www.python.org/dev/peps/pep-0328/
+from .. import curve as curve # this module defines data containers.
+from .. import experiment as experiment # this module defines expt. types
+from ..config import Setting # configurable setting class
+from . import Driver as Driver # this is the Driver base class
+
+# The driver must inherit from the parent
+# :class:`hooke.driver.Driver` (which we have imported as `Driver`).
+class TutorialDriver (Driver):
+ """Handle simple text data as an example Driver.
+ """
+ def __init__(self):
+ """YOU MUST OVERRIDE Driver.__init__.
+
+ Here you set a value for `name` to identify your driver. It
+ should match the module name.
+ """
+ super(TutorialDriver, self).__init__(name='tutorial')
+
+ def default_settings(self):
+ """Return a list of any configurable settings for your driver.
+
+ If your driver does not have any configurable settings, there
+ is no need to override this method.
+ """
+ section = '%s driver' % self.name
+ return [
+ Setting(section=section, help=self.__doc__),
+ Setting(section=section, option='x units', value='nm',
+ help='Set the units used for the x data.'),
+ ]
+
+ def is_me(self):
+ """YOU MUST OVERRIDE Driver.is_me.
+
+ RETURNS: Boolean (`True` or `False`)
+
+ This method is a heuristic that looks at the file content and
+ decides if the file can be opened by the driver itself. It
+ returns `True` if the file opened can be interpreted by the
+ current driver, `False` otherwise. Defining this method allows
+ Hooke to understand what kind of files we're looking at
+ automatically.
+ """
+
+ f = open(self.filename, 'r')
+ header = f.readline() # we only need the first line
+ f.close()
+
+ """Our "magic fingerprint" is the TUTORIAL_FILE header. Of
+ course, depending on the data file, you can have interesting
+ headers, or patterns, etc. that you can use to guess the data
+ format. What matters is successful recognition and the boolean
+ (True/False) return.
+ """
+ if header.startswith('TUTORIAL_FILE'):
+ return True
+ return False
+
+ def read(self, path):
+ f = open(path,'r') # open the file for reading
+ """In this case, we have a data format that is just a list of
+ ASCII values, so we can just divide that in rows, and generate
+ a list with each item being a row. Of course if your data
+ files are binary, or follow a different approach, do whatever
+ you need. :)
+ """
+ self.data = list(self.filedata)
+ f.close() # remember to close the file
+
+ data = curve.Data()
+ info = {'filetype':'tutorial', 'experiment':'generic'}
+ return (data, info)
+++ /dev/null
-'''
-tutorialdriver.py
-
-TUTORIAL DRIVER FOR HOOKE
-
-Example driver to teach how to write a driver for data types.
-(c)Massimo Sandal 2008
-'''
-
-'''
-Here we define a (fake) file format that is read by this driver. The file format is as following:
-
-TUTORIAL_FILE
-PLOT1
-X1
-n1
-n2
-...
-nN
-Y1
-n1
-n2
-...
-nN
-X2
-n1
-n2
-..
-nN
-Y2
-n1
-n2
-..
-nN
-PLOT2
-X1
-...
-Y1
-...
-X2
-...
-Y2
-...
-END
-that is, two plots with two datasets each.
-'''
-
-from .. import curve as lhc #We need to import this library to define some essential data types
-
-class tutorialdriverDriver(lhc.Driver):
- '''
- This is a *generic* driver, not a specific force spectroscopy driver.
- See the written documentation to see what a force spectroscopy driver must be defined to take into account Hooke facilities.
-
- Our driver is a class with the name convention nameofthedriverDriver, where "nameofthedriver" is the filename.
- The driver must inherit from the parent class lhc.Driver, so the syntax is
- class nameofthedriverDriver(lhc.Driver)
- '''
- def __init__(self, filename):
- '''
- THIS METHOD MUST BE DEFINED.
- The __init__ method MUST call the filename, so that it can open the file.
- '''
- self.filename=filename #self.filename can always be useful, and should be defined
- self.filedata = open(filename,'r') #We open the file
- '''
- In this case, we have a data format that is just a list of ASCII values, so we can just divide that in rows, and generate a list
- with each item being a row.
- Of course if your data files are binary, or follow a different approach, do whatever you need. :)
- '''
- self.data = list(self.filedata)
- self.filedata.close() #remember to close the file
-
- '''These are two strings that can be used by Hooke commands/plugins to understand what they are looking at. They have no other
- meaning. They have to be somehow defined however - commands often look for those variables.
-
- self.filetype should contain the name of the exact filetype defined by the driver (so that filetype-specific commands can know
- if they're dealing with the correct filetype)
- self.experiment should contain instead the type of data involved (for example, various drivers can be used for force-clamp experiments,
- but hooke commands could like to know if we're looking at force clamp data, regardless of their origin, and not other
- kinds of data)
-
- Of course, all other variables you like can be defined in the class.
- '''
- self.filetype = 'tutorial'
- self.experiment = 'generic'
-
- def is_me(self):
- '''
- THIS METHOD MUST BE DEFINED.
- RETURNS: Boolean (True or False)
- This method must be an heuristic that looks at the file content and decides if the file can be opened by the driver itself.
- It returns True if the file opened can be interpreted by the current driver, False otherwise.
- Defining this method allows Hooke to understand what kind of files we're looking at automatically.
-
- We have to re-open/re-close the file here.
- '''
-
- myfile=open(self.filename, 'r')
- headerline=myfile.readlines()[0] #we take the first line
- myfile.close()
-
- '''
- Here, our "magic fingerprint" is the TUTORIAL_FILE header. Of course, depending on the data file, you can have interesting
- headers, or patterns, etc. that you can use to guess the data format. What matters is successful recognizing, and returning
- a boolean (True/False).
- '''
- if headerline[:-1]=='TUTORIAL_FILE': #[:-1], otherwise the return character is included in the line
- return True
- else:
- return False
-
- def _generate_vectors(self):
- '''
- Here we parse the data and generate the raw vectors. This method has only to do with the peculiar file format here, so it's of
- no big interest (I just wrote it to present a functional driver).
-
- Only thing to remember, it can be nice to call methods that are used only "internally" by the driver (or by plugins) with a
- "_" prefix, so to have a visual remark. But it's just an advice.
- '''
- vectors={'PLOT1':[[],[],[],[]] , 'PLOT2':[[],[],[],[]]}
- positions={'X1':0,'Y1':1,'X2':2,'Y2':3}
- whatplot=''
- pos=0
- for item in self.data:
- try:
- num=float(item)
- vectors[whatplot][pos].append(num)
- except ValueError:
- if item[:-1]=='PLOT1':
- whatplot=item[:-1]
- elif item[:-1]=='PLOT2':
- whatplot=item[:-1]
- elif item[0]=='X' or item[0]=='Y':
- pos=positions[item[:-1]]
- else:
- pass
-
- return vectors
-
- def close_all(self):
- '''
- THIS METHOD MUST BE DEFINED.
- This method is a precaution method that is invoked when cycling to avoid eventually dangling open files.
- In this method, all file objects defined in the driver must be closed.
- '''
- self.filename.close()
-
-
- def default_plots(self):
- '''
- THIS METHOD MUST BE DEFINED.
- RETURNS: [ lhc.PlotObject ] or [ lhc.PlotObject, lhc.PlotObject]
-
- This is the method that returns the plots to Hooke.
- It must return a list with *one* or *two* PlotObjects.
-
- See the curve.py source code to see how PlotObjects are defined and work in detail.
- '''
- gen_vectors=self._generate_vectors()
-
- #Here we create the main plot PlotObject and initialize its vectors.
- main_plot=lhc.PlotObject()
- main_plot.vectors=[]
- #The same for the other plot.
- other_plot=lhc.PlotObject()
- other_plot.vectors=[]
-
- '''
- Now we fill the plot vectors with our data.
- set 1 set 2
- The "correct" shape of the vector is [ [[x1,x2,x3...],[y1,y2,y3...]] , [[x1,x2,x3...],[y1,y2,y3...]] ], so we have to put stuff in this way into it.
-
- The add_set() method takes care of this , just use plot.add_set(x,y).
- '''
- main_plot.add_set(gen_vectors['PLOT1'][0],gen_vectors['PLOT1'][1])
- main_plot.add_set(gen_vectors['PLOT1'][2],gen_vectors['PLOT1'][3])
-
- other_plot.add_set(gen_vectors['PLOT2'][0],gen_vectors['PLOT2'][1])
- other_plot.add_set(gen_vectors['PLOT2'][2],gen_vectors['PLOT2'][3])
-
- '''
- normalize_vectors() trims the vectors, so that if two x/y couples are of different lengths, the latest
- points are trimmed (otherwise we have a python error). Always a good idea to run it, to avoid crashes.
- '''
- main_plot.normalize_vectors()
- other_plot.normalize_vectors()
-
- '''
- Here we define:
- - units: [string, string], define the measure units of X and Y axes
- - destination: 0/1 , defines where to plot the plot (0=top, 1=bottom), default=0
- - title: string , the plot title.
-
- for each plot.
- Again, see curve.py comments for details.
- '''
- main_plot.units=['unit of x','unit of y']
- main_plot.destination=0
- main_plot.title=self.filename+' main'
-
- other_plot.units=['unit of x','unit of y']
- other_plot.destination=1
- other_plot.title=self.filename+' other'
-
- return [main_plot, other_plot]
--- /dev/null
+"""Define :class:`Experiment` and assorted subclasses.
+
+This allows :class:`hooke.plugin.Plugin`\s to specify the types of
+experiments they can handle.
+"""
+
+class Experiment (object):
+ """Base class for experiment classification.
+ """
+ pass
+
+class ForceClamp (Experiment):
+ """Constant force experiments.
+ """
+ pass
+
+class VelocityClamp (Experiment):
+ """Constant piezo velocity experiments.
+ """
All of the science happens in here.
"""
-import os.path
import Queue as queue
from ..config import Setting
--- /dev/null
+
+ def _generate_vectors(self):
+ """
+ Here we parse the data and generate the raw vectors. This
+ method has only to do with the peculiar file format here, so
+ it's of no big interest (I just wrote it to present a
+ functional driver).
+
+ Only thing to remember, it can be nice to call methods that
+ are used only "internally" by the driver (or by plugins) with
+ a "_" prefix, so to have a visual remark. But it's just an
+ advice.
+ """
+ vectors={'PLOT1':[[],[],[],[]] , 'PLOT2':[[],[],[],[]]}
+ positions={'X1':0,'Y1':1,'X2':2,'Y2':3}
+ whatplot=''
+ pos=0
+ for item in self.data:
+ try:
+ num=float(item)
+ vectors[whatplot][pos].append(num)
+ except ValueError:
+ if item[:-1]=='PLOT1':
+ whatplot=item[:-1]
+ elif item[:-1]=='PLOT2':
+ whatplot=item[:-1]
+ elif item[0]=='X' or item[0]=='Y':
+ pos=positions[item[:-1]]
+ else:
+ pass
+
+ return vectors
+
+ def default_plots(self):
+ """
+ THIS METHOD MUST BE DEFINED.
+ RETURNS: [ lhc.PlotObject ] or [ lhc.PlotObject, lhc.PlotObject]
+
+ This is the method that returns the plots to Hooke.
+ It must return a list with *one* or *two* PlotObjects.
+
+ See the curve.py source code to see how PlotObjects are defined and work in detail.
+ """
+ gen_vectors=self._generate_vectors()
+
+ #Here we create the main plot PlotObject and initialize its vectors.
+ main_plot=lhc.PlotObject()
+ main_plot.vectors=[]
+ #The same for the other plot.
+ other_plot=lhc.PlotObject()
+ other_plot.vectors=[]
+
+ """
+ Now we fill the plot vectors with our data.
+ set 1 set 2
+ The "correct" shape of the vector is [ [[x1,x2,x3...],[y1,y2,y3...]] , [[x1,x2,x3...],[y1,y2,y3...]] ], so we have to put stuff in this way into it.
+
+ The add_set() method takes care of this , just use plot.add_set(x,y).
+ """
+ main_plot.add_set(gen_vectors['PLOT1'][0],gen_vectors['PLOT1'][1])
+ main_plot.add_set(gen_vectors['PLOT1'][2],gen_vectors['PLOT1'][3])
+
+ other_plot.add_set(gen_vectors['PLOT2'][0],gen_vectors['PLOT2'][1])
+ other_plot.add_set(gen_vectors['PLOT2'][2],gen_vectors['PLOT2'][3])
+
+ """
+ normalize_vectors() trims the vectors, so that if two x/y couples are of different lengths, the latest
+ points are trimmed (otherwise we have a python error). Always a good idea to run it, to avoid crashes.
+ """
+ main_plot.normalize_vectors()
+ other_plot.normalize_vectors()
+
+ """
+ Here we define:
+ - units: [string, string], define the measure units of X and Y axes
+ - destination: 0/1 , defines where to plot the plot (0=top, 1=bottom), default=0
+ - title: string , the plot title.
+
+ for each plot.
+ Again, see curve.py comments for details.
+ """
+ main_plot.units=['unit of x','unit of y']
+ main_plot.destination=0
+ main_plot.title=self.filename+' main'
+
+ other_plot.units=['unit of x','unit of y']
+ other_plot.destination=1
+ other_plot.title=self.filename+' other'
+
+ return [main_plot, other_plot]