X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=hooke%2Fdriver%2Fpicoforce.py;h=94339d438964b76f97b29e1ee132e5998a5f388f;hb=b90995fb4b6d8151df862d40edc8c369d7052cfa;hp=64d349f060bd21f99054c5e0f9f25cb1c233bf80;hpb=63f8022384c15c83abf2b436243d608a5a6c835e;p=hooke.git diff --git a/hooke/driver/picoforce.py b/hooke/driver/picoforce.py index 64d349f..94339d4 100644 --- a/hooke/driver/picoforce.py +++ b/hooke/driver/picoforce.py @@ -4,21 +4,21 @@ # # 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 # . -"""Library for interpreting Veeco PicoForce force spectroscopy files. +"""Driver for Veeco PicoForce force spectroscopy files. """ import pprint @@ -29,7 +29,6 @@ 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 @@ -48,7 +47,7 @@ class PicoForceDriver (Driver): 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) @@ -152,8 +151,8 @@ class PicoForceDriver (Driver): Otherwise, raise `ValueError`. """ version = info['Force file list'].get('Version', None) - if version not in ['0x06120002']: - raise ValueError( + if version not in ['0x06120002', '0x06130001', '0x07200000']: + raise NotImplementedError( '%s file version %s not supported (yet!)\n%s' % (self.name, version, pprint.pformat(info['Force file list']))) @@ -171,35 +170,37 @@ 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) + 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 +208,44 @@ 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']) + 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 + 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: + #d.tofile('%s-2.dat' % tname, sep='\n') + tname = self._replace_name(tname, d, traces, info) + if tname == None: + continue # Don't replace anything + else: + #d.tofile('%s.dat' % tname, sep='\n') + pass + 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 +258,89 @@ 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 _replace_name(self, trace_name, trace, traces, info): + """Determine if a duplicate trace name should replace an earlier trace. + + Return the target trace name if it should be replaced by the + new trace, or `None` if the new trace should be dropped. + """ + #msg = [] + #target = traces[trace_name] + # + ## Compare the info dictionaries for each trace + #ik = set(trace.info.keys()) + #ok = set(traces[trace_name].info.keys()) + #if ik != ok: # Ensure we have the same set of keys for both traces + # msg.append('extra keys: %s, missing keys %s' % (ik-ok, ok-ik)) + #else: + # # List keys we *require* to change between traces + # variable_keys = ['Data offset', 'X data type'] # TODO: What is X data type? + # for key in trace.info.keys(): + # if key in variable_keys: + # if target.info[key] == trace.info[key]: + # msg.append('constant %s (%s == %s)' + # % (key, target.info[key], trace.info[key])) + # else: + # if target.info[key] != trace.info[key]: + # msg.append('variable %s (%s != %s)' + # % (key, target.info[key], trace.info[key])) + # Compare the data + #if not (traces[trace_name] == trace).all(): + # msg.append('data difference') + #if len(msg) > 0: + # raise NotImplementedError( + # 'Missmatched duplicate traces for %s: %s' + # % (trace_name, ', '.join(msg))) + import logging + log = logging.getLogger('hooke') + for name,t in traces.items(): + if (t == trace).all(): + log.debug('replace %s with %s-2' % (name, trace_name)) + return name # Replace this identical dataset. + 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'] 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)) @@ -277,35 +348,63 @@ class PicoForceDriver (Driver): # 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']) + if 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']) 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'] = 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)) + ret['z piezo gain'] = float(match.group(2)) + + 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)) @@ -343,7 +442,11 @@ class PicoForceDriver (Driver): * info['z piezo sensitivity (m/V)'] ) - ret[:,d_col] = ( + # Leading '-' because deflection voltage increases as the tip + # moves away from the surface, but it makes more sense to me + # to have it increase as it moves toward the surface (positive + # tension on the protein chain). + ret[:,d_col] = -( (data[:,d_col] * info['deflection sensitivity (V/bit)'] - info['deflection offset (V)'])