API
 
Loading...
Searching...
No Matches
__init__.py
Go to the documentation of this file.
1import logging
2from enum import Enum
3import time
4import numpy as np
5import hcipy as hp
6
7import xconf
8
9from magaox.indi.device import XDevice, BaseConfig
10from magaox.camera import XCam
11from magaox.deformable_mirror import XDeformableMirror
12from magaox.constants import StateCodes
13from magaox.state_manager import XStateMachine
14
15from purepyindi2 import device, properties, constants
16from purepyindi2.messages import DefNumber, DefSwitch, DefLight, DefText
17
18from .utils import *
19
20@xconf.config
22 """
23 """
24 shmim : str = xconf.field(help="Name of the camera device (specifically, the associated shmim, if different).")
25 dark_shmim : str = xconf.field(help="Name of the dark frame shmim associated with this camera device.")
26 stagepos : float = xconf.field(help="Stage position for pupil control.")
27
28@xconf.config
30 """
31 """
32 path : str = xconf.field(help="Path to the calibration files for the pupil alignment.")
33 fwpupil_modes : list[str] = xconf.field(help="List of calibrated fwpupil masks.")
34 fwlyot_modes : list[str] = xconf.field(help="List of calibrated fwlyot masks.")
35
36@xconf.config
38 """
39 """
40 shmim : str = xconf.field(help="Name of the DM device (specifically, the associate shmim, if different).")
41 channel : int = xconf.field(help="The DM channel number.")
42
43@xconf.config
44class pupilCorAlignConfig(BaseConfig):
45 """Automatic coronagraph alignment assistant
46 """
47 camera : CameraConfig = xconf.field(help="Camera to use")
48 dm : DMConfig = xconf.field(help="DM to use")
49 calibration : CalibrationConfig = xconf.field(help='Calibration parameters')
50
51class States(Enum):
52 IDLE = 0
53 FWPUPIL = 1
54 FWLYOT = 2
55 CENTROID = 3
56
57class RefStates(Enum):
58 IDLE = 0
59 FWPUPILREF = 1
60 FWLYOTREF = 2
61 CENTROIDREF = 3
62
63class pupilCorAlign(XDevice):
64 config : pupilCorAlignConfig
65
66 def setup(self):
67 self.log.debug(f"I was configured! See? {self.config=}")
68
69 self.log.info("Found camera: {:s}".format(self.config.camera.shmim))
70 self.camera = XCam(self.config.camera.shmim, use_hcipy=True)
71
72 self.ncpc_act_grid = hp.make_pupil_grid(34, 34/30.0 * np.array([1.0, np.sqrt(2)]))
73 self.ncpc_dm = XDeformableMirror(dm=self.config.dm.shmim, channel=self.config.dm.channel)
74
75 self.client.get_properties('fwscind')
76 self.client.get_properties('fwpupil')
77 self.client.get_properties('fwfpm')
78 self.client.get_properties('fwpupil')
79 self.client.get_properties('fwlyot')
80 self.client.get_properties('camsci1')
81 self.client.get_properties('camsci1-dark')
82 self.client.get_properties('stagesci1')
83 self.client.get_properties('picomotors')
84 self.client.get_properties('ttmperi')
85
86 fsm = properties.TextVector(name='fsm')
87 fsm.add_element(DefText(name='state', _value=StateCodes.INITIALIZED.name))
88 self.add_property(fsm)
89
90 #, 'fwpupilRef', 'fwlyotRef', 'centroidRef']
91 #, self.handle_fwpupil_ref, self.handle_fwlyot_ref, self.handle_centroid_ref]
92 self._state_names = ['idle', 'fwpupil', 'fwlyot', 'centroid']
94 self._state_machine = XStateMachine(self, self._state_names, States, self._state_callbacks)
95
96 self._reference_state_names = ['idle', 'fwpupilRef', 'fwlyotRef', 'centroidRef']
98 self._reference_state_machine = XStateMachine(self, self._reference_state_names, RefStates, self._reference_state_callbacks, 'reference')
99
100 # Should I make these files configurable?
101 # Or should these be auto discoverable?
102 self.pupil_reference = hp.read_field(self.config.calibration.path + "reference_pupil_image.fits")
103 self.xcorr_pupil = XCorrShift(self.pupil_reference, 1001, 101, filter_size=1)
104
105 self.actuator_reference = hp.read_field(self.config.calibration.path + "actuator_reference_image.fits")
106 self.xcorr_actuators = XCorrShift(self.actuator_reference, 1001, 101, filter_size=1)
107
108 self.fwpupil_references = [hp.read_field(self.config.calibration.path + "reference_{:s}_image.fits".format(mode.replace('-', '_'))) for mode in self.config.calibration.fwpupil_modes]
109 self.xcorr_fwpupil = [XCorrShift(im, 1001, 101, filter_size=1) for im in self.fwpupil_references]
110
111 self.fwlyot_references = [hp.read_field(self.config.calibration.path + "reference_{:s}_image.fits".format(mode.replace('-', '_'))) for mode in self.config.calibration.fwlyot_modes]
112 self.xcorr_fwlyot = [XCorrShift(im, 1001, 101, filter_size=1) for im in self.fwlyot_references]
113
114 # Load all the calibration files
115 self.actuators_shift = np.array([0.0, 0.0])
117
118 self.pupil_targets = ['ttmperi.axis1_voltage', 'ttmperi.axis2_voltage']
119 self.fwpupil_targets = ['picomotors.picopupil_pos', 'fwpupil.filter']
120 self.fwlyot_targets = ['picomotors.picolyot_pos', 'fwlyot.filter']
121
122 ttmperi_response_matrix = np.loadtxt(self.config.calibration.path + 'ttmperi_pupil_response_matrix.txt')
123 fwpupil_response_matrix = np.loadtxt(self.config.calibration.path + 'fwpupil_picopupil_bumpmask_response_matrix.txt')[:,::-1]
124 fwlyot_response_matrix = np.loadtxt(self.config.calibration.path + 'fwlyot_picolyot_lyot_response_matrix.txt')[:,::-1]
125
126 #
127 #current_position_picolyot = self.client['picomotors.picolyot_pos.current']
128 #current_position_picopupil = self.client['picomotors.picolyot_pos.current']
129
130 self.ttm_reconstruction_matrix = np.linalg.pinv(ttmperi_response_matrix)
131 self.fwpupil_reconstruction_matrix = np.linalg.pinv(fwpupil_response_matrix)
132 self.fwlyot_reconstruction_matrix = np.linalg.pinv(fwlyot_response_matrix)
133
134 # These also need to be exposed to indi
135 self.num_stack = 1
136 self.gain = 0.25
138 self.amp = 0.25
139 self.sleep_time = 2.0
140
141 nv = properties.NumberVector(name='nstack')
142 nv.add_element(DefNumber(
143 name='current', label='Number of frames', format='%i',
144 min=1, max=150, step=1, _value=1
145 ))
146 nv.add_element(DefNumber(
147 name='target', label='Number of frames', format='%i',
148 min=1, max=150, step=1, _value=1
149 ))
150 self.add_property(nv, callback=self.handle_nstack)
151
152 nv = properties.NumberVector(name='gain')
153 nv.add_element(DefNumber(
154 name='current', label='Loop Gain', format='%.2f',
155 min=0.00, max=1.00, step=0.01, _value=0.10
156 ))
157 nv.add_element(DefNumber(
158 name='target', label='Loop Gain', format='%.2f',
159 min=0.00, max=1.00, step=0.01, _value=0.10
160 ))
161 self.add_property(nv, callback=self.handle_gain)
162
163 # These need to be exposed to INDI
164 self.fwpupil_error = np.array([0., 0.])
165 self.fwlyot_error = np.array([0., 0.])
166
167 nv = properties.NumberVector(name='fwpupil')
168 nv.add_element(DefNumber( #first element
169 name='dx', label='dx', format='%.4f',
170 min=-50.00, max=50.00, step=0.0001, _value=0.21178766
171 ))
172 nv.add_element(DefNumber(
173 name='dy', label='dy', format='%.4f',
174 min=-50.00, max=50.00, step=0.0001, _value=0.19275196
175 ))
176 self.add_property(nv, callback=self.handle_fwpupil_error)
177
178 nv = properties.NumberVector(name='fwlyot')
179 nv.add_element(DefNumber( #first element
180 name='dx', label='dx', format='%.4f',
181 min=-50.00, max=50.00, step=0.0001, _value=0.21178766
182 ))
183 nv.add_element(DefNumber(
184 name='dy', label='dy', format='%.4f',
185 min=-50.00, max=50.00, step=0.0001, _value=0.19275196
186 ))
187 self.add_property(nv, callback=self.handle_fwlyot_error)
188
189 self.taken_reference = False
190 self.log.info(f'pupilCorAlign app is fully setup.')
191
193 '''
194 '''
195 if self.client['fwpupil.fsm.state'] != 'READY':
196 return False
197 elif self.client['fwlyot.fsm.state'] != 'READY':
198 return False
199 elif self.client['picomotors.fsm.state'] != 'READY':
200 return False
201 return True
202
203 def control_position(self, shift, indi_targets, reconstruction_matrix):
204 '''
205 '''
206 if self.stages_are_ready():
207 current_positions = [self.client['{:s}.current'.format(indi_targets[i])] for i in [0, 1]]
208 cmd = reconstruction_matrix.dot(shift)
209 self.client['{:s}.target'.format(indi_targets[0])] = current_positions[0] - self.gain * cmd[0]
210 self.client['{:s}.target'.format(indi_targets[1])] = current_positions[1] - self.gain * cmd[1]
211 time.sleep(self.sleep_time)
212
213 def measure_position(self, correlator):
214 '''
215 '''
216 im = self.camera.grab_stack(self.num_stack, check_before_wait=True)
217 shift = correlator.measure(im) - self.actuators_shift
218 return shift
219
221 '''
222 '''
223 actuator_im = 0.
224 for i in range(self.pattern_repeat):
225 for s in [-1, 1]:
226 self.ncpc_dm.actuators += s * self.amp * self.actuator_probe_pattern
227 self.ncpc_dm.send(0.05)
228
229 self.camera.grab_stack(2, check_before_wait=True)
230 actuator_im += s * self.camera.grab_stack(self.num_stack, check_before_wait=True)
231
232 self.ncpc_dm.actuators -= s * self.amp * self.actuator_probe_pattern
233 self.ncpc_dm.send()
234
235 return actuator_im
236
237 def get_current_state(self, indi_property):
238 '''
239 '''
240 for name in self.client[indi_property]:
241 if self.client[indi_property + '.' + name] == constants.SwitchState.ON:
242 return name
243
244 def handle_fwpupil(self):
245 '''
246 '''
247 for mi, m in enumerate(self.config.calibration.fwpupil_modes):
248 # Check if we are in the correct position
249 if self.client['fwpupil.filterName.{:s}'.format(m)] == constants.SwitchState.ON:
250 self.fwpupil_error = self.measure_position(self.xcorr_fwpupil[mi])
251 if self.taken_reference:
253 else:
254 self.log.info(f'No reference taken. Take centroid reference first.')
255
256 def handle_fwlyot(self):
257 '''
258 '''
259 for mi, m in enumerate(self.config.calibration.fwlyot_modes):
260 # Check if we are in the correct position
261 if self.client['fwlyot.filterName.{:s}'.format(m)] == constants.SwitchState.ON:
262 self.fwlyot_error = self.measure_position(self.xcorr_fwlyot[mi])
263 if self.taken_reference:
265 else:
266 self.log.info(f'No reference taken. Take centroid reference first.')
267
269 '''
270 '''
271 actuator_im = self.measure_actuator()
272 self.actuators_shift = self.xcorr_actuators.measure(actuator_im)
273 self.taken_reference = True
274 self._state_machine.transition_to_idle()
275
277 '''
278 '''
279 im = self.camera.grab_stack(self.num_stack, check_before_wait=True)
280 current_name = self.get_current_state('fwpupil.filterName')
281 filename = "reference_{:s}_image.fits".format(current_name.replace('-', '_'))
282 hp.write_field(im, self.config.calibration.path + filename)
283 self._reference_state_machine.transition_to_idle()
284
286 '''
287 '''
288 print("entering handle fwlyot ref")
289 im = self.camera.grab_stack(self.num_stack, check_before_wait=True)
290 current_name = self.get_current_state('fwlyot.filterName')
291 filename = "reference_{:s}_image.fits".format(current_name.replace('-', '_'))
292 print(current_name)
293
294 print("Writing file to {:s}".format(self.config.calibration.path + filename))
295 hp.write_field(im, self.config.calibration.path + filename)
296
297 print("transition back")
298 self._reference_state_machine.transition_to_idle()
299
301 '''
302 '''
303 actuator_im = self.measure_actuator()
304 filename = "actuator_reference_image.fits"
305 hp.write_field(im, self.config.calibration.path + filename)
306 self._reference_state_machine.transition_to_idle()
307
308 def loop(self):
309 '''
310 '''
311 self._state_machine.loop()
313 self.update_properties()
314
316 '''
317 '''
318 self.properties['fwpupil']['dx'] = float(self.fwpupil_error[0])
319 self.properties['fwpupil']['dy'] = float(self.fwpupil_error[1])
320 self.update_property(self.properties['fwpupil'])
321
322 self.properties['fwlyot']['dx'] = float(self.fwlyot_error[0])
323 self.properties['fwlyot']['dy'] = float(self.fwlyot_error[1])
324 self.update_property(self.properties['fwlyot'])
325
326 def handle_nstack(self, existing_property, new_message):
327 '''
328 '''
329 if 'target' in new_message and new_message['target'] != existing_property['current']:
330 existing_property['current'] = new_message['target']
331 existing_property['target'] = new_message['target']
332 self.num_stack = int(new_message['target'])
333 self.log.debug(f'now averaging over {self.num_stack} frames')
334 self.update_property(existing_property)
335
336 def handle_gain(self, existing_property, new_message):
337 '''
338 '''
339 if 'target' in new_message and new_message['target'] != existing_property['current']:
340 existing_property['current'] = new_message['target']
341 existing_property['target'] = new_message['target']
342 self.gain = float(new_message['target'])
343 self.log.debug(f'new feedback gain {self.gain} ')
344 self.update_property(existing_property)
345
346 # Ask joseph for a nicer way to deal with this
347 def handle_fwpupil_error(self, existing_property, new_message):
348 pass
349
350 def handle_fwlyot_error(self, existing_property, new_message):
351 pass
352
353main = pupilCorAlign.console_app
handle_fwpupil_error(self, existing_property, new_message)
Definition __init__.py:347
handle_nstack(self, existing_property, new_message)
Definition __init__.py:326
handle_fwlyot_error(self, existing_property, new_message)
Definition __init__.py:350
control_position(self, shift, indi_targets, reconstruction_matrix)
Definition __init__.py:203
handle_gain(self, existing_property, new_message)
Definition __init__.py:336
get_current_state(self, indi_property)
Definition __init__.py:237
measure_position(self, correlator)
Definition __init__.py:213
pupilCorAlignConfig config
Definition __init__.py:64
make_ncpc_alignment_poke_pattern()
Definition utils.py:67