Add matplotlib config option and stepper_approach plot.
[pyafm.git] / pyafm / afm.py
index dd6d2396a2e2bc95a110949e39a4c8e651654f70..fc8dacbefc4f3d8e37c2776241e3206e6016b7dd 100644 (file)
@@ -1,19 +1,18 @@
-# Copyright (C) 2009-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2011-2012 W. Trevor King <wking@tremily.us>
 #
 # This file is part of pyafm.
 #
-# pyafm is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation, either version 3 of the License, or (at your
-# option) any later version.
+# pyafm is free software: you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
 #
-# pyafm is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# pyafm is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License
-# along with pyafm.  If not, see <http://www.gnu.org/licenses/>.
+# You should have received a copy of the GNU General Public License along with
+# pyafm.  If not, see <http://www.gnu.org/licenses/>.
 
 """Tools for controlling atomic force microscopes.
 
@@ -23,6 +22,14 @@ for controlling the piezo (`pypiezo`) and stepper (`stepper`), this
 module only contains methods that require the capabilities of both.
 """
 
+try:
+    import matplotlib as _matplotlib
+    import matplotlib.pyplot as _matplotlib_pyplot
+    import time as _time  # for timestamping lines on plots
+except (ImportError, RuntimeError), e:
+    _matplotlib = None
+    _matplotlib_import_error = e
+
 from pypiezo.afm import AFMPiezo as _AFMPiezo
 from pypiezo.base import convert_bits_to_meters as _convert_bits_to_meters
 from pypiezo.base import convert_meters_to_bits as _convert_meters_to_bits
@@ -31,6 +38,7 @@ from pypiezo.surface import FlatFit as _FlatFit
 from pypiezo.surface import SurfaceError as _SurfaceError
 
 from . import LOG as _LOG
+from . import package_config as _package_config
 from .stepper import Stepper as _Stepper
 from .temperature import Temperature as _Temperature
 
@@ -81,7 +89,8 @@ class AFM (object):
     >>> config['temperature'] = pyafm.config.TemperatureConfig()
     >>> config['temperature']['name'] = 'test temperature'
 
-    >>> afm = AFM(config=config, devices=devices)
+    >>> afm = AFM(config=config)
+    >>> afm.load_from_config(devices=devices)
     >>> afm.setup_config()
 
     >>> afm.get_temperature()  # doctest: +SKIP
@@ -260,7 +269,8 @@ class AFM (object):
           test temperature
         <HDF5 dataset "units": shape (), type "|S7">
           Celsius
-    >>> afm2 = pyafm.storage.load_afm(filename=filename, devices=devices)
+    >>> afm2 = pyafm.storage.load_afm(filename=filename)
+    >>> afm2.load_from_config(devices=devices)
 
     >>> afm2.get_temperature()  # doctest: +SKIP
     297.37
@@ -274,22 +284,23 @@ class AFM (object):
 
     >>> os.remove(filename)
     """
-    def __init__(self, config, piezo=None, stepper=None, temperature=None,
-                 devices=None):
+    def __init__(self, config, piezo=None, stepper=None, temperature=None):
         self.config = config
         self.piezo = piezo
         self.stepper = stepper
         self.temperature = temperature
-        self.load_from_config(devices=devices)
 
     def load_from_config(self, devices):
         c = self.config  # reduce verbosity
         if self.piezo is None and c['piezo']:
-            self.piezo = _AFMPiezo(config=c['piezo'], devices=devices)
+            self.piezo = _AFMPiezo(config=c['piezo'])
+            self.piezo.load_from_config(devices=devices)
         if self.stepper is None and c['stepper']:
-            self.stepper = _Stepper(config=c['stepper'], devices=devices)
+            self.stepper = _Stepper(config=c['stepper'])
+            self.stepper.load_from_config(devices=devices)
         if self.temperature is None and c['temperature']:
             self.temperature = _Temperature(config=c['temperature'])
+            self.temperature.load_from_config()
 
     def setup_config(self):
         if self.piezo:
@@ -373,8 +384,8 @@ class AFM (object):
 
         _LOG.debug('fine tune the stepper position')
         while pos_m < -stepper_tolerance:  # step back if we need to
-            _LOG.debug('step back')
             self.stepper.single_step(-1)
+            _LOG.debug('step back to {}'.format(self.stepper.position))
             try:
                 pos = self.piezo.get_surface_position(
                     axis_name=self.config['main-axis'],
@@ -387,8 +398,8 @@ class AFM (object):
             _LOG.debug('located surface at stepper %d, piezo %d (%g m)'
                       % (self.stepper.position, pos, pos_m))
         while pos_m > stepper_tolerance:  # step forward if we need to
-            _LOG.debug('step forward')
             self.stepper.single_step(1)
+            _LOG.debug('step forward to {}'.format(self.stepper.position))
             try:
                 pos = self.piezo.get_surface_position(
                     axis_name=self.config['main-axis'],
@@ -444,6 +455,7 @@ class AFM (object):
             except _SurfaceError, e:
                 _LOG.info(e)
             self.stepper.single_step(-1)  # step out
+            _LOG.debug('stepped back to {}'.format(self.stepper.position))
         _LOG.debug('giving up on finding the surface')
         _LOG.warn(e)
         raise e
@@ -451,6 +463,10 @@ class AFM (object):
     def stepper_approach(self, target_deflection):
         _LOG.info('approach with stepper until deflection > {}'.format(
                 target_deflection))
+        record_data = _package_config['matplotlib']
+        if record_data:
+            position = []
+            deflection = []
         self._check_target_deflection(deflection=target_deflection)
         cd = self.piezo.read_deflection()  # cd = current deflection in bits
         _LOG.debug('single stepping approach')
@@ -459,6 +475,20 @@ class AFM (object):
                     cd, target_deflection))
             self.stepper.single_step(1)  # step in
             cd = self.piezo.read_deflection()
+            if record_data:
+                position.append(self.stepper.position)
+                deflection.append(cd)
+        if _package_config['matplotlib']:
+            figure = _matplotlib_pyplot.figure()
+            axes = figure.add_subplot(1, 1, 1)
+            axes.hold(False)
+            timestamp = _time.strftime('%H-%M-%S')
+            axes.set_title('stepper approach {}'.format(timestamp))
+            plot = axes.plot(position, deflection, 'b.-')
+            figure.canvas.draw()
+            figure.show()
+            if not _matplotlib.is_interactive():
+                _matplotlib_pyplot.show()
 
     def move_toward_surface(self, distance):
         """Step in approximately `distance` meters.