class AnalogInputViewer(traits.HasTraits): channels = traits.List usb_device_number2index = traits.Property(depends_on='channels') @traits.cached_property def _get_usb_device_number2index(self): result = {} for i, channel in enumerate(self.channels): result[channel.device_channel_num] = i return result traits_view = View( Group( Item( 'channels', style='custom', editor=ListEditor(rows=3, editor=InstanceEditor(), style='custom'), resizable=True, )), resizable=True, width=800, height=600, title='Analog Input', ) def __init__(self, *args, **kwargs): super(AnalogInputViewer, self).__init__(*args, **kwargs) for usb_channel_num in [0, 1, 2, 3]: self.channels.append( AnalogInputChannelViewer(device_channel_num=usb_channel_num))
class MappedFeature(RangeFeature): """ Defines a feature that is selectable """ value = traits.CInt(0, editor = ui.EnumEditor(name = 'values')) low = traits.CInt(0) high = traits.CInt(1) map = traits.Dict({0:'feature0', 1: 'feature'}) values = traits.Property(traits.Dict, depends_on = 'low,high,map') def _get_values(self): d = {} for i in range(self.low,self.high + 1): d[i] = self.map[i] return d
class Box(HasTraits): # left, bottom, width, height bounds = traits.Array('d', (4, )) left = traits.Property(Float) bottom = traits.Property(Float) width = traits.Property(Float) height = traits.Property(Float) right = traits.Property(Float) # read only top = traits.Property(Float) # read only def _bounds_default(self): return [0.0, 0.0, 1.0, 1.0] def _get_left(self): return self.bounds[0] def _set_left(self, left): oldbounds = self.bounds[:] self.bounds[0] = left self.trait_property_changed('bounds', oldbounds, self.bounds) def _get_bottom(self): return self.bounds[1] def _set_bottom(self, bottom): oldbounds = self.bounds[:] self.bounds[1] = bottom self.trait_property_changed('bounds', oldbounds, self.bounds) def _get_width(self): return self.bounds[2] def _set_width(self, width): oldbounds = self.bounds[:] self.bounds[2] = width self.trait_property_changed('bounds', oldbounds, self.bounds) def _get_height(self): return self.bounds[2] def _set_height(self, height): oldbounds = self.bounds[:] self.bounds[2] = height self.trait_property_changed('bounds', oldbounds, self.bounds) def _get_right(self): return self.left + self.width def _get_top(self): return self.bottom + self.height def _bounds_changed(self, old, new): pass
class IntRangeFeature(traits.HasTraits): """ Defines a feature that is settable by slider """ value = traits.Range('low','high','value_') value_ = traits.CInt(0.) low = traits.CInt(-10000.) high = traits.CInt(10000.) is_settable = traits.Bool(False) id = traits.Property(depends_on = 'name') index = traits.Int(0) name = 'gain' view = ui.View(ui.Item('value', show_label = False, style = 'custom')) def _get_id(self): return _SINGLE_VALUED_FEATURES.get(self.name)
class ROI(traits.HasTraits): top = create_int_multirange_feature('top',0) left = create_int_multirange_feature('left',1) width = create_int_multirange_feature('width',2) height = create_int_multirange_feature('height',3) values = traits.Property(traits.Int, depends_on = 'top.value,left.value,width.value,height.value') def _get_values(self): return self.top.value, self.left.value, self.width.value, self.height.value view = ui.View( ui.Item('top', style = 'custom'), ui.Item('left', style = 'custom'), ui.Item('width', style = 'custom'), ui.Item('height', style = 'custom'), )
class CameraUI(traits.HasTraits): """Camera settings defines basic camera settings """ camera_control = traits.Instance(Camera, transient = True) cameras = traits.List([_NO_CAMERAS],transient = True) camera = traits.Any(value = _NO_CAMERAS, desc = 'camera serial number', editor = ui.EnumEditor(name = 'cameras')) search = traits.Button(desc = 'camera search action') _is_initialized= traits.Bool(False, transient = True) play = traits.Button(desc = 'display preview action') stop = traits.Button(desc = 'close preview action') on_off = traits.Button('On/Off', desc = 'initiate/Uninitiate camera action') gain = create_range_feature('gain',desc = 'camera gain',transient = True) shutter = create_range_feature('shutter', desc = 'camera exposure time',transient = True) format = create_mapped_feature('format',_FORMAT, desc = 'image format',transient = True) roi = traits.Instance(ROI,transient = True) im_shape = traits.Property(depends_on = 'format.value,roi.values') im_dtype = traits.Property(depends_on = 'format.value') capture = traits.Button() save_button = traits.Button('Save as...') message = traits.Str(transient = True) view = ui.View(ui.Group(ui.HGroup(ui.Item('camera', springy = True), ui.Item('search', show_label = False, springy = True), ui.Item('on_off', show_label = False, springy = True), ui.Item('play', show_label = False, enabled_when = 'is_initialized', springy = True), ui.Item('stop', show_label = False, enabled_when = 'is_initialized', springy = True), ), ui.Group( ui.Item('gain', style = 'custom'), ui.Item('shutter', style = 'custom'), ui.Item('format', style = 'custom'), ui.Item('roi', style = 'custom'), ui.HGroup(ui.Item('capture',show_label = False), ui.Item('save_button',show_label = False)), enabled_when = 'is_initialized', ), ), resizable = True, statusbar = [ ui.StatusItem( name = 'message')], buttons = ['OK']) #default initialization def __init__(self, **kw): super(CameraUI, self).__init__(**kw) self.search_cameras() def _camera_control_default(self): return Camera() def _roi_default(self): return ROI() #@display_cls_error def _get_im_shape(self): top, left, width, height = self.roi.values shape = (height, width) try: colors = _COLORS[self.format.value] if colors > 1: shape += (colors,) except KeyError: raise NotImplementedError('Unsupported format') return shape #@display_cls_error def _get_im_dtype(self): try: return _DTYPE[self.format.value] except KeyError: raise NotImplementedError('Unsupported format') def _search_fired(self): self.search_cameras() #@display_cls_error def search_cameras(self): """ Finds cameras if any and selects first from list """ try: cameras = get_number_cameras() except Exception as e: cameras = [] raise e finally: if len(cameras) == 0: cameras = [_NO_CAMERAS] self.cameras = cameras self.camera = cameras[0] #@display_cls_error def _camera_changed(self): if self._is_initialized: self._is_initialized= False self.camera_control.close() self.message = 'Camera uninitialized' #@display_cls_error def init_camera(self): self._is_initialized= False if self.camera != _NO_CAMERAS: self.camera_control.init(self.camera) self.init_features() self._is_initialized= True self.message = 'Camera initialized' #@display_cls_error def _on_off_fired(self): if self._is_initialized: self._is_initialized= False self.camera_control.close() self.message = 'Camera uninitialized' else: self.init_camera() #@display_cls_error def init_features(self): """ Initializes all features to values given by the camera """ features = self.camera_control.get_camera_features() self._init_single_valued_features(features) self._init_roi(features) #@display_cls_error def _init_single_valued_features(self, features): """ Initializes all single valued features to camera values """ for name, id in list(_SINGLE_VALUED_FEATURES.items()): feature = getattr(self, name) feature.low, feature.high = features[id]['params'][0] feature.value = self.camera_control.get_feature(id)[0] #@display_cls_error def _init_roi(self, features): for i,name in enumerate(('top','left','width','height')): feature = getattr(self.roi, name) low, high = features[FEATURE_ROI]['params'][i] value = self.camera_control.get_feature(FEATURE_ROI)[i] try: feature.value = value finally: feature.low, feature.high = low, high @traits.on_trait_change('format.value') def _on_format_change(self, object, name, value): if self._is_initialized: self.camera_control.set_preview_state(STOP_PREVIEW) self.camera_control.set_stream_state(STOP_STREAM) self.set_feature(FEATURE_PIXEL_FORMAT, [value]) @traits.on_trait_change('gain.value,shutter.value') def _single_valued_feature_changed(self, object, name, value): if self._is_initialized: self.set_feature(object.id, [value]) #@display_cls_error def set_feature(self, id, values, flags = 2): self.camera_control.set_feature(id, values, flags = flags) @traits.on_trait_change('roi.values') def a_roi_feature_changed(self, object, name, value): if self._is_initialized: self.set_feature(FEATURE_ROI, value) try: self._is_initialized= False self.init_features() finally: self._is_initialized= True #@display_cls_error def _play_fired(self): self.camera_control.set_preview_state(STOP_PREVIEW) self.camera_control.set_stream_state(STOP_STREAM) self.camera_control.set_stream_state(START_STREAM) self.camera_control.set_preview_state(START_PREVIEW) #@display_cls_error def _stop_fired(self): self.camera_control.set_preview_state(STOP_PREVIEW) self.camera_control.set_stream_state(STOP_STREAM) self.error = '' #@display_cls_error def _format_changed(self, value): self.camera_control.set_preview_state(STOP_PREVIEW) self.camera_control.set_stream_state(STOP_STREAM) self.camera_control.set_feature(FEATURE_PIXEL_FORMAT, [value],2) #@display_cls_error def _capture_fired(self): self.camera_control.set_stream_state(STOP_STREAM) self.camera_control.set_stream_state(START_STREAM) im = self.capture_image() plt.imshow(im) plt.show() def capture_image(self): im = numpy.empty(shape = self.im_shape, dtype = self.im_dtype) self.camera_control.get_next_frame(im) return im.newbyteorder('>') def save_image(self, fname): """Captures image and saves to format guessed from filename extension""" im = self.capture_image() base, ext = os.path.splitext(fname) if ext == '.npy': numpy.save(fname, im) else: im = toimage(im) im.save(fname) def _save_button_fired(self): f = pyface.FileDialog(action = 'save as') #wildcard = self.filter) if f.open() == pyface.OK: self.save_image(f.path) def capture_HDR(self): pass def __del__(self): try: self.camera_control.set_preview_state(STOP_PREVIEW) self.camera_control.set_stream_state(STOP_STREAM) except: pass
class DeviceAnalogInState(traits.HasTraits): """encapsulate all (relevant) analog input state on the device Making these variables a member of their own HasTraits class means that updates to the device can be treated in an atomic way. """ # Analog input state AIN0_enabled = traits.Bool(False) AIN0_name = traits.String("AIN0") AIN1_enabled = traits.Bool(False) AIN1_name = traits.String("AIN1") AIN2_enabled = traits.Bool(True) AIN2_name = traits.String("AIN2") AIN3_enabled = traits.Bool(False) AIN3_name = traits.String("AIN3") trigger_device = traits.Instance('DeviceModel',transient=True) adc_prescaler = traits.Trait(128.0,{ 128.0:0x07,64.0: 0x06, # According to Atmel's at90usb1287 manual, faster than this is # too fast to get good measurements with 8MHz crystal. ## '32': 0x05,'16': 0x04,'8': 0x03, ## '4': 0x02,'2': 0x00, # also 0x01 }) downsample_bits = traits.Range(low=0,high=2**5-1,value=0) AIN_running = traits.Bool(False) sample_rate_total = traits.Property(label='Sample rate (Hz), all channels', depends_on=['adc_prescaler', 'trigger_device', 'downsample_bits']) sample_rate_chan = traits.Property(label='each channel', depends_on=['sample_rate_total', 'AIN0_enabled','AIN1_enabled', 'AIN2_enabled','AIN3_enabled',]) # but useful when plotting/saving data Vcc = traits.Float(3.3) traits_view = View(Group(Group(Item('AIN_running'), Item( 'Vcc', tooltip=('This does not set Vcc on the AT90USBKEY. Use to record the ' 'value of Vcc. (default = 3.3V)')), orientation='horizontal'), Group(Item('AIN0_enabled',padding=0), Item('AIN0_name',padding=0), Item('AIN1_enabled',padding=0), Item('AIN1_name',padding=0), padding=0, orientation='horizontal'), Group(Item('AIN2_enabled',padding=0), Item('AIN2_name',padding=0), Item('AIN3_enabled',padding=0), Item('AIN3_name',padding=0), padding=0, orientation='horizontal'), Group(Item('adc_prescaler'), Item('downsample_bits'), orientation='horizontal'), Group(Item('sample_rate_total', #show_label=False, style='readonly', ), Item('sample_rate_chan', #show_label=False, style='readonly', ), orientation='horizontal'), )) @traits.cached_property def _get_sample_rate_total(self): if self.trigger_device is not None: input_frequency = self.trigger_device.FOSC/self.adc_prescaler else: input_frequency = 100*1e3 # fake value # from USBKEY datasheet: if input_frequency < 50*1e3: warnings.warn('ADC sample frequency is too slow to get good sampling') if input_frequency > 200*1e3: warnings.warn('ADC sample frequency is too fast to get good sampling') #print 'input_frequency %.1f (kHz)'%(input_frequency/1000.0,) clock_cycles_per_sample = 13.0 clock_adc = input_frequency/clock_cycles_per_sample downsample_factor = self.downsample_bits+1 downsampled_clock_adc = clock_adc/downsample_factor return downsampled_clock_adc @traits.cached_property def _get_sample_rate_chan(self): n_chan = sum(map(int,[self.AIN0_enabled,self.AIN1_enabled, self.AIN2_enabled,self.AIN3_enabled])) if n_chan == 0: return 0.0 rate = self.sample_rate_total/float(n_chan) return rate
class DeviceModel(traits.HasTraits): """Represent the trigger device in the host computer, and push any state We keep a local copy of the state of the device in memory on the host computer, and any state changes to the device to through this class, also allowing us to update our copy of the state. """ # Private runtime details _libusb_handle = traits.Any(None,transient=True) _lock = traits.Any(None,transient=True) # lock access to the handle real_device = traits.Bool(False,transient=True) # real USB device present FOSC = traits.Float(8000000.0,transient=True) ignore_version_mismatch = traits.Bool(False, transient=True) # A couple properties frames_per_second = RemoteFpsFloat frames_per_second_actual = traits.Property(depends_on='_t3_state') timer3_top = traits.Property(depends_on='_t3_state') # Timer 3 state: _t3_state = traits.Instance(DeviceTimer3State) # atomic updates # LEDs state _led_state = traits.Int led1 = traits.Property(depends_on='_led_state') led2 = traits.Property(depends_on='_led_state') led3 = traits.Property(depends_on='_led_state') led4 = traits.Property(depends_on='_led_state') # Event would be fine for these, but use Button to get nice editor reset_framecount_A = traits.Button reset_AIN_overflow = traits.Button do_single_frame_pulse = traits.Button ext_trig1 = traits.Button ext_trig2 = traits.Button ext_trig3 = traits.Button # Analog input state: _ain_state = traits.Instance(DeviceAnalogInState) # atomic updates Vcc = traits.Property(depends_on='_ain_state') AIN_running = traits.Property(depends_on='_ain_state') enabled_channels = traits.Property(depends_on='_ain_state') enabled_channel_names = traits.Property(depends_on='_ain_state') # The view: traits_view = View(Group( Group(Item('frames_per_second', label='frame rate', ), Item('frames_per_second_actual', show_label=False, style='readonly', ), orientation='horizontal',), Group(Item('ext_trig1',show_label=False), Item('ext_trig2',show_label=False), Item('ext_trig3',show_label=False), orientation='horizontal'), Item('_ain_state',show_label=False, style='custom'), Item('reset_AIN_overflow',show_label=False), )) def __init__(self,*a,**k): super(DeviceModel,self).__init__(*a,**k) self._t3_state = DeviceTimer3State() self._ain_state = DeviceAnalogInState(trigger_device=self) def __new__(cls,*args,**kwargs): """Set the transient object state This must be done outside of __init__, because instances can get created without calling __init__. In particular, when being loaded from a pickle. """ self = super(DeviceModel, cls).__new__(cls,*args,**kwargs) self._lock = threading.Lock() self._open_device() # force the USBKEY's state to our idea of its state self.__led_state_changed() self.__t3_state_changed() self.__ain_state_changed() self.reset_AIN_overflow = True # reset ain overflow #self.rand_pulse_enable() #self.rand_pulse_disable() #self.set_aout_values(300,250) return self def _set_led_mask(self,led_mask,value): if value: self._led_state = self._led_state | led_mask else: self._led_state = self._led_state & ~led_mask def __led_state_changed(self): buf = ctypes.create_string_buffer(2) buf[0] = chr(CAMTRIG_SET_LED_STATE) buf[1] = chr(self._led_state) self._send_buf(buf) @traits.cached_property def _get_led1(self): return bool(self._led_state & LEDS_LED1) def _set_led1(self,value): self._set_led_mask(LEDS_LED1,value) @traits.cached_property def _get_led2(self): return bool(self._led_state & LEDS_LED2) def _set_led2(self,value): self._set_led_mask(LEDS_LED2,value) @traits.cached_property def _get_led3(self): return bool(self._led_state & LEDS_LED3) def _set_led3(self,value): self._set_led_mask(LEDS_LED3,value) @traits.cached_property def _get_led4(self): return bool(self._led_state & LEDS_LED4) def _set_led4(self,value): self._set_led_mask(LEDS_LED4,value) @traits.cached_property def _get_Vcc(self): return self._ain_state.Vcc @traits.cached_property def _get_AIN_running(self): return self._ain_state.AIN_running @traits.cached_property def _get_enabled_channels(self): result = [] if self._ain_state.AIN0_enabled: result.append(0) if self._ain_state.AIN1_enabled: result.append(1) if self._ain_state.AIN2_enabled: result.append(2) if self._ain_state.AIN3_enabled: result.append(3) return result @traits.cached_property def _get_enabled_channel_names(self): result = [] if self._ain_state.AIN0_enabled: result.append(self._ain_state.AIN0_name) if self._ain_state.AIN1_enabled: result.append(self._ain_state.AIN1_name) if self._ain_state.AIN2_enabled: result.append(self._ain_state.AIN2_name) if self._ain_state.AIN3_enabled: result.append(self._ain_state.AIN3_name) return result @traits.cached_property def _get_timer3_top(self): return self._t3_state.timer3_top @traits.cached_property def _get_frames_per_second_actual(self): if self._t3_state.timer3_CS==0: return 0 return self.FOSC/self._t3_state.timer3_CS/self._t3_state.timer3_top def set_frames_per_second_approximate(self,value): """Set the framerate as close as possible to the desired value""" new_t3_state = DeviceTimer3State() if value==0: new_t3_state.timer3_CS=0 else: # For all possible clock select values CSs = np.array([1.0,8.0,64.0,256.0,1024.0]) # find the value of top that to gives the desired framerate best_top = np.clip(np.round(self.FOSC/CSs/value),0,2**16-1).astype(np.int) # and find the what the framerate would be at that top value best_rate = self.FOSC/CSs/best_top # and choose the best one. idx = np.argmin(abs(best_rate-value)) expected_rate = best_rate[idx] new_t3_state.timer3_CS = CSs[idx] new_t3_state.timer3_top = best_top[idx] ideal_ocr3a = 0.02 * new_t3_state.timer3_top # 2% duty cycle ocr3a = int(np.round(ideal_ocr3a)) if ocr3a==0: ocr3a=1 if ocr3a >= new_t3_state.timer3_top: ocr3a-=1 if ocr3a <= 0: raise ValueError('impossible combination for ocr3a') new_t3_state.ocr3a = ocr3a self._t3_state = new_t3_state # atomic update def get_framestamp(self,full_output=False): """Get the framestamp and the value of PORTC The framestamp includes fraction of IFI until next frame. The inter-frame counter counts up from 0 to self.timer3_top between frame ticks. """ if not self.real_device: now = time.time() if full_output: framecount = now//1 tcnt3 = now%1.0 results = now, framecount, tcnt3 else: results = now return results buf = ctypes.create_string_buffer(1) buf[0] = chr(CAMTRIG_GET_FRAMESTAMP_NOW) self._send_buf(buf) data = self._read_buf() if data is None: raise NoDataError('no data available from device') framecount = 0 for i in range(8): framecount += ord(data[i]) << (i*8) tcnt3 = ord(data[8]) + (ord(data[9]) << 8) frac = tcnt3/float(self._t3_state.timer3_top) if frac>1: print('In ttriger.DeviceModel.get_framestamp(): ' 'large fractional value in framestamp. resetting') frac=1 framestamp = framecount+frac # WBD #if full_output: # results = framestamp, framecount, tcnt3 #else: # results = framestamp pulse_width = ord(data[10]) if full_output: results = framestamp, pulse_width, framecount, tcnt3 else: results = framestamp, pulse_width return results def get_analog_input_buffer_rawLE(self): if not self.real_device: outbuf = np.array([],dtype='<u2') # unsigned 2 byte little endian return outbuf EP_LEN = 256 INPUT_BUFFER = ctypes.create_string_buffer(EP_LEN) bufs = [] got_bytes = False timeout = 50 # msec cnt = 0 # Count number of times endpoint has been read min_cnt = 2 # Minimum number of times end point should be read while 1: # keep pumping until no more data try: with self._lock: n_bytes = usb.bulk_read(self._libusb_handle, (ENDPOINT_DIR_IN|ANALOG_EPNUM), INPUT_BUFFER, timeout) except usb.USBNoDataAvailableError: break cnt += 1 n_elements = n_bytes//2 buf = np.fromstring(INPUT_BUFFER.raw,dtype='<u2') # unsigned 2 byte little endian buf = buf[:n_elements] bufs.append(buf) if (n_bytes < EP_LEN) and (cnt >= min_cnt): break # don't bother waiting for data to dribble in if len(bufs): outbuf = np.hstack(bufs) else: outbuf = np.array([],dtype='<u2') # unsigned 2 byte little endian return outbuf def __t3_state_changed(self): # A value was assigned to self._t3_state. # 1. Send its contents to device self._send_t3_state() # 2. Ensure updates to it also get sent to device if self._t3_state is None: return self._t3_state.on_trait_change(self._send_t3_state) def _send_t3_state(self): """ensure our concept of the device's state is correct by setting it""" t3 = self._t3_state # shorthand if t3 is None: return buf = ctypes.create_string_buffer(10) buf[0] = chr(CAMTRIG_NEW_TIMER3_DATA) buf[1] = chr(t3.ocr3a//0x100) buf[2] = chr(t3.ocr3a%0x100) buf[3] = chr(t3.ocr3b//0x100) buf[4] = chr(t3.ocr3b%0x100) buf[5] = chr(t3.ocr3c//0x100) buf[6] = chr(t3.ocr3c%0x100) buf[7] = chr(t3.timer3_top//0x100) # icr3a buf[8] = chr(t3.timer3_top%0x100) # icr3a buf[9] = chr(t3.timer3_CS_) self._send_buf(buf) def __ain_state_changed(self): # A value was assigned to self._ain_state. # 1. Send its contents to device self._send_ain_state() # 2. Ensure updates to it also get sent to device if self._ain_state is None: return self._ain_state.on_trait_change(self._send_ain_state) def _send_ain_state(self): """ensure our concept of the device's state is correct by setting it""" ain_state = self._ain_state # shorthand if ain_state is None: return if ain_state.AIN_running: # analog_cmd_flags channel_list = 0 if ain_state.AIN0_enabled: channel_list |= ENABLE_ADC_CHAN0 if ain_state.AIN1_enabled: channel_list |= ENABLE_ADC_CHAN1 if ain_state.AIN2_enabled: channel_list |= ENABLE_ADC_CHAN2 if ain_state.AIN3_enabled: channel_list |= ENABLE_ADC_CHAN3 analog_cmd_flags = ADC_START_STREAMING | channel_list analog_sample_bits = ain_state.adc_prescaler_ | (ain_state.downsample_bits<<3) else: analog_cmd_flags = ADC_STOP_STREAMING analog_sample_bits = 0 buf = ctypes.create_string_buffer(3) buf[0] = chr(CAMTRIG_AIN_SERVICE) buf[1] = chr(analog_cmd_flags) buf[2] = chr(analog_sample_bits) self._send_buf(buf) def enter_dfu_mode(self): buf = ctypes.create_string_buffer(1) buf[0] = chr(CAMTRIG_ENTER_DFU) self._send_buf(buf) def _do_single_frame_pulse_fired(self): buf = ctypes.create_string_buffer(1) buf[0] = chr(CAMTRIG_DO_TRIG_ONCE) self._send_buf(buf) def _ext_trig1_fired(self): buf = ctypes.create_string_buffer(2) buf[0] = chr(CAMTRIG_SET_EXT_TRIG) buf[1] = chr(EXT_TRIG1) self._send_buf(buf) def _ext_trig2_fired(self): buf = ctypes.create_string_buffer(2) buf[0] = chr(CAMTRIG_SET_EXT_TRIG) buf[1] = chr(EXT_TRIG2) self._send_buf(buf) def _ext_trig3_fired(self): buf = ctypes.create_string_buffer(2) buf[0] = chr(CAMTRIG_SET_EXT_TRIG) buf[1] = chr(EXT_TRIG3) self._send_buf(buf) def _reset_framecount_A_fired(self): buf = ctypes.create_string_buffer(1) buf[0] = chr(CAMTRIG_RESET_FRAMECOUNT_A) self._send_buf(buf) def _reset_AIN_overflow_fired(self): buf = ctypes.create_string_buffer(3) buf[0] = chr(CAMTRIG_AIN_SERVICE) buf[1] = chr(ADC_RESET_AIN) # 3rd byte doesn't matter self._send_buf(buf) # WBD - functions for enabling and disabling random pulses # -------------------------------------------------------- def rand_pulse_enable(self): buf = ctypes.create_string_buffer(2) buf[0] = chr(CAMTRIG_RAND_PULSE) buf[1] = chr(RAND_PULSE_ENABLE) self._send_buf(buf) def rand_pulse_disable(self): buf = ctypes.create_string_buffer(2) buf[0] = chr(CAMTRIG_RAND_PULSE) buf[1] = chr(RAND_PULSE_DISABLE) self._send_buf(buf) # WBD - function for setting analog output values # ------------------------------------------------------- def set_aout_values(self,val0, val1): buf = ctypes.create_string_buffer(5) buf[0] = chr(CAMTRIG_SET_AOUT) buf[1] = chr(val0//0x100) buf[2] = chr(val0%0x100) buf[3] = chr(val1//0x100) buf[4] = chr(val1%0x100) self._send_buf(buf) # WBD - get pulse width from frame count # ------------------------------------------------------- def get_width_from_framecnt(self,framecnt): buf = ctypes.create_string_buffer(5) buf[0] = chr(CAMTRIG_GET_PULSE_WIDTH) for i in range(1,5): buf[i] = chr((framecnt >> ((i-1)*8)) & 0b11111111) self._send_buf(buf) data = self._read_buf() val = ord(data[0]) return val # WBD - modified read_buf functions for multiple epnum in buffers # --------------------------------------------------------------- def _read_buf(self): if not self.real_device: return None buf = ctypes.create_string_buffer(16) timeout = 1000 epnum = (ENDPOINT_DIR_IN|CAMTRIG_EPNUM) with self._lock: try: val = usb.bulk_read(self._libusb_handle, epnum, buf, timeout) except usb.USBNoDataAvailableError: return None return buf # --------------------------------------------------------------- def _send_buf(self,buf): if not self.real_device: return with self._lock: val = usb.bulk_write(self._libusb_handle, 0x06, buf, 9999) def _open_device(self): require_trigger = int(os.environ.get('REQUIRE_TRIGGER','1')) if require_trigger: usb.init() if not usb.get_busses(): usb.find_busses() usb.find_devices() busses = usb.get_busses() found = False for bus in busses: for dev in bus.devices: debug('idVendor: 0x%04x idProduct: 0x%04x'% (dev.descriptor.idVendor,dev.descriptor.idProduct)) if (dev.descriptor.idVendor == 0x1781 and dev.descriptor.idProduct == 0x0BAF): found = True break if found: break if not found: raise RuntimeError("Cannot find device. (Perhaps run with " "environment variable REQUIRE_TRIGGER=0.)") else: self.real_device = False return with self._lock: self._libusb_handle = usb.open(dev) manufacturer = usb.get_string_simple(self._libusb_handle,dev.descriptor.iManufacturer) product = usb.get_string_simple(self._libusb_handle,dev.descriptor.iProduct) serial = usb.get_string_simple(self._libusb_handle,dev.descriptor.iSerialNumber) assert manufacturer == 'Strawman', 'Wrong manufacturer: %s'%manufacturer valid_product = 'Camera Trigger 1.0' if product == valid_product: self.FOSC = 8000000.0 elif product.startswith('Camera Trigger 1.01'): osc_re = r'Camera Trigger 1.01 \(F_CPU = (.*)\)\w*' match = re.search(osc_re,product) fosc_str = match.groups()[0] if fosc_str.endswith('UL'): fosc_str = fosc_str[:-2] self.FOSC = float(fosc_str) else: errmsg = 'Expected product "%s", but you have "%s"'%( valid_product,product) if self.ignore_version_mismatch: print 'WARNING:',errmsg self.FOSC = 8000000.0 print ' assuming FOSC=',self.FOSC else: raise ValueError(errmsg) interface_nr = 0 if hasattr(usb,'get_driver_np'): # non-portable libusb extension name = usb.get_driver_np(self._libusb_handle,interface_nr) if name != '': usb.detach_kernel_driver_np(self._libusb_handle,interface_nr) if dev.descriptor.bNumConfigurations > 1: debug("WARNING: more than one configuration, choosing first") config = dev.config[0] usb.set_configuration(self._libusb_handle, config.bConfigurationValue) usb.claim_interface(self._libusb_handle, interface_nr) self.real_device = True
class JFIEmulatorClassWorker(JFIEmulatorClass): """runs in process that updates panels""" angle_gain = traits.Property( depends_on=['max_voltage', 'min_angle', 'max_angle']) angle_offset = traits.Property(depends_on=['angle_gain', 'min_angle']) def __init__( self, ## stimulus_state_queue=None, ## stimulus_timeseries_queue=None, display_text_queue=None, ): super(JFIEmulatorClass, self).__init__() self.incoming_data_queue = Queue.Queue() # temporary until set by host @traits.cached_property def _get_angle_gain(self): return self.max_voltage / (self.max_angle - self.min_angle) @traits.cached_property def _get_angle_offset(self): return -self.angle_gain * self.min_angle def _stop_experiment_fired(self): pass def set_incoming_queue(self, data_queue): self.incoming_data_queue = data_queue def do_work(self): """This gets called frequently (e.g. 100 Hz)""" ## # Get any available incoming data. Ignore all but most recent. last_data = None while 1: try: last_data = self.incoming_data_queue.get_nowait() except Queue.Empty, err: break # Update voltages if new data arrived. if last_data is not None: (framenumber, left_angle_degrees, right_angle_degrees, trigger_timestamp) = last_data if not np.isnan(left_angle_degrees): left_adc_volts = left_angle_degrees * self.angle_gain + self.angle_offset if left_adc_volts < 0: left_adc_volts = 0 if left_adc_volts > self.max_voltage: left_adc_volts = self.max_voltage left_adc_units = int(left_adc_volts * self.volts_to_adc_units) UL.cbAOut(self.BoardNum, self.chan_left, self.gain, left_adc_units) if not np.isnan(right_angle_degrees): right_adc_volts = right_angle_degrees * self.angle_gain + self.angle_offset if right_adc_volts < 0: right_adc_volts = 0 if right_adc_volts > self.max_voltage: right_adc_volts = self.max_voltage right_adc_units = int(right_adc_volts * self.volts_to_adc_units) UL.cbAOut(self.BoardNum, self.chan_right, self.gain, right_adc_units)
class LiveTimestampModelerWithAnalogInput(LiveTimestampModeler): view_AIN = traits.Button(label='view analog input (AIN)') viewer = traits.Instance(AnalogInputViewer) # the actual analog data (as a wordstream) ain_data_raw = traits.Array(dtype=np.uint16, transient=True) old_data_raw = traits.Array(dtype=np.uint16, transient=True) timer3_top = traits.Property( ) # necessary to calculate precise timestamps for AIN data channel_names = traits.Property() Vcc = traits.Property(depends_on='_trigger_device') ain_overflowed = traits.Int( 0, transient=True) # integer for display (boolean readonly editor ugly) ain_wordstream_buffer = traits.Any() traits_view = View( Group( Item('synchronize', show_label=False), Item('view_time_model_plot', show_label=False), Item('ain_overflowed', style='readonly'), Item( name='gain', style='readonly', editor=TextEditor(evaluate=float, format_func=myformat), ), Item( name='offset', style='readonly', editor=TextEditor(evaluate=float, format_func=myformat2), ), Item( name='residual_error', style='readonly', editor=TextEditor(evaluate=float, format_func=myformat), ), Item('view_AIN', show_label=False), ), title='Timestamp modeler', ) @traits.cached_property def _get_Vcc(self): return self._trigger_device.Vcc def _get_timer3_top(self): return self._trigger_device.timer3_top def _get_channel_names(self): return self._trigger_device.enabled_channel_names def update_analog_input(self): """call this function frequently to avoid overruns""" new_data_raw = self._trigger_device.get_analog_input_buffer_rawLE() data_raw = np.hstack((new_data_raw, self.old_data_raw)) self.ain_data_raw = new_data_raw newdata_all = [] chan_all = [] any_overflow = False #cum_framestamps = [] while len(data_raw): result = cDecode.process(data_raw) (N, samples, channels, did_overflow, framestamp) = result if N == 0: # no data was able to be processed break data_raw = data_raw[N:] newdata_all.append(samples) chan_all.append(channels) if did_overflow: any_overflow = True # Save framestamp data. # This is not done yet: ## if framestamp is not None: ## cum_framestamps.append( framestamp ) self.old_data_raw = data_raw # save unprocessed data for next run if any_overflow: # XXX should move to logging the error. self.ain_overflowed = 1 raise AnalogDataOverflowedError() if len(chan_all) == 0: # no data return chan_all = np.hstack(chan_all) newdata_all = np.hstack(newdata_all) USB_channel_numbers = np.unique(chan_all) #print len(newdata_all),'new samples on channels',USB_channel_numbers ## F_OSC = 8000000.0 # 8 MHz ## adc_prescaler = 128 ## downsample = 20 # maybe 21? ## n_chan = 3 ## F_samp = F_OSC/adc_prescaler/downsample/n_chan ## dt=1.0/F_samp ## ## print '%.1f Hz sampling. %.3f msec dt'%(F_samp,dt*1e3) ## MAXLEN_SEC=0.3 ## #MAXLEN = int(MAXLEN_SEC/dt) MAXLEN = 5000 #int(MAXLEN_SEC/dt) ## ## print 'MAXLEN',MAXLEN ## ## print for USB_chan in USB_channel_numbers: vi = self.viewer.usb_device_number2index[USB_chan] cond = chan_all == USB_chan newdata = newdata_all[cond] oldidx = self.viewer.channels[vi].index olddata = self.viewer.channels[vi].data if len(oldidx): baseidx = oldidx[-1] + 1 else: baseidx = 0.0 newidx = np.arange(len(newdata), dtype=np.float) + baseidx tmpidx = np.hstack((oldidx, newidx)) tmpdata = np.hstack((olddata, newdata)) if len(tmpidx) > MAXLEN: # clip to MAXLEN self.viewer.channels[vi].index = tmpidx[-MAXLEN:] self.viewer.channels[vi].data = tmpdata[-MAXLEN:] else: self.viewer.channels[vi].index = tmpidx self.viewer.channels[vi].data = tmpdata def _view_AIN_fired(self): self.viewer.edit_traits()
class LiveTimestampModeler(traits.HasTraits): _trigger_device = traits.Instance(ttrigger.DeviceModel) sync_interval = traits.Float(2.0) has_ever_synchronized = traits.Bool(False, transient=True) frame_offset_changed = traits.Event timestamps_framestamps = traits.Array(shape=(None, 2), dtype=np.float) timestamp_data = traits.Any() block_activity = traits.Bool(False, transient=True) synchronize = traits.Button(label='Synchronize') synchronizing_info = traits.Any(None) gain_offset_residuals = traits.Property( depends_on=['timestamps_framestamps']) residual_error = traits.Property(depends_on='gain_offset_residuals') gain = traits.Property(depends_on='gain_offset_residuals') offset = traits.Property(depends_on='gain_offset_residuals') frame_offsets = traits.Dict() last_frame = traits.Dict() view_time_model_plot = traits.Button traits_view = View( Group( Item( name='gain', style='readonly', editor=TextEditor(evaluate=float, format_func=myformat), ), Item( name='offset', style='readonly', editor=TextEditor(evaluate=float, format_func=myformat2), ), Item( name='residual_error', style='readonly', editor=TextEditor(evaluate=float, format_func=myformat), ), Item('synchronize', show_label=False), Item('view_time_model_plot', show_label=False), ), title='Timestamp modeler', ) def _block_activity_changed(self): if self.block_activity: print('Do not change frame rate or AIN parameters. ' 'Automatic prevention of doing ' 'so is not currently implemented.') else: print('You may change frame rate again') def _view_time_model_plot_fired(self): raise NotImplementedError('') def _synchronize_fired(self): if self.block_activity: print('Not synchronizing because activity is blocked. ' '(Perhaps because you are saving data now.') return orig_fps = self._trigger_device.frames_per_second_actual self._trigger_device.set_frames_per_second_approximate(0.0) self._trigger_device.reset_framecount_A = True # trigger reset event self.synchronizing_info = (time.time() + self.sync_interval + 0.1, orig_fps) @traits.cached_property def _get_gain(self): result = self.gain_offset_residuals if result is None: # not enought data return None gain, offset, residuals = result return gain @traits.cached_property def _get_offset(self): result = self.gain_offset_residuals if result is None: # not enought data return None gain, offset, residuals = result return offset @traits.cached_property def _get_residual_error(self): result = self.gain_offset_residuals if result is None: # not enought data return None gain, offset, residuals = result if residuals is None or len(residuals) == 0: # not enought data return None assert len(residuals) == 1 return residuals[0] @traits.cached_property def _get_gain_offset_residuals(self): if self.timestamps_framestamps is None: return None timestamps = self.timestamps_framestamps[:, 0] framestamps = self.timestamps_framestamps[:, 1] if len(timestamps) < 2: return None # like model_remote_to_local in flydra.analysis remote_timestamps = framestamps local_timestamps = timestamps a1 = remote_timestamps[:, np.newaxis] a2 = np.ones((len(remote_timestamps), 1)) A = np.hstack((a1, a2)) b = local_timestamps[:, np.newaxis] x, resids, rank, s = np.linalg.lstsq(A, b) gain = x[0, 0] offset = x[1, 0] return gain, offset, resids def set_trigger_device(self, device): self._trigger_device = device self._trigger_device.on_trait_event( self._on_trigger_device_reset_AIN_overflow_fired, name='reset_AIN_overflow') def _on_trigger_device_reset_AIN_overflow_fired(self): self.ain_overflowed = 0 def _get_now_framestamp(self, max_error_seconds=0.003, full_output=False): count = 0 while count <= 10: now1 = time.time() try: results = self._trigger_device.get_framestamp( full_output=full_output) except ttrigger.NoDataError: raise ImpreciseMeasurementError('no data available') now2 = time.time() if full_output: framestamp, framecount, tcnt = results else: framestamp = results count += 1 measurement_error = abs(now2 - now1) if framestamp % 1.0 < 0.1: warnings.warn('workaround of TCNT race condition on MCU...') continue if measurement_error < max_error_seconds: break time.sleep(0.01) # wait 10 msec before trying again if not measurement_error < max_error_seconds: raise ImpreciseMeasurementError( 'could not obtain low error measurement') if framestamp % 1.0 < 0.1: raise ImpreciseMeasurementError('workaround MCU bug') now = (now1 + now2) * 0.5 if full_output: results = now, framestamp, now1, now2, framecount, tcnt else: results = now, framestamp return results def clear_samples(self, call_update=True): self.timestamps_framestamps = np.empty((0, 2)) if call_update: self.update() def update(self, return_last_measurement_info=False): """call this function fairly often to pump information from the USB device""" if self.synchronizing_info is not None: done_time, orig_fps = self.synchronizing_info # suspended trigger pulses to re-synchronize if time.time() >= done_time: # we've waited the sync duration, restart self._trigger_device.set_frames_per_second_approximate( orig_fps) self.clear_samples(call_update=False) # avoid recursion self.synchronizing_info = None self.has_ever_synchronized = True results = self._get_now_framestamp( full_output=return_last_measurement_info) now, framestamp = results[:2] if return_last_measurement_info: start_timestamp, stop_timestamp, framecount, tcnt = results[2:] self.timestamps_framestamps = np.vstack( (self.timestamps_framestamps, [now, framestamp])) # If more than 100 samples, if len(self.timestamps_framestamps) > 100: # keep only the most recent 50. self.timestamps_framestamps = self.timestamps_framestamps[-50:] if return_last_measurement_info: return start_timestamp, stop_timestamp, framecount, tcnt def get_frame_offset(self, id_string): return self.frame_offsets[id_string] def register_frame(self, id_string, framenumber, frame_timestamp, full_output=False): """note that a frame happened and return start-of-frame time""" # This may get called from another thread (e.g. the realtime # image processing thread). # An important note about locking and thread safety: This code # relies on the Python interpreter to lock data structures # across threads. To do this internally, a lock would be made # for each variable in this instance and acquired before each # access. Because the data structures are simple Python # objects, I believe the operations are atomic and thus this # function is OK. # Don't trust camera drivers with giving a good timestamp. We # only use this to reset our framenumber-to-time data # gathering, anyway. frame_timestamp = time.time() if frame_timestamp is not None: last_frame_timestamp = self.last_frame.get(id_string, -np.inf) this_interval = frame_timestamp - last_frame_timestamp did_frame_offset_change = False if this_interval > self.sync_interval: if self.block_activity: print( 'changing frame offset is disallowed, but you attempted to do it. ignoring.' ) else: # re-synchronize camera # XXX need to figure out where frame offset of two comes from: self.frame_offsets[id_string] = framenumber - 2 did_frame_offset_change = True self.last_frame[id_string] = frame_timestamp if did_frame_offset_change: self.frame_offset_changed = True # fire any listeners result = self.gain_offset_residuals if result is None: # not enough data if full_output: results = None, None, did_frame_offset_change else: results = None return results gain, offset, residuals = result corrected_framenumber = framenumber - self.frame_offsets[id_string] trigger_timestamp = corrected_framenumber * gain + offset if full_output: results = trigger_timestamp, corrected_framenumber, did_frame_offset_change else: results = trigger_timestamp return results
class Affine(HasTraits): """ An affine 3x3 matrix that supports matrix multiplication with other Affine instances or numpy arrays. a = Affine() a.translate = 10,20 a.scale = 20, 40 Be careful not to do *inplace* operations on the array components or the update callbacks will not be triggered, eg DO NOT a.translate += 10, 20 rather DO a.translate_delta(10, 20) Multiplication works as expected: a1 = Affine() a1.scale = 10, 20 a2 = Affine() a2.scale = 4, 5 print a1*a2 x = numpy.random(3, 10) print a1*x All of the translate, scale, xlim, ylim and vec6 properties are simply views into the data matrix, and are updated by reference """ # connect to the data_modified event if you want a callback data = Array('d', (3, 3)) translate = traits.Property(Array('d', (2, ))) scale = traits.Property(Array('d', (2, ))) vec6 = traits.Property(Array('d', (6, ))) xlim = traits.Property(Array('d', (2, ))) ylim = traits.Property(Array('d', (2, ))) #data_modified = traits.Event def _data_default(self): return npy.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], npy.float_) def _get_xlim(self): sx, b, tx = self.data[0] return self._get_lim(sx, tx) def _set_xlim(self, xlim): xmin, xmax = xlim oldsx, oldb, oldtx = self.data[0] sx = 1. / (xmax - xmin) tx = -xmin * sx forward = oldsx != sx or oldtx != tx if forward: old = self.data.copy() self.data[0][0] = sx self.data[0][-1] = tx self._data_changed(old, self.data) def _get_ylim(self): c, sy, ty = self.data[1] return self._get_lim(sy, ty) def _set_ylim(self, ylim): ymin, ymax = ylim oldc, oldsy, oldty = self.data[1] sy = 1. / (ymax - ymin) ty = -ymin * sy forward = oldsy != sy or oldty != ty if forward: old = self.data.copy() self.data[1][1] = sy self.data[1][-1] = ty self._data_changed(old, self.data) def _get_translate(self): return [self.data[0][-1], self.data[1][-1]] def _set_translate(self, s): oldtx = self.data[0][-1] oldty = self.data[1][-1] tx, ty = s forward = tx != oldtx or ty != oldty if forward: old = self.data.copy() self.data[0][-1] = tx self.data[1][-1] = ty self._data_changed(old, self.data) def _get_scale(self): return [self.data[0][0], self.data[1][1]] def _set_scale(self, s): oldsx = self.data[0][0] oldsy = self.data[1][1] sx, sy = s forward = sx != oldsx or sy != oldsy if forward: old = self.data.copy() self.data[0][0] = sx self.data[1][1] = sy self._data_changed(old, self.data) def _get_vec6(self): a, b, tx = self.data[0] c, d, ty = self.data[1] return [a, b, c, d, tx, ty] def _set_vec6(self, v): a, b, c, d, tx, ty = v olda, oldb, oldtx = self.data[0] oldc, oldd, oldty = self.data[1] forward = a != olda or b != oldb or c != oldc or d != oldd or tx != oldtx or ty != oldty if forward: old = self.data.copy() self.data[0] = a, b, tx self.data[1] = c, d, ty self._data_changed(old, self.data) def _get_lim(self, s, t): lmin = -t / s lmax = 1. / s + lmin return lmin, lmax def _data_changed(self, old, new): # Make it known if the translate changed oldtx, oldty = old[0][-1], old[1][-1] tx, ty = new[0][-1], new[1][-1] oldsx, oldsy = old[0][0], old[1][1] sx, sy = new[0][0], new[1][1] oldb, oldc = old[0][1], old[1][0] b, c = new[0][1], new[1][0] tchanged = False schanged = False vchanged = False tchanged = oldtx != tx or oldty != ty schanged = oldsx != sx or oldsy != sy vchanged = tchanged or schanged or b != oldb or c != oldc xchanged = oldtx != tx or oldsx != sx ychanged = oldty != ty or oldsy != sy if tchanged: self.trait_property_changed('translate', [oldtx, oldty], [tx, ty]) if schanged: self.trait_property_changed('scale', [oldsx, oldsy], [sx, sy]) if xchanged: oldxmin, oldxmax = self._get_lim(oldsx, oldtx) xmin, xmax = self._get_lim(sx, tx) self.trait_property_changed('xlim', [oldxmin, oldxmax], [xmin, xmax]) if ychanged: oldymin, oldymax = self._get_lim(oldsy, oldty) ymin, ymax = self._get_lim(sy, ty) self.trait_property_changed('ylim', [oldymin, oldymax], [ymin, ymax]) if vchanged: self.trait_property_changed( 'vec6', [oldsx, oldb, oldc, oldsy, oldtx, oldty], [sx, b, c, sy, tx, ty]) if tchanged or schanged or vchanged: #self._data_modified = True self.trait_property_changed('data_modified', old, new) def follow(self, othervec6): self.vec6 = othervec6 def __mul__(self, other): if isinstance(other, Affine): new = Affine() new.data = npy.dot(self.data, other.data) return new elif isinstance(other, npy.ndarray): return npy.dot(self.data, other) raise TypeError('Do not know how to multiply Affine by %s' % type(other)) def __repr__(self): return 'AFFINE: %s' % ', '.join([str(val) for val in self.vec6])