1
2
3 """
4 Define unit conversion support for NeXus style units.
5
6 The unit format is somewhat complicated. There are variant spellings
7 and incorrect capitalization to worry about, as well as forms such as
8 "mili*metre" and "1e-7 seconds".
9
10 This is a minimal implementation of units including only what I happen to
11 need now. It does not support the complete dimensional analysis provided
12 by the package udunits on which NeXus is based, or even the units used
13 in the NeXus definition files.
14
15 Unlike other units packages, such as that in DANSE, this package does
16 not carry the units along with the value, but merely provides a conversion
17 function for transforming values.
18
19 Usage example:
20
21 u = nxsunit.Converter('mili*metre') # Units stored in mm
22 v = u(3000,'m') # Convert the value 3000 mm into meters
23
24 NeXus example:
25
26 # Load sample orientation in radians regardless of how it is stored.
27 # 1. Open the path
28 file.openpath('/entry1/sample/sample_orientation')
29 # 2. scan the attributes, retrieving 'units'
30 units = [for attr,value in file.attrs() if attr == 'units']
31 # 3. set up the converter (assumes that units actually exists)
32 u = nxsunit.Converter(units[0])
33 # 4. read the data and convert to the correct units
34 v = u(file.read(),'radians')
35
36 This is a standalone module, not relying on either DANSE or NeXus, and
37 can be used for other unit conversion tasks.
38
39 Note: minutes are used for angle and seconds are used for time. We
40 cannot tell what the correct interpretation is without knowing something
41 about the fields themselves. If this becomes an issue, we will need to
42 allow the application to set the dimension for the units rather than
43 getting the dimension from the units as we are currently doing.
44 """
45
46
47
48
49
50
51
52
53 from __future__ import division
54 import math
55
56
57
58
59
61 """
62 Construct standard SI names for the given unit.
63 Builds e.g.,
64 s, ns
65 second, nanosecond, nano*second
66 seconds, nanoseconds
67 Includes prefixes for femto through peta.
68
69 Ack! Allows, e.g., Coulomb and coulomb even though Coulomb is not
70 a unit because some NeXus files store it that way!
71
72 Returns a dictionary of names and scales.
73 """
74 prefix = dict(peta=1e15,tera=1e12,giga=1e9,mega=1e6,kilo=1e3,
75 deci=1e-1,centi=1e-2,milli=1e-3,mili=1e-3,micro=1e-6,
76 nano=1e-9,pico=1e-12,femto=1e-15)
77 short_prefix = dict(P=1e15,T=1e12,G=1e9,M=1e6,k=1e3,
78 d=1e-1,c=1e-2,m=1e-3,u=1e-6,
79 n=1e-9,p=1e-12,f=1e-15)
80 map = {abbr:1}
81 map.update([(P+abbr,scale) for (P,scale) in short_prefix.iteritems()])
82 for name in [unit,unit.capitalize()]:
83 map.update({name:1,name+'s':1})
84 map.update([(P+name,scale) for (P,scale) in prefix.iteritems()])
85 map.update([(P+'*'+name,scale) for (P,scale) in prefix.iteritems()])
86 map.update([(P+name+'s',scale) for (P,scale) in prefix.iteritems()])
87 return map
88
90 """
91 Construct names for the given units. Builds singular and plural form.
92 """
93 map = {}
94 map.update([(name,scale) for name,scale in kw.iteritems()])
95 map.update([(name+'s',scale) for name,scale in kw.iteritems()])
96 return map
97
99
100 distance = _build_metric_units('meter','m')
101 distance.update(_build_metric_units('metre','m'))
102 distance.update(_build_plural_units(micron=1e-6, Angstrom=1e-10))
103 distance.update({'A':1e-10, 'Ang':1e-10})
104
105
106
107 time = _build_metric_units('second','s')
108 time.update(_build_plural_units(hour=3600,day=24*3600,week=7*24*3600))
109
110
111
112 angle = _build_plural_units(degree=1, minute=1/60.,
113 arcminute=1/60., arcsecond=1/3600., radian=180/math.pi)
114 angle.update(deg=1, arcmin=1/60., arcsec=1/3600., rad=180/math.pi)
115
116 frequency = _build_metric_units('hertz','Hz')
117 frequency.update(_build_metric_units('Hertz','Hz'))
118 frequency.update(_build_plural_units(rpm=1/60.))
119
120
121
122 temperature = _build_metric_units('kelvin','K')
123 temperature.update(_build_metric_units('Kelvin','K'))
124
125 charge = _build_metric_units('coulomb','C')
126 charge.update({'microAmp*hour':0.0036})
127
128 sld = { '10^-6 Angstrom^-2': 1e-6, 'Angstrom^-2': 1}
129 Q = { 'invAng': 1, 'invAngstroms': 1,
130 '10^-3 Angstrom^-1': 1e-3, 'nm^-1': 10 }
131
132
133
134
135 unknown = {None:1, '???':1, '': 1, 'a.u.':1}
136
137 dims = [unknown, distance, time, angle, frequency,
138 temperature, charge, sld, Q]
139 return dims
140
142 """
143 Unit converter for NeXus style units.
144
145 """
146
147 scalemap = None
148 scalebase = 1
149 dims = _build_all_units()
150
161
162
163 - def scale(self, units=""):
166
168
169
170
171
172
173 if units == "" or self.scalemap is None: return value
174 try:
175 return value * (self.scalebase/self.scalemap[units])
176 except KeyError:
177 raise KeyError("%s not in %s"%(units," ".join(self.scalemap.keys())))
178
180 if expect != get: raise ValueError, "Expected %s but got %s"%(expect,get)
181
182
184 _check(2,Converter('mm')(2000,'m'))
185 _check(0.003,Converter('microseconds')(3,units='ms'))
186 _check(45,Converter('nanokelvin')(45))
187
188 _check(0.5,Converter('seconds')(1800,units='hours'))
189 _check(2.5,Converter('a.u.')(2.5,units=''))
190
191 if __name__ == "__main__":
192 test()
193