Initial attempt at PicoForce 0x05120005 support.
[hooke.git] / hooke / driver / picoforce.py
index a465c2ef5ad1c359532e435f9fb6a2ff83aca80d..845412d04da6cdacb2fddc61866b56a613553007 100644 (file)
@@ -4,23 +4,24 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
 # <http://www.gnu.org/licenses/>.
 
-"""Driver for Veeco PicoForce force spectroscopy files.
+"""Driver for Bruker PicoForce force spectroscopy files.
 """
 
+import os.path
 import pprint
 import re
 import time
@@ -28,32 +29,31 @@ import time
 import numpy
 
 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
 
 
-__version__='0.0.0.20100516'
+__version__='0.0.0.20110421'
+
 
 class PicoForceDriver (Driver):
-    """Handle Veeco Picoforce force spectroscopy files.
+    """Handle Bruker Picoforce force spectroscopy files.
     """
     def __init__(self):
         super(PicoForceDriver, self).__init__(name='picoforce')
 
     def is_me(self, path):
+        if os.path.isdir(path):
+            return False
         f = file(path, 'r')
         header = f.read(30)
         f.close()
 
         return header[2:17] == 'Force file list'
 
-    def read(self, path):
+    def read(self, path, info=None):
         info = self._read_header_path(path)
         self._check_version(info)
         data = self._read_data_path(path, info)
-        info['filetype'] = self.name
-        info['experiment'] = experiment.VelocityClamp
         return (data, info)
 
     def _read_header_path(self, path):
@@ -152,7 +152,8 @@ class PicoForceDriver (Driver):
         Otherwise, raise `ValueError`.
         """
         version = info['Force file list'].get('Version', None)
-        if version not in ['0x06120002', '0x06130001', '0x07200000']:
+        if version not in ['0x05120005', '0x06120002', '0x06130001',
+                           '0x07200000']:
             raise NotImplementedError(
                 '%s file version %s not supported (yet!)\n%s'
                 % (self.name, version,
@@ -195,6 +196,9 @@ class PicoForceDriver (Driver):
         traces = {}
         version = info['Force file list']['Version']
         type_re = re.compile('S \[(\w*)\] "([\w\s.]*)"')
+        if isinstance(info['Ciao force image list'], dict):
+            # there was only one image, but convert to list for processing
+            info['Ciao force image list'] = [info['Ciao force image list']]
         for image in info['Ciao force image list']:
             offset = int(image['Data offset'])
             length = int(image['Data length'])
@@ -209,12 +213,16 @@ class PicoForceDriver (Driver):
                 offset=offset,
                 info=image,
                 )
-            if version in ['0x06120002', '0x06130001']:
+            if version in ['0x05120005', '0x06120002', '0x06130001']:
                 match = type_re.match(image['@4:Image Data'])
                 assert match != None, 'Bad regexp for %s, %s' \
                     % ('@4:Image Data', image['@4:Image Data'])
-                assert match.group(1).lower() == match.group(2).replace(' ','').lower(), \
-                    'Name missmatch: "%s", "%s"' % (match.group(1), match.group(2))
+                if version == '0x06130001' and match.group(1) == 'ZLowVoltage':
+                    assert match.group(2) == 'Low Voltage Z', \
+                        'Name missmatch: "%s", "%s"' % (match.group(1), match.group(2))
+                else:
+                    assert match.group(1).lower() == match.group(2).replace(' ','').lower(), \
+                        'Name missmatch: "%s", "%s"' % (match.group(1), match.group(2))
                 tname = match.group(2)
             else:
                 assert version == '0x07200000', version
@@ -240,6 +248,37 @@ class PicoForceDriver (Driver):
                 #d.tofile('%s.dat' % tname, sep='\n')
                 pass
             traces[tname] = d
+        if 'Z sensor' not in traces:
+            # some picoforce files only save deflection
+            assert version == '0x05120005', version
+            force_info = info['Ciao force list']
+            deflection = traces['Deflection']
+            volt_re = re.compile(
+                'V \[Sens. ([\w\s.]*)\] \(([.0-9]*) V/LSB\) *(-?[.0-9]*) V')
+            match = volt_re.match(force_info['@4:Ramp size Zsweep'])
+            size = float(match.group(3))
+            match = volt_re.match(force_info['@4:Ramp offset Zsweep'])
+            offset = float(match.group(3))
+            match = volt_re.match(force_info['@4:Ramp Begin Zsweep'])
+            begin = float(match.group(3))
+            match = volt_re.match(force_info['@4:Ramp End Zsweep'])
+            end = float(match.group(3))
+            #\@4:Feedback value Zsweep: V [Sens. Zscan] (0.002056286 V/LSB)       0 V
+            #\@4:Z display Zsweep: V [Sens. Zscan] (0.002056286 V/LSB) 18.53100 V
+            assert len(deflection) % 2 == 0, len(deflection)
+            points = len(deflection)/2
+            traces['Z sensor'] = curve.Data(
+                shape=deflection.shape,
+                dtype=numpy.float,
+                info=info['Ciao force image list'][0],
+                )
+            # deflection data seems to be saved as
+            #   [final approach, ..., initial approach,
+            #    initial retract, ..., final retract]
+            traces['Z sensor'][:points] = numpy.linspace(
+                offset+begin+size, offset+begin, points)
+            traces['Z sensor'][-points:] = numpy.linspace(
+                offset+begin+size, offset+end, points)
         return traces
 
     def _validate_traces(self, z_piezo, deflection):
@@ -292,23 +331,30 @@ class PicoForceDriver (Driver):
         #    raise NotImplementedError(
         #        'Missmatched duplicate traces for %s: %s'
         #        % (trace_name, ', '.join(msg)))
-        import sys
+        import logging
+        log = logging.getLogger('hooke')
         for name,t in traces.items():
             if (t == trace).all():
-                print >> sys.stderr, 'replace %s with %s-2' % (name, trace_name)
+                log.debug('replace %s with %s-2' % (name, trace_name))
                 return name  # Replace this identical dataset.
-        print >> sys.stderr, 'store %s-2 as Other' % (trace_name)
+        log.debug('store %s-2 as Other' % (trace_name))
         return 'Other'
         # return None
 
     def _translate_block_info(self, info, z_piezo_info, deflection_info, name):
         version = info['Force file list']['Version']
+        if version == '0x05120005':
+            k_key = 'Spring constant'
+        else:
+            assert version in [
+                '0x06120002', '0x06130001', '0x07200000'], version
+            k_key = 'Spring Constant'
         ret = {
             'name': name,
             'raw info': info,
             'raw z piezo info': z_piezo_info,
             'raw deflection info': deflection_info,
-            'spring constant (N/m)': float(z_piezo_info['Spring Constant']),
+            'spring constant (N/m)': float(z_piezo_info[k_key]),
             }
 
         t = info['Force file list']['Date'] # 04:42:34 PM Tue Sep 11 2007
@@ -318,11 +364,16 @@ class PicoForceDriver (Driver):
             'V \[Sens. ([\w\s.]*)\] \(([.0-9]*) V/LSB\) (-?[.0-9]*) V')
         hz_re = re.compile(
             'V \[Sens. ([\w\s.]*)\] \(([.0-9]*) kHz/LSB\) (-?[.0-9]*) kHz')
-        if version in ['0x06120002', '0x06130001']:
+        if version in ['0x05120005', '0x06120002', '0x06130001']:
             match = volt_re.match(z_piezo_info['@4:Z scale'])
             assert match != None, 'Bad regexp for %s, %s' \
                 % ('@4:Z scale', z_piezo_info['@4:Z scale'])
-            assert match.group(1) == 'ZSensorSens', z_piezo_info['@4:Z scale']
+            if version == '0x05120005':
+                assert match.group(1) == 'Deflection', (
+                    z_piezo_info['@4:Z scale'])
+            else:
+                assert match.group(1) == 'ZSensorSens', (
+                    z_piezo_info['@4:Z scale'])
         else:
             assert version == '0x07200000', version
             match = hz_re.match(z_piezo_info['@4:Z scale'])
@@ -337,21 +388,47 @@ class PicoForceDriver (Driver):
         match = volt_re.match(deflection_info['@4:Z scale'])
         assert match != None, 'Bad regexp for %s, %s' \
             % ('@4:Z scale', deflection_info['@4:Z scale'])
-        assert match.group(1) == 'DeflSens', z_piezo_info['@4:Z scale']
+        if version == '0x05120005':
+            assert match.group(1) == 'Deflection', z_piezo_info['@4:Z scale']
+        else:
+            assert version in [
+                '0x06120002', '0x06130001', '0x07200000'], version
+            assert match.group(1) == 'DeflSens', z_piezo_info['@4:Z scale']
         ret['deflection sensitivity (V/bit)'] = float(match.group(2))
         ret['deflection range (V)'] = float(match.group(3))
         ret['deflection offset (V)'] = 0.0
         # offset assumed if raw data is signed...
 
         nm_sens_re = re.compile('V ([.0-9]*) nm/V')
-        match = nm_sens_re.match(info['Scanner list']['@Sens. Zsens'])
-        assert match != None, 'Bad regexp for %s/%s, %s' \
-            % ('Scanner list', '@Sens. Zsens', info['Scanner list']['@4:Z scale'])
+        if version == '0x05120005':
+            match = nm_sens_re.match(info['Scanner list']['@Sens. Zscan'])
+            assert match != None, 'Bad regexp for %s/%s, %s' \
+                % ('Scanner list', '@Sens. Zscan',
+                   info['Scanner list']['@Sens. Zscan'])
+        elif version in ['0x06120002', '0x06130001']:        
+            match = nm_sens_re.match(info['Ciao scan list']['@Sens. ZSensorSens'])
+            assert match != None, 'Bad regexp for %s/%s, %s' \
+                % ('Ciao scan list', '@Sens. ZSensorSens',
+                   info['Ciao scan list']['@Sens. ZSensorSens'])
+        else:
+            assert version == '0x07200000', version
+            match = nm_sens_re.match(info['Ciao scan list']['@Sens. ZsensSens'])
+            assert match != None, 'Bad regexp for %s/%s, %s' \
+                % ('Ciao scan list', '@Sens. ZsensSens',
+                   info['Ciao scan list']['@Sens. ZsensSens'])
         ret['z piezo sensitivity (m/V)'] = float(match.group(1))*1e-9
 
-        match = nm_sens_re.match(info['Ciao scan list']['@Sens. DeflSens'])
-        assert match != None, 'Bad regexp for %s/%s, %s' \
-            % ('Ciao scan list', '@Sens. DeflSens', info['Ciao scan list']['@Sens. DeflSens'])
+        if version == '0x05120005':
+            match = nm_sens_re.match(info['Ciao scan list']['@Sens. Deflection'])
+            assert match != None, 'Bad regexp for %s/%s, %s' \
+                % ('Scanner list', '@Sens. Zscan',
+                   info['Ciao scan list']['@Sens. Deflection'])
+        else:
+            assert version in [
+                '0x06120002', '0x06130001', '0x07200000'], version
+            match = nm_sens_re.match(info['Ciao scan list']['@Sens. DeflSens'])
+            assert match != None, 'Bad regexp for %s/%s, %s' \
+                % ('Ciao scan list', '@Sens. DeflSens', info['Ciao scan list']['@Sens. DeflSens'])
         ret['deflection sensitivity (m/V)'] = float(match.group(1))*1e-9
 
         match = volt_re.match(info['Ciao force list']['@Z scan start'])
@@ -380,22 +457,28 @@ class PicoForceDriver (Driver):
             ret['z piezo sensitivity (V/bit)'] = float(match.group(2))
             ret['z piezo range (V)'] = float(match.group(3))
         else:
-            assert version == '0x07200000', version
+            assert version in ['0x05120005', '0x07200000'], version
             pass
 
-        match = volt_re.match(z_piezo_info['@4:Ramp size'])
-        assert match != None, 'Bad regexp for %s, %s' \
-            % ('@4:Ramp size', info['@4:Ramp size'])
-        assert match.group(1) == 'Zsens', match.group(1)
-        ret['z piezo ramp size (V/bit)'] = float(match.group(2))
-        ret['z piezo ramp size (V)'] = float(match.group(3))
+        if version == '0x05120005':
+            # already accounded for when generating 'Z sensor' trace
+            pass
+        else:
+            assert version in [
+                '0x06120002', '0x06130001', '0x07200000'], version
+            match = volt_re.match(z_piezo_info['@4:Ramp size'])
+            assert match != None, 'Bad regexp for %s, %s' \
+                % ('@4:Ramp size', info['@4:Ramp size'])
+            assert match.group(1) == 'Zsens', match.group(1)
+            ret['z piezo ramp size (V/bit)'] = float(match.group(2))
+            ret['z piezo ramp size (V)'] = float(match.group(3))
 
-        match = volt_re.match(z_piezo_info['@4:Ramp offset'])
-        assert match != None, 'Bad regexp for %s, %s' \
-            % ('@4:Ramp offset', info['@4:Ramp offset'])
-        assert match.group(1) == 'Zsens', match.group(1)
-        ret['z piezo ramp offset (V/bit)'] = float(match.group(2))
-        ret['z piezo ramp offset (V)'] = float(match.group(3))
+            match = volt_re.match(z_piezo_info['@4:Ramp offset'])
+            assert match != None, 'Bad regexp for %s, %s' \
+                % ('@4:Ramp offset', info['@4:Ramp offset'])
+            assert match.group(1) == 'Zsens', match.group(1)
+            ret['z piezo ramp offset (V/bit)'] = float(match.group(2))
+            ret['z piezo ramp offset (V)'] = float(match.group(3))
 
         # Unaccounted for:
         #   Samps*
@@ -418,7 +501,7 @@ class PicoForceDriver (Driver):
         z_col = info['columns'].index('z piezo (m)')
         d_col = info['columns'].index('deflection (m)')
 
-        # Leading '-' because Veeco's z increases towards the surface
+        # Leading '-' because Bruker's z increases towards the surface
         # (positive indentation), but it makes more sense to me to
         # have it increase away from the surface (positive
         # separation).