Ran update_copyright.py, updating all the copyright blurbs and adding AUTHORS.
[hooke.git] / hooke / driver / picoforcealt.py
1 # Copyright (C) 2006-2010 Alberto Gomez-Casado
2 #                         Massimo Sandal <devicerandom@gmail.com>
3 #                         W. Trevor King <wking@drexel.edu>
4 #
5 # This file is part of Hooke.
6 #
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.
11 #
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.
16 #
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/>.
20
21 """Library for interpreting Picoforce force spectroscopy files.
22
23 An alternave implementation of :mod:`hooke.driver.picoforce`.
24 """
25
26 import re, struct
27 from scipy import arange
28
29 from .. import curve as lhc
30
31 __version__='0.0.0.20081706'
32
33
34
35 class DataChunk(list):
36     #Dummy class to provide ext and ret methods to the data list.
37
38     def ext(self):
39         halflen=(len(self)/2)
40         return self[0:halflen]
41
42     def ret(self):
43         halflen=(len(self)/2)
44         return self[halflen:]
45
46 class picoforcealtDriver(lhc.Driver):
47
48     #Construction and other special methods
49
50     def __init__(self,filename):
51         '''
52         constructor method
53         '''
54
55         self.textfile=file(filename)
56         self.binfile=file(filename,'rb')
57
58         #The 0,1,2 data chunks are:
59         #0: D (vs T)
60         #1: Z (vs T)
61         #2: D (vs Z)
62
63         self.forcechunk=0
64         self.distancechunk=1
65         #TODO eliminate the need to set chunk numbers
66
67         self.filepath=filename
68         self.debug=True
69
70         self.filetype='picoforce'
71         self.experiment='smfs'
72
73
74
75     def _get_samples_line(self):
76         '''
77         Gets the samples per line parameters in the file, to understand trigger behaviour.
78         '''
79         self.textfile.seek(0)
80
81         samps_expr=re.compile(".*Samps")
82
83         samps_values=[]
84         for line in self.textfile.readlines():
85             if samps_expr.match(line):
86                 try:
87                     samps=int(line.split()[2]) #the third word splitted is the offset (in bytes)
88                     samps_values.append(samps)
89                 except:
90                     pass
91
92                 #We raise a flag for the fact we meet an offset, otherwise we would take spurious data length arguments.
93
94         return int(samps_values[0])
95
96     def _get_chunk_coordinates(self):
97         '''
98         This method gets the coordinates (offset and length) of a data chunk in our
99         Picoforce file.
100
101         It returns a list containing two tuples:
102         the first element of each tuple is the data_offset, the second is the corresponding
103         data size.
104
105         In near future probably each chunk will get its own data structure, with
106         offset, size, type, etc.
107         '''
108         self.textfile.seek(0)
109
110         offset_expr=re.compile(".*Data offset")
111         length_expr=re.compile(".*Data length")
112
113         data_offsets=[]
114         data_sizes=[]
115         flag_offset=0
116
117         for line in self.textfile.readlines():
118
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.
123                 flag_offset=1
124
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.
130                 flag_offset=0
131
132         return zip(data_offsets,data_sizes)
133
134     def _get_data_chunk(self,whichchunk):
135         '''
136         reads a data chunk and converts it in 16bit signed int.
137         '''
138         offset,size=self._get_chunk_coordinates()[whichchunk]
139
140
141         self.binfile.seek(offset)
142         raw_chunk=self.binfile.read(size)
143
144         my_chunk=[]
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)
151
152         return DataChunk(my_chunk)
153
154     def _force(self):
155         #returns force vector
156         Kspring=self.get_spring_constant()
157         return DataChunk([(meter*Kspring) for meter in self._deflection()])
158
159     def _deflection(self):
160         #for internal use (feeds _force)
161         voltrange=1
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]
166         
167         return deflect
168
169
170     def _Z(self):
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()
175
176         xstep=senszscan*rampsize/sampsline*10**(-9)
177
178         xext=arange(sampsline*xstep,0,-xstep)
179         xret=arange(sampsline*xstep,0,-xstep)
180
181         return DataChunk(xext.tolist()+xret.tolist())
182
183     def _get_Z_scale(self):
184         self.textfile.seek(0)
185         expr=re.compile(".*@4:Z scale")
186
187         for line in self.textfile.readlines():
188             if expr.match(line):
189                 zscale=float((line.split()[5]).strip("() []"))
190                 break
191         return zscale
192
193     def _get_rampsize(self):
194         self.textfile.seek(0)
195         expr=re.compile(".*@4:Ramp size:")
196
197         for line in self.textfile.readlines():
198             if expr.match(line):
199                 zsens=float((line.split()[7]).strip("() []"))
200                 break
201         return zsens
202
203     def _get_Z_scan_sens(self):
204         self.textfile.seek(0)
205         expr=re.compile(".*@Sens. Zsens")
206
207         for line in self.textfile.readlines():
208             if expr.match(line):
209                 zsens=float((line.split()[3]).strip("() []"))
210                 break
211         return zsens
212
213
214
215     def get_deflection_sensitivity(self):
216         '''
217         gets deflection sensitivity
218         '''
219         self.textfile.seek(0)
220
221         def_sensitivity_expr=re.compile(".*@Sens. DeflSens")
222
223         for line in self.textfile.readlines():
224             if def_sensitivity_expr.match(line):
225                 def_sensitivity=float(line.split()[3])
226                 break
227         #return it in SI units (that is: m/V, not nm/V)
228         return def_sensitivity*(10**(-9))
229
230     def get_spring_constant(self):
231         '''
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...
235         '''
236         self.textfile.seek(0)
237
238         springconstant_expr=re.compile(".*Spring Constant")
239
240         constants=[]
241
242         for line in self.textfile.readlines():
243             if springconstant_expr.match(line):
244                 constants.append(float(line.split()[2]))
245
246         return constants[0]
247
248     def is_me(self):
249         '''
250         self-identification of file type magic
251         '''
252         curve_file=file(self.filepath)
253         header=curve_file.read(30)
254         curve_file.close()
255
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]]
259             return True
260         else:
261             return False
262
263     def close_all(self):
264         '''
265         Explicitly closes all files
266         '''
267         self.textfile.close()
268         self.binfile.close()
269
270     def default_plots(self):
271         '''
272         creates the default PlotObject
273         '''
274         force=self._force()
275         zdomain=self._Z()
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
283
284
285         return [main_plot]
286
287     def deflection(self):
288         #interface for correct plotmanip and others
289         deflectionchunk=DataChunk(self._deflection())
290         return deflectionchunk.ext(),deflectionchunk.ret()