#
# 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/>.
-"""Library for interpreting Veeco PicoForce force spectroscopy files.
+"""Driver for Veeco PicoForce force spectroscopy files.
"""
import pprint
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
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)
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'])))
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,
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)))
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))
# 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))
* 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)'])