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