1 # Copyright (C) 2006-2010 Alberto Gomez-Casado
2 # Massimo Sandal <devicerandom@gmail.com>
3 # W. Trevor King <wking@drexel.edu>
5 # This file is part of Hooke.
7 # Hooke is free software: you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public
9 # License as published by the Free Software Foundation, either
10 # version 3 of the License, or (at your option) any later version.
12 # Hooke is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Lesser General Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with Hooke. If not, see
19 # <http://www.gnu.org/licenses/>.
21 """Library for interpreting Picoforce force spectroscopy files.
23 An alternave implementation of :mod:`hooke.driver.picoforce`.
27 from scipy import arange
29 from .. import curve as lhc
31 __version__='0.0.0.20081706'
35 class DataChunk(list):
36 #Dummy class to provide ext and ret methods to the data list.
40 return self[0:halflen]
46 class picoforcealtDriver(lhc.Driver):
48 #Construction and other special methods
50 def __init__(self,filename):
55 self.textfile=file(filename)
56 self.binfile=file(filename,'rb')
58 #The 0,1,2 data chunks are:
65 #TODO eliminate the need to set chunk numbers
67 self.filepath=filename
70 self.filetype='picoforce'
71 self.experiment='smfs'
75 def _get_samples_line(self):
77 Gets the samples per line parameters in the file, to understand trigger behaviour.
81 samps_expr=re.compile(".*Samps")
84 for line in self.textfile.readlines():
85 if samps_expr.match(line):
87 samps=int(line.split()[2]) #the third word splitted is the offset (in bytes)
88 samps_values.append(samps)
92 #We raise a flag for the fact we meet an offset, otherwise we would take spurious data length arguments.
94 return int(samps_values[0])
96 def _get_chunk_coordinates(self):
98 This method gets the coordinates (offset and length) of a data chunk in our
101 It returns a list containing two tuples:
102 the first element of each tuple is the data_offset, the second is the corresponding
105 In near future probably each chunk will get its own data structure, with
106 offset, size, type, etc.
108 self.textfile.seek(0)
110 offset_expr=re.compile(".*Data offset")
111 length_expr=re.compile(".*Data length")
117 for line in self.textfile.readlines():
119 if offset_expr.match(line):
120 offset=int(line.split()[2]) #the third word splitted is the offset (in bytes)
121 data_offsets.append(offset)
122 #We raise a flag for the fact we meet an offset, otherwise we would take spurious data length arguments.
125 #same for the data length
126 if length_expr.match(line) and flag_offset:
127 size=int(line.split()[2])
128 data_sizes.append(size)
129 #Put down the offset flag until the next offset is met.
132 return zip(data_offsets,data_sizes)
134 def _get_data_chunk(self,whichchunk):
136 reads a data chunk and converts it in 16bit signed int.
138 offset,size=self._get_chunk_coordinates()[whichchunk]
141 self.binfile.seek(offset)
142 raw_chunk=self.binfile.read(size)
145 for data_position in range(0,len(raw_chunk),2):
146 data_unit_bytes=raw_chunk[data_position:data_position+2]
147 #The unpack function converts 2-bytes in a signed int ('h').
148 #we use output[0] because unpack returns a 1-value tuple, and we want the number only
149 data_unit=struct.unpack('h',data_unit_bytes)[0]
150 my_chunk.append(data_unit)
152 return DataChunk(my_chunk)
155 #returns force vector
156 Kspring=self.get_spring_constant()
157 return DataChunk([(meter*Kspring) for meter in self._deflection()])
159 def _deflection(self):
160 #for internal use (feeds _force)
162 z_scale=self._get_Z_scale()
163 deflsensitivity=self.get_deflection_sensitivity()
164 volts=[((float(lsb))*voltrange*z_scale) for lsb in self.data_chunks[self.forcechunk]]
165 deflect=[volt*deflsensitivity for volt in volts]
171 #returns distance vector (calculated instead than from data chunk)
172 rampsize=self._get_rampsize()
173 sampsline=self._get_samples_line()
174 senszscan=self._get_Z_scan_sens()
176 xstep=senszscan*rampsize/sampsline*10**(-9)
178 xext=arange(sampsline*xstep,0,-xstep)
179 xret=arange(sampsline*xstep,0,-xstep)
181 return DataChunk(xext.tolist()+xret.tolist())
183 def _get_Z_scale(self):
184 self.textfile.seek(0)
185 expr=re.compile(".*@4:Z scale")
187 for line in self.textfile.readlines():
189 zscale=float((line.split()[5]).strip("() []"))
193 def _get_rampsize(self):
194 self.textfile.seek(0)
195 expr=re.compile(".*@4:Ramp size:")
197 for line in self.textfile.readlines():
199 zsens=float((line.split()[7]).strip("() []"))
203 def _get_Z_scan_sens(self):
204 self.textfile.seek(0)
205 expr=re.compile(".*@Sens. Zsens")
207 for line in self.textfile.readlines():
209 zsens=float((line.split()[3]).strip("() []"))
215 def get_deflection_sensitivity(self):
217 gets deflection sensitivity
219 self.textfile.seek(0)
221 def_sensitivity_expr=re.compile(".*@Sens. DeflSens")
223 for line in self.textfile.readlines():
224 if def_sensitivity_expr.match(line):
225 def_sensitivity=float(line.split()[3])
227 #return it in SI units (that is: m/V, not nm/V)
228 return def_sensitivity*(10**(-9))
230 def get_spring_constant(self):
232 gets spring constant.
233 We actually find *three* spring constant values, one for each data chunk (F/t, Z/t, F/z).
234 They are normally all equal, but we retain all three for future...
236 self.textfile.seek(0)
238 springconstant_expr=re.compile(".*Spring Constant")
242 for line in self.textfile.readlines():
243 if springconstant_expr.match(line):
244 constants.append(float(line.split()[2]))
250 self-identification of file type magic
252 curve_file=file(self.filepath)
253 header=curve_file.read(30)
256 if header[2:17] == 'Force file list': #header of a picoforce file
257 #here DONT translate chunk
258 self.data_chunks=[self._get_data_chunk(num) for num in [0,1,2]]
265 Explicitly closes all files
267 self.textfile.close()
270 def default_plots(self):
272 creates the default PlotObject
276 samples=self._get_samples_line()
277 main_plot=lhc.PlotObject()
278 main_plot.vectors=[[zdomain.ext()[0:samples], force.ext()[0:samples]],[zdomain.ret()[0:samples], force.ret()[0:samples]]]
279 main_plot.normalize_vectors()
280 main_plot.units=['meters','newton']
281 main_plot.destination=0
282 main_plot.title=self.filepath
287 def deflection(self):
288 #interface for correct plotmanip and others
289 deflectionchunk=DataChunk(self._deflection())
290 return deflectionchunk.ext(),deflectionchunk.ret()