Convert all channels to their default calibration in the JPK driver.
[hooke.git] / hooke / hooke.py
1 # Copyright (C) 2008-2010 Fabrizio Benedetti
2 #                         Massimo Sandal <devicerandom@gmail.com>
3 #                         Rolf Schmidt <rschmidt@alcor.concordia.ca>
4 #                         W. Trevor King <wking@drexel.edu>
5 #
6 # This file is part of Hooke.
7 #
8 # Hooke is free software: you can redistribute it and/or modify it
9 # under the terms of the GNU Lesser General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
12 #
13 # Hooke is distributed in the hope that it will be useful, but WITHOUT
14 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
16 # Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with Hooke.  If not, see
20 # <http://www.gnu.org/licenses/>.
21
22 """Hooke - A force spectroscopy review & analysis tool.
23 """
24
25 if False: # Queue pickle error debugging code
26     """The Hooke class is passed back from the CommandEngine process
27     to the main process via a :class:`multiprocessing.queues.Queue`,
28     which uses :mod:`pickle` for serialization.  There are a number of
29     objects that are unpicklable, and the error messages are not
30     always helpful.  This block of code hooks you into the Queue's
31     _feed method so you can print out useful tidbits to help find the
32     particular object that is gumming up the pickle works.
33     """
34     import multiprocessing.queues
35     import sys
36     feed = multiprocessing.queues.Queue._feed
37     def new_feed (buffer, notempty, send, writelock, close):
38         def s(obj):
39             print 'SEND:', obj, dir(obj)
40             for a in dir(obj):
41                 attr = getattr(obj, a)
42                 #print '  ', a, attr, type(attr)
43             if obj.__class__.__name__ == 'Hooke':
44                 # Set suspect attributes to None until you resolve the
45                 # PicklingError.  Then fix whatever is breaking the
46                 # pickling.
47                 #obj.commands = None
48                 #obj.drivers = None
49                 #obj.plugins = None
50                 #obj.ui = None
51                 pass
52             sys.stdout.flush()
53             send(obj)
54         feed(buffer, notempty, s, writelock, close)
55     multiprocessing.queues.Queue._feed = staticmethod(new_feed)
56
57 from ConfigParser import NoSectionError
58 import logging
59 import logging.config
60 import multiprocessing
61 import optparse
62 import os.path
63 import Queue
64 import unittest
65 import StringIO
66 import sys
67
68 from . import version
69 from . import engine
70 from . import config as config_mod
71 from . import playlist
72 from . import plugin as plugin_mod
73 from . import driver as driver_mod
74 from . import ui
75
76
77 class Hooke (object):
78     def __init__(self, config=None, debug=0):
79         self.debug = debug
80         default_settings = (config_mod.DEFAULT_SETTINGS
81                             + plugin_mod.default_settings()
82                             + driver_mod.default_settings()
83                             + ui.default_settings())
84         if config == None:
85             config = config_mod.HookeConfigParser(
86                 paths=config_mod.DEFAULT_PATHS,
87                 default_settings=default_settings)
88             config.read()
89         self.config = config
90         self.load_log()
91         self.load_plugins()
92         self.load_drivers()
93         self.load_ui()
94         self.engine = engine.CommandEngine()
95         self.playlists = playlist.NoteIndexList()
96
97     def load_log(self):
98         config_file = StringIO.StringIO()
99         self.config.write(config_file)
100         logging.config.fileConfig(StringIO.StringIO(config_file.getvalue()))
101         # Don't attach the logger because it contains an unpicklable
102         # thread.lock.  Instead, grab it directly every time you need it.
103         #self.log = logging.getLogger('hooke')
104
105     def load_plugins(self):
106         self.plugins = plugin_mod.load_graph(
107             plugin_mod.PLUGIN_GRAPH, self.config, include_section='plugins')
108         self.configure_plugins()
109         self.commands = []
110         for plugin in self.plugins:
111             self.commands.extend(plugin.commands())
112         self.command_by_name = dict(
113             [(c.name, c) for c in self.commands])
114
115     def load_drivers(self):
116         self.drivers = plugin_mod.load_graph(
117             driver_mod.DRIVER_GRAPH, self.config, include_section='drivers')
118         self.configure_drivers()
119
120     def load_ui(self):
121         self.ui = ui.load_ui(self.config)
122         self.configure_ui()
123
124     def configure_plugins(self):
125         for plugin in self.plugins:
126             self._configure_item(plugin)
127
128     def configure_drivers(self):
129         for driver in self.drivers:
130             self._configure_item(driver)
131
132     def configure_ui(self):
133         self._configure_item(self.ui)
134
135     def _configure_item(self, item):
136         conditions = self.config.items('conditions')
137         try:
138             item.config = dict(self.config.items(item.setting_section))
139         except NoSectionError:
140             item.config = {}
141         for key,value in conditions:
142             if key not in item.config:
143                 item.config[key] = value
144
145     def close(self, save_config=False):
146         if save_config == True:
147             self.config.write()  # Does not preserve original comments
148
149     def run_command(self, command, arguments):
150         """Run the command named `command` with `arguments` using
151         :meth:`~hooke.engine.CommandEngine.run_command`.
152
153         Allows for running commands without spawning another process
154         as in :class:`HookeRunner`.
155         """
156         self.engine.run_command(self, command, arguments)
157
158
159 class HookeRunner (object):
160     def run(self, hooke):
161         """Run Hooke's main execution loop.
162
163         Spawns a :class:`hooke.engine.CommandEngine` subprocess and
164         then runs the UI, rejoining the `CommandEngine` process after
165         the UI exits.
166         """
167         ui_to_command,command_to_ui,command = self._setup_run(hooke)
168         try:
169             hooke.ui.run(hooke.commands, ui_to_command, command_to_ui)
170         finally:
171             hooke = self._cleanup_run(ui_to_command, command_to_ui, command)
172         return hooke
173
174     def run_lines(self, hooke, lines):
175         """Run the pre-set commands `lines` with the "command line" UI.
176
177         Allows for non-interactive sessions that are otherwise
178         equivalent to :meth:'.run'.
179         """
180         cmdline = ui.load_ui(hooke.config, 'command line')
181         ui_to_command,command_to_ui,command = self._setup_run(hooke)
182         try:
183             cmdline.run_lines(
184                 hooke.commands, ui_to_command, command_to_ui, lines)
185         finally:
186             hooke = self._cleanup_run(ui_to_command, command_to_ui, command)
187         return hooke
188
189     def _setup_run(self, hooke):
190         ui_to_command = multiprocessing.Queue()
191         command_to_ui = multiprocessing.Queue()
192         manager = multiprocessing.Manager()
193         command = multiprocessing.Process(name='command engine',
194             target=hooke.engine.run, args=(hooke, ui_to_command, command_to_ui))
195         command.start()
196         hooke.engine = None  # no more need for the UI-side version.
197         return (ui_to_command, command_to_ui, command)
198
199     def _cleanup_run(self, ui_to_command, command_to_ui, command):
200         log = logging.getLogger('hooke')
201         log.debug('cleanup sending CloseEngine')
202         ui_to_command.put(engine.CloseEngine())
203         hooke = None
204         while not isinstance(hooke, Hooke):
205             log.debug('cleanup waiting for Hooke instance from the engine.')
206             hooke = command_to_ui.get(block=True)
207             log.debug('cleanup got %s instance' % type(hooke))
208         command.join()
209         for playlist in hooke.playlists:  # Curve._hooke is not pickled.
210             for curve in playlist:
211                 curve.set_hooke(hooke)
212         return hooke
213
214
215 def main():
216     p = optparse.OptionParser()
217     p.add_option(
218         '--version', dest='version', default=False, action='store_true',
219         help="Print Hooke's version information and exit.")
220     p.add_option(
221         '-s', '--script', dest='script', metavar='FILE',
222         help='Script of command line Hooke commands to run.')
223     p.add_option(
224         '-c', '--command', dest='commands', metavar='COMMAND',
225         action='append', default=[],
226         help='Add a command line Hooke command to run.')
227     p.add_option(
228         '-p', '--persist', dest='persist', action='store_true', default=False,
229         help="Don't exit after running a script or commands.")
230     p.add_option(
231         '--save-config', dest='save_config',
232         action='store_true', default=False,
233         help="Automatically save a changed configuration on exit.")
234     p.add_option(
235         '--debug', dest='debug', action='store_true', default=False,
236         help="Enable debug logging.")
237     options,arguments = p.parse_args()
238     if len(arguments) > 0:
239         print >> sys.stderr, 'More than 0 arguments to %s: %s' \
240             % (sys.argv[0], arguments)
241         p.print_help(sys.stderr)
242         sys.exit(1)
243
244     hooke = Hooke(debug=__debug__)
245     runner = HookeRunner()
246
247     if options.version == True:
248         print version()
249         sys.exit(0)
250     if options.debug == True:
251         hooke.config.set(
252             section='handler_hand1', option='level', value='NOTSET')
253         hooke.load_log()
254     if options.script != None:
255         with open(os.path.expanduser(options.script), 'r') as f:
256             options.commands.extend(f.readlines())
257     if len(options.commands) > 0:
258         try:
259             hooke = runner.run_lines(hooke, options.commands)
260         finally:
261             if options.persist == False:
262                 hooke.close(save_config=options.save_config)
263                 sys.exit(0)
264
265     try:
266         hooke = runner.run(hooke)
267     finally:
268         hooke.close(save_config=options.save_config)