Exemplo n.º 1
0
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))
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
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'),
    )
Exemplo n.º 6
0
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
Exemplo n.º 7
0
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
Exemplo n.º 8
0
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
Exemplo n.º 9
0
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)
Exemplo n.º 10
0
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()
Exemplo n.º 11
0
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
Exemplo n.º 12
0
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])