8f7a494fe322c57b0559ecd2da3b84f85a47ea1f
[hooke.git] / hooke / driver / picoforcealt.py
1 '''
2 libpicoforce.py
3
4 Library for interpreting Picoforce force spectroscopy files. Alternate version
5
6 Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy).
7 Copyright (C) 2008 Alberto Gomez-Casado (University of Twente, Netherlands).
8
9 This program is released under the GNU General Public License version 2.
10 '''
11
12 import re, struct
13 from scipy import arange
14
15 from .. import curve as lhc
16
17 __version__='0.0.0.20081706'
18
19
20
21 class DataChunk(list):
22     #Dummy class to provide ext and ret methods to the data list.
23
24     def ext(self):
25         halflen=(len(self)/2)
26         return self[0:halflen]
27
28     def ret(self):
29         halflen=(len(self)/2)
30         return self[halflen:]
31
32 class picoforcealtDriver(lhc.Driver):
33
34     #Construction and other special methods
35
36     def __init__(self,filename):
37         '''
38         constructor method
39         '''
40
41         self.textfile=file(filename)
42         self.binfile=file(filename,'rb')
43
44         #The 0,1,2 data chunks are:
45         #0: D (vs T)
46         #1: Z (vs T)
47         #2: D (vs Z)
48
49         self.forcechunk=0
50         self.distancechunk=1
51         #TODO eliminate the need to set chunk numbers
52
53         self.filepath=filename
54         self.debug=True
55
56         self.filetype='picoforce'
57         self.experiment='smfs'
58
59
60
61     def _get_samples_line(self):
62         '''
63         Gets the samples per line parameters in the file, to understand trigger behaviour.
64         '''
65         self.textfile.seek(0)
66
67         samps_expr=re.compile(".*Samps")
68
69         samps_values=[]
70         for line in self.textfile.readlines():
71             if samps_expr.match(line):
72                 try:
73                     samps=int(line.split()[2]) #the third word splitted is the offset (in bytes)
74                     samps_values.append(samps)
75                 except:
76                     pass
77
78                 #We raise a flag for the fact we meet an offset, otherwise we would take spurious data length arguments.
79
80         return int(samps_values[0])
81
82     def _get_chunk_coordinates(self):
83         '''
84         This method gets the coordinates (offset and length) of a data chunk in our
85         Picoforce file.
86
87         It returns a list containing two tuples:
88         the first element of each tuple is the data_offset, the second is the corresponding
89         data size.
90
91         In near future probably each chunk will get its own data structure, with
92         offset, size, type, etc.
93         '''
94         self.textfile.seek(0)
95
96         offset_expr=re.compile(".*Data offset")
97         length_expr=re.compile(".*Data length")
98
99         data_offsets=[]
100         data_sizes=[]
101         flag_offset=0
102
103         for line in self.textfile.readlines():
104
105             if offset_expr.match(line):
106                 offset=int(line.split()[2]) #the third word splitted is the offset (in bytes)
107                 data_offsets.append(offset)
108                 #We raise a flag for the fact we meet an offset, otherwise we would take spurious data length arguments.
109                 flag_offset=1
110
111             #same for the data length
112             if length_expr.match(line) and flag_offset:
113                 size=int(line.split()[2])
114                 data_sizes.append(size)
115                 #Put down the offset flag until the next offset is met.
116                 flag_offset=0
117
118         return zip(data_offsets,data_sizes)
119
120     def _get_data_chunk(self,whichchunk):
121         '''
122         reads a data chunk and converts it in 16bit signed int.
123         '''
124         offset,size=self._get_chunk_coordinates()[whichchunk]
125
126
127         self.binfile.seek(offset)
128         raw_chunk=self.binfile.read(size)
129
130         my_chunk=[]
131         for data_position in range(0,len(raw_chunk),2):
132             data_unit_bytes=raw_chunk[data_position:data_position+2]
133             #The unpack function converts 2-bytes in a signed int ('h').
134             #we use output[0] because unpack returns a 1-value tuple, and we want the number only
135             data_unit=struct.unpack('h',data_unit_bytes)[0]
136             my_chunk.append(data_unit)
137
138         return DataChunk(my_chunk)
139
140     def _force(self):
141         #returns force vector
142         Kspring=self.get_spring_constant()
143         return DataChunk([(meter*Kspring) for meter in self._deflection()])
144
145     def _deflection(self):
146         #for internal use (feeds _force)
147         voltrange=1
148         z_scale=self._get_Z_scale()
149         deflsensitivity=self.get_deflection_sensitivity()
150         volts=[((float(lsb))*voltrange*z_scale) for lsb in self.data_chunks[self.forcechunk]]
151         deflect=[volt*deflsensitivity for volt in volts]
152         
153         return deflect
154
155
156     def _Z(self):
157         #returns distance vector (calculated instead than from data chunk)
158         rampsize=self._get_rampsize()
159         sampsline=self._get_samples_line()
160         senszscan=self._get_Z_scan_sens()
161
162         xstep=senszscan*rampsize/sampsline*10**(-9)
163
164         xext=arange(sampsline*xstep,0,-xstep)
165         xret=arange(sampsline*xstep,0,-xstep)
166
167         return DataChunk(xext.tolist()+xret.tolist())
168
169     def _get_Z_scale(self):
170         self.textfile.seek(0)
171         expr=re.compile(".*@4:Z scale")
172
173         for line in self.textfile.readlines():
174             if expr.match(line):
175                 zscale=float((line.split()[5]).strip("() []"))
176                 break
177         return zscale
178
179     def _get_rampsize(self):
180         self.textfile.seek(0)
181         expr=re.compile(".*@4:Ramp size:")
182
183         for line in self.textfile.readlines():
184             if expr.match(line):
185                 zsens=float((line.split()[7]).strip("() []"))
186                 break
187         return zsens
188
189     def _get_Z_scan_sens(self):
190         self.textfile.seek(0)
191         expr=re.compile(".*@Sens. Zsens")
192
193         for line in self.textfile.readlines():
194             if expr.match(line):
195                 zsens=float((line.split()[3]).strip("() []"))
196                 break
197         return zsens
198
199
200
201     def get_deflection_sensitivity(self):
202         '''
203         gets deflection sensitivity
204         '''
205         self.textfile.seek(0)
206
207         def_sensitivity_expr=re.compile(".*@Sens. DeflSens")
208
209         for line in self.textfile.readlines():
210             if def_sensitivity_expr.match(line):
211                 def_sensitivity=float(line.split()[3])
212                 break
213         #return it in SI units (that is: m/V, not nm/V)
214         return def_sensitivity*(10**(-9))
215
216     def get_spring_constant(self):
217         '''
218         gets spring constant.
219         We actually find *three* spring constant values, one for each data chunk (F/t, Z/t, F/z).
220         They are normally all equal, but we retain all three for future...
221         '''
222         self.textfile.seek(0)
223
224         springconstant_expr=re.compile(".*Spring Constant")
225
226         constants=[]
227
228         for line in self.textfile.readlines():
229             if springconstant_expr.match(line):
230                 constants.append(float(line.split()[2]))
231
232         return constants[0]
233
234     def is_me(self):
235         '''
236         self-identification of file type magic
237         '''
238         curve_file=file(self.filepath)
239         header=curve_file.read(30)
240         curve_file.close()
241
242         if header[2:17] == 'Force file list': #header of a picoforce file
243             #here DONT translate chunk
244             self.data_chunks=[self._get_data_chunk(num) for num in [0,1,2]]
245             return True
246         else:
247             return False
248
249     def close_all(self):
250         '''
251         Explicitly closes all files
252         '''
253         self.textfile.close()
254         self.binfile.close()
255
256     def default_plots(self):
257         '''
258         creates the default PlotObject
259         '''
260         force=self._force()
261         zdomain=self._Z()
262         samples=self._get_samples_line()
263         main_plot=lhc.PlotObject()
264         main_plot.vectors=[[zdomain.ext()[0:samples], force.ext()[0:samples]],[zdomain.ret()[0:samples], force.ret()[0:samples]]]
265         main_plot.normalize_vectors()
266         main_plot.units=['meters','newton']
267         main_plot.destination=0
268         main_plot.title=self.filepath
269
270
271         return [main_plot]
272
273     def deflection(self):
274         #interface for correct plotmanip and others
275         deflectionchunk=DataChunk(self._deflection())
276         return deflectionchunk.ext(),deflectionchunk.ret()