1 import ConfigParserRT
2 import linear
3 import logging
4 import math
5 from rotator import angle
6
8 """
9 Object representing the DAZLE/CIRPASS camera's detector focus/tilt
10 mechanism which consists of a three point kinematic mount.
11 """
12 - def __init__(self, det1, det2, det3, offset1, offset2, offset3, name):
13 """
14 Arguments:
15
16 det1: A linear.linear object corresponding to detector motor 1.
17 det2: A linear.linear object corresponding to detector motor 2.
18 det3: A linear.linear object corresponding to detector motor 3.
19 offset1: Offset from the nominal zero position to zero the tilt.
20 offset2: Offset from the nominal zero position to zero the tilt.
21 offset3: Offset from the nominal zero position to zero the tilt.
22 name: An identifier used for logging purposes.
23 """
24 self.logger = logging.getLogger(name)
25 if isinstance(det1, linear.linear):
26 self.det1 = det1
27 else:
28 self.logger.critical('det1 is not a linear.linear object!')
29 if isinstance(det2, linear.linear):
30 self.det2 = det2
31 else:
32 self.logger.critical('det2 is not a linear.linear object!')
33 if isinstance(det3, linear.linear):
34 self.det3 = det3
35 else:
36 self.logger.critical('det3 is not a linear.linear object!')
37
38 self.offset1 = int(offset1)
39 self.offset2 = int(offset2)
40 self.offset3 = int(offset3)
41
43 """
44 Should be called by the parent instrument object's destructor
45 to enable status information to be saved to file on exit.
46
47 Takes a ConfigParserRT.SafeConfigParser object as argument.
48 """
49 self.logger.debug("updateConfig() called")
50 if isinstance(config, ConfigParserRT.SafeConfigParser):
51 config.set(self.name, 'offset1', str(self.offset1))
52 config.set(self.name, 'offset2', str(self.offset2))
53 config.set(self.name, 'offset3', str(self.offset3))
54 else:
55 self.logger.critical("Invalid argument to updateConfig(%s)" \
56 % repr(config))
57 self.logger.critical("Unable to save status data")
58
60 """
61 Calls the setupLinear() methods of each of the three mechanism motors
62 to establish the centre of the range of movement, limit switch
63 positions and software limits.
64
65 Due to slight variations in limit switch positions this will not
66 properly zero the tilts of the detector, in order to do this a target
67 screen should be used together with manual tilt adjustments, and then
68 zeroTilts() called to set the required offsets for each motor.
69
70 Care should be taken in using this routine, and it should not be run
71 unless necessary. As each of the motors is exercised over its full
72 range in turn large tilts will occur and there are some combinations
73 of motor positions where this can result in the detector box contacting
74 the LN2 can. Attempting to ensure that the motors, and in particular
75 motor 3, are near the middle of their range before beginning this
76 process will minimise risk.
77 """
78 det1.setupLinear()
79 det3.setupLinear()
80 det2.setupLinear()
81
83 """
84 Returns the current focus position (in microns) and the tilts along
85 the two diagonals of the detector (as rotator.angle objects).
86 """
87 pos1 = det1.getPosition() - self.offset1
88 pos2 = det2.getPosition() - self.offset2
89 pos3 = det3.getPosition() - self.offset3
90 focus = (pos1 + pos3) / 2.0
91 tilt1 = angle(math.arctan2(pos1 - pos3, 152000))
92 tilt2 = angle(math.arctan2(pos2 - focus, 76000))
93 return focus, tilt1, tilt2
94
96 """
97 Similar to getPosition(), but instead prints a human readable
98 string decribing the focus position (microns) and the tiles (degrees).
99 """
100 focus, tilt1, tilt2 = self.getPosition()
101 print 'Focus: %i, tilt1: %f, tilt2: %f' % \
102 (focus, tilt1.getAngle('d'), tilt2.getAngle('d'))
103
105 """
106 Call this function after manually zeroing the tilt of the detector
107 by imaging a test target screen. This adjusts the 3 offsets used in
108 calculating the detector position so that afterwards the tilt
109 calculations will be correct.
110 """
111
112 pos1 = det1.getPosition() - self.offset1
113 pos3 = det3.getPosition() - self.offset3
114 focus = (pos1 + pos3) / 2.0
115
116 self.offset1 = det1.getPosition() - focus
117 self.offset2 = det2.getPosition() - focus
118 self.offset3 = det3.getPosition() - focus
119
120 - def moveTo(self, focus, tilt1=0, tilt2=0):
121 """
122 Arguments:
123
124 focus: new focus position, in microns.
125 tilt1: new tilt along the 1-3 diagonal, in degrees, default 0
126 tilt2: new tilt along the 3 diagonal, in degrees, default 0.
127 """
128 focus = float(focus)
129
130 tilt1 = math.radians(float(tilt1))
131 tilt2 = math.radians(float(tilt2))
132
133 new1 = focus + 76000 * math.tan(tilt1) + offset1
134 new3 = focus - 76000 * math.tan(tilt1) + offset3
135 new2 = focus + 76000 * math.tan(tilt2) + offset2
136
137 low1, up1 = self.det1.getLimits()
138 low2, up2 = self.det2.getLimits()
139 low3, up3 = self.det3.getLimits()
140 if new1 < low1:
141 self.logger.critical(\
142 'Move exceeds lower limit of motor 1, ignored!')
143 return
144 if new1 > up1:
145 self.logger.critical(\
146 'Move exceeds upper limit of motor 1, ignored!')
147 return
148 if new2 < low2:
149 self.logger.critical(\
150 'Move exceeds lower limit of motor 2, ignored!')
151 return
152 if new2 > up2:
153 self.logger.critical(\
154 'Move exceeds upper limit of motor 2, ignored!')
155 return
156 if new3 < low3:
157 self.logger.critical(\
158 'Move exceeds lower limit of motor 3, ignored!')
159 return
160 if new3 > up3:
161 self.logger.critical(\
162 'Move exceeds upper limit of motor 3, ignored!')
163 return
164
165
166 cur1 = self.det1.getPosition()
167 cur2 = self.det2.getPosition()
168 cur3 = self.det3.getPosition()
169 while cur1 != new1 or cur2 != new2 or cur3 != new3:
170 if abs(new1-cur1) > 1000:
171 self.det1.moveBy(1000 * (new1-cur1) / abs(new1-cur1))
172 elif new1 != cur1:
173 self.det1.moveTo(new1)
174
175 if abs(new2-cur2) > 1000:
176 self.det2.moveBy(1000 * (new2-cur2) / abs(new2-cur2))
177 elif new2 != cur2:
178 self.det2.moveTo(new2)
179
180 if abs(new3-cur3) > 1000:
181 self.det3.moveBy(1000 * (new3-cur3) / abs(new3-cur3))
182 elif new3 != cur3:
183 self.det3.moveTo(new3)
184
185 cur1 = self.det1.getPosition()
186 cur2 = self.det2.getPosition()
187 cur3 = self.det3.getPosition()
188
189 - def moveBy(dfocus, dtilt1=0, dtilt2=0):
190 """
191 Arguments:
192
193 dfocus: The amount that the focus position should change by, in
194 microns.
195 dtilt1: The amount that tilt about the first diagonal should change by
196 in degrees
197 dtilt2: The amount that tilt about the second diagonal should change by
198 in degrees.
199 """
200 curr_focus, curr_tilt1, curr_tilt2 = self.getPosition()
201 new_focus = curr_focus + float(dfocus)
202 new_tilt1 = curr_tilt1.getAngle('d') + float(dtilt1)
203 new_tilt2 = curr_tilt2.getAngle('d') + flaot(dtilt2)
204 self.moveTo(new_focus, new_tilt1, new_tilt2)
205
207 """
208 Returns a tuple containing the upper and lower focus position limits
209 for zero tilts. If tilts are non-zero this range will be reduced, and
210 likewise the focus position will determine the range of available
211 tilts. This function doesn't try to work those effects out.
212 """
213 lowers, uppers = zip(self.det1.getLimits(), self.det2.getLimits(), \
214 self.det3.getLimits())
215 lowers = lowers - (self.offset1, self.offset2, self.offset3)
216 uppers = uppers - (self.offset1, self.offset2, self.offset3)
217 return min(lowers), max(uppers)
218