Brought picoforce driver up to speed with 0x06130001 and 0x07200000
authorW. Trevor King <wking@drexel.edu>
Fri, 4 Jun 2010 12:01:34 +0000 (08:01 -0400)
committerW. Trevor King <wking@drexel.edu>
Fri, 4 Jun 2010 12:01:34 +0000 (08:01 -0400)
hooke/driver/picoforce.py
test/picoforce_driver.py [new file with mode: 0644]

index 8018eb56362af3bd04d139efcc15a3db75bee1a4..e664a951726ff3b6402adeaa0c0e45894ec69b19 100644 (file)
@@ -152,7 +152,7 @@ class PicoForceDriver (Driver):
         Otherwise, raise `ValueError`.
         """
         version = info['Force file list'].get('Version', None)
-        if version not in ['0x06120002']:
+        if version not in ['0x06120002', '0x06130001', '0x07200000']:
             raise NotImplementedError(
                 '%s file version %s not supported (yet!)\n%s'
                 % (self.name, version,
@@ -171,35 +171,39 @@ class PicoForceDriver (Driver):
     def _read_data_file(self, file, info):
         file.seek(0)
         traces = self._extract_traces(buffer(file.read()), info)
-        deflection,z_piezo,deflection_B = traces
-        self._validate_traces(z_piezo, deflection, deflection_B)
-        L = len(deflection)
+        for k,t in traces.items():
+            t.tofile('t-%s' % k, sep='\n')
+        self._validate_traces(
+            traces['Z sensor'], traces['Deflection'])
+        L = len(traces['Deflection'])
         approach = self._extract_block(
-            info, z_piezo, deflection, 0, L/2, 'approach')
+            info, traces['Z sensor'], traces['Deflection'], 0, L/2, 'approach')
         retract = self._extract_block(
-            info, z_piezo, deflection, L/2, L, 'retract')
+            info, traces['Z sensor'], traces['Deflection'], L/2, L, 'retract')
         data = [approach, retract]
         return data
 
     def _extract_traces(self, buffer, info):
         """Extract each of the three vector blocks in a PicoForce file.
 
-        The blocks are:
+        The blocks are (in variable order):
 
-        * Deflection input
         * Z piezo sensor input
+        * Deflection input
         * Deflection again?
 
         And their headers are marked with 'Ciao force image list'.
         """
-        traces = []
+        traces = {}
+        version = info['Force file list']['Version']
+        type_re = re.compile('S \[(\w*)\] "([\w\s.]*)"')
         for image in info['Ciao force image list']:
             offset = int(image['Data offset'])
             length = int(image['Data length'])
             sample_size = int(image['Bytes/pixel'])
-            rows = length / sample_size
             if sample_size != 2:
                 raise NotImplementedError('Size: %s' % sample_size)
+            rows = length / sample_size
             d = curve.Data(
                 shape=(rows),
                 dtype=numpy.int16,
@@ -207,19 +211,55 @@ class PicoForceDriver (Driver):
                 offset=offset,
                 info=image,
                 )
-            traces.append(d)
+            if version in ['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))
+                tname = match.group(2)
+            else:
+                assert version == '0x07200000', version
+                match = type_re.match(image['@4:Image Data'])
+                assert match != None, 'Bad regexp for %s, %s' \
+                    % ('@4:Image Data', image['@4:Image Data'])
+                if match.group(1) == 'PulseFreq1':
+                    assert match.group(2) == 'Freq. 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)
+                if tname == 'Freq. 1':  # Normalize trace names between versions
+                    tname = 'Z sensor'
+                elif tname == 'Deflection Error':
+                    tname = 'Deflection'
+            if tname in traces:
+                continue
+                msg = None
+                if (traces[tname].info != image):
+                    ik = set(image.keys())
+                    ok = set(traces[tname].info.keys())
+                    if ik != ok:
+                        mmsg = 'extra keys: %s, missing keys %s' % (ik-ok, ok-ik)
+                    else:
+                        mmsg = []
+                        for key in image.keys():
+                            if image[key] != traces[tname].info[key]:
+                                mmsg.append(
+                                    '%s (%s != %s)'
+                                    % (key, image[key], traces[tname].info[key]))
+                        mmsg = ', '.join(mmsg)
+                    msg = 'info difference: %s' % mmsg
+                elif not (traces[tname] == d).all():
+                    msg = 'data difference'
+                if msg != None:
+                    raise NotImplementedError(
+                        'Missmatched duplicate traces for %s: %s'
+                        % (tname, msg))
+            traces[tname] = d
         return traces
 
-    def _validate_traces(self, z_piezo, deflection, deflection_B):
-        key = 'Spring Constant'
-        spring_constant = z_piezo.info[key]
-        for trace in [deflection, deflection_B]:
-            if trace.info[key] != spring_constant:
-                raise NotImplementedError(
-                    'spring constant missmatch: %s != %s'
-                    % (spring_constant, trace.info[key]))
-        if max(abs(deflection_B[:-1]-deflection[:-1])) != 0:
-            raise NotImplementedError('trace 0 != trace 2')
+    def _validate_traces(self, z_piezo, deflection):
         if len(z_piezo) != len(deflection):
             raise ValueError('Trace length missmatch: %d != %d'
                              % (len(z_piezo), len(deflection)))
@@ -232,44 +272,46 @@ class PicoForceDriver (Driver):
         block[:,1] = deflection[start:stop]
         block.info = self._translate_block_info(
             info, z_piezo.info, deflection.info, name)
+        block.info['columns'] = ['z piezo (m)', 'deflection (m)']
         block = self._scale_block(block)
         return block
 
     def _translate_block_info(self, info, z_piezo_info, deflection_info, name):
+        version = info['Force file list']['Version']
         ret = {
-            'name':name,
-            'raw info':info,
+            '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['Spring Constant']),
             }
 
         t = info['Force file list']['Date'] # 04:42:34 PM Tue Sep 11 2007
         ret['time'] = time.strptime(t, '%I:%M:%S %p %a %b %d %Y')
 
-        type_re = re.compile('S \[(\w*)\] "([\w\s]*)"')
-        match = type_re.match(z_piezo_info['@4:Image Data'])
-        assert match.group(1).lower() == match.group(2).replace(' ','').lower(), \
-            'Name missmatch: "%s", "%s"' % (match.group(1), match.group(2))
-        ret['columns'] = [match.group(2)]
-        match = type_re.match(deflection_info['@4:Image Data'])
-        assert match.group(1).lower() == match.group(2).replace(' ','').lower(), \
-            'Name missmatch: "%s", "%s"' % (match.group(1), match.group(2))
-        ret['columns'].append(match.group(2))
-        assert ret['columns'] == ['Z sensor', 'Deflection'], \
-            'Unexpected columns: %s' % ret['columns']
-        ret['columns'] = ['z piezo (m)', 'deflection (m)']
-
         volt_re = re.compile(
-            'V \[Sens. (\w*)\] \(([.0-9]*) V/LSB\) (-?[.0-9]*) V')
-        match = volt_re.match(z_piezo_info['@4:Z scale'])
-        assert match.group(1) == 'ZSensorSens', z_piezo_info['@4:Z scale']
+            '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']:
+            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']
+        else:
+            assert version == '0x07200000', version
+            match = hz_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) == 'Freq. 1', z_piezo_info['@4:Z scale']
         ret['z piezo sensitivity (V/bit)'] = float(match.group(2))
         ret['z piezo range (V)'] = float(match.group(3))
         ret['z piezo offset (V)'] = 0.0
         # offset assumed if raw data is signed...
 
         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']
         ret['deflection sensitivity (V/bit)'] = float(match.group(2))
         ret['deflection range (V)'] = float(match.group(3))
@@ -278,34 +320,54 @@ class PicoForceDriver (Driver):
 
         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'])
         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'])
         ret['deflection sensitivity (m/V)'] = float(match.group(1))*1e-9
 
         match = volt_re.match(info['Ciao force list']['@Z scan start'])
+        assert match != None, 'Bad regexp for %s/%s, %s' \
+            % ('Ciao force list', '@Z scan start', info['Ciao force list']['@Z scan start'])
         ret['z piezo scan (V/bit)'] = float(match.group(2))
         ret['z piezo scan start (V)'] = float(match.group(3))
 
         match = volt_re.match(info['Ciao force list']['@Z scan size'])
+        assert match != None, 'Bad regexp for %s/%s, %s' \
+            % ('Ciao force list', '@Z scan size', info['Ciao force list']['@Z scan size'])
         ret['z piezo scan size (V)'] = float(match.group(3))
 
         const_re = re.compile('C \[([:\w\s]*)\] ([.0-9]*)')
         match = const_re.match(z_piezo_info['@Z magnify'])
+        assert match != None, 'Bad regexp for %s, %s' \
+            % ('@Z magnify', info['@Z magnify'])
         assert match.group(1) == '4:Z scale', match.group(1)
         ret['z piezo gain'] = float(match.group(2))
 
-        match = volt_re.match(z_piezo_info['@4:Z scale'])
-        assert match.group(1) == 'ZSensorSens', match.group(1)
-        ret['z piezo sensitivity (V/bit)'] = float(match.group(2))
-        ret['z piezo range (V)'] = float(match.group(3))
+        if version in ['0x06120002', '0x06130001']:        
+            match = volt_re.match(z_piezo_info['@4:Z scale'])
+            assert match != None, 'Bad regexp for %s, %s' \
+                % ('@4:Z scale', info['@4:Z scale'])
+            assert match.group(1) == 'ZSensorSens', match.group(1)
+            ret['z piezo sensitivity (V/bit)'] = float(match.group(2))
+            ret['z piezo range (V)'] = float(match.group(3))
+        else:
+            assert version == '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))
 
         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))
diff --git a/test/picoforce_driver.py b/test/picoforce_driver.py
new file mode 100644 (file)
index 0000000..d1345e1
--- /dev/null
@@ -0,0 +1,69 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# 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 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/>.
+
+"""
+>>> from hooke.hooke import Hooke, HookeRunner
+>>> h = Hooke()
+>>> r = HookeRunner()
+>>> h = r.run_lines(h, ['load_playlist test/data/vclamp_picoforce/playlist'])
+<FilePlaylist playlist.hkp>
+Success
+<BLANKLINE>
+>>> h = r.run_lines(h, ['curve_info']) # doctest: +ELLIPSIS, +REPORT_UDIFF
+name: 20071120a_i27_t33.100
+path: test/data/vclamp_picoforce/20071120a_i27_t33.100
+experiment: <class 'hooke.experiment.VelocityClamp'>
+driver: <hooke.driver.picoforce.PicoForceDriver object at 0x...>
+filetype: picoforce
+note: 
+blocks: 2
+block sizes: [(2048, 2), (2048, 2)]
+Success
+<BLANKLINE>
+
+Also checkout the newer versions we have available.
+
+>>> h = r.run_lines(h, ['previous_curve'])
+Success
+<BLANKLINE>
+>>> h = r.run_lines(h, ['curve_info']) # doctest: +ELLIPSIS, +REPORT_UDIFF
+name: 0x07200000
+path: test/data/vclamp_picoforce/0x07200000
+experiment: <class 'hooke.experiment.VelocityClamp'>
+driver: <hooke.driver.picoforce.PicoForceDriver object at 0x...>
+filetype: picoforce
+note: 
+blocks: 2
+block sizes: [(512, 2), (512, 2)]
+Success
+<BLANKLINE>
+>>> h = r.run_lines(h, ['previous_curve'])
+Success
+<BLANKLINE>
+>>> h = r.run_lines(h, ['curve_info']) # doctest: +ELLIPSIS, +REPORT_UDIFF
+name: 0x06130001
+path: test/data/vclamp_picoforce/0x06130001
+experiment: <class 'hooke.experiment.VelocityClamp'>
+driver: <hooke.driver.picoforce.PicoForceDriver object at 0x...>
+filetype: picoforce
+note: 
+blocks: 2
+block sizes: [(2048, 2), (2048, 2)]
+Success
+<BLANKLINE>
+"""