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 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 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 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 Signal(t.HasTraits, MVA): data = t.Any() axes_manager = t.Instance(AxesManager) original_parameters = t.Instance(Parameters) mapped_parameters = t.Instance(Parameters) physical_property = t.Str() def __init__(self, file_data_dict=None, *args, **kw): """All data interaction is made through this class or its subclasses Parameters: ----------- dictionary : dictionary see load_dictionary for the format """ super(Signal, self).__init__() self.mapped_parameters = Parameters() self.original_parameters = Parameters() if type(file_data_dict).__name__ == "dict": self.load_dictionary(file_data_dict) self._plot = None self.mva_results = MVA_Results() self._shape_before_unfolding = None self._axes_manager_before_unfolding = None def load_dictionary(self, file_data_dict): """Parameters: ----------- file_data_dict : dictionary A dictionary containing at least a 'data' keyword with an array of arbitrary dimensions. Additionally the dictionary can contain the following keys: axes: a dictionary that defines the axes (see the AxesManager class) attributes: a dictionary which keywords are stored as attributes of the signal class mapped_parameters: a dictionary containing a set of parameters that will be stored as attributes of a Parameters class. For some subclasses some particular parameters might be mandatory. original_parameters: a dictionary that will be accesible in the original_parameters attribute of the signal class and that typically contains all the parameters that has been imported from the original data file. """ self.data = file_data_dict['data'] if 'axes' not in file_data_dict: file_data_dict['axes'] = self._get_undefined_axes_list() self.axes_manager = AxesManager(file_data_dict['axes']) if not 'mapped_parameters' in file_data_dict: file_data_dict['mapped_parameters'] = {} if not 'original_parameters' in file_data_dict: file_data_dict['original_parameters'] = {} if 'attributes' in file_data_dict: for key, value in file_data_dict['attributes'].iteritems(): self.__setattr__(key, value) self.original_parameters.load_dictionary( file_data_dict['original_parameters']) self.mapped_parameters.load_dictionary( file_data_dict['mapped_parameters']) def _get_signal_dict(self): dic = {} dic['data'] = self.data.copy() dic['axes'] = self.axes_manager._get_axes_dicts() dic['mapped_parameters'] = \ self.mapped_parameters._get_parameters_dictionary() dic['original_parameters'] = \ self.original_parameters._get_parameters_dictionary() return dic def _get_undefined_axes_list(self): axes = [] for i in xrange(len(self.data.shape)): axes.append({ 'name': 'undefined', 'scale': 1., 'offset': 0., 'size': int(self.data.shape[i]), 'units': 'undefined', 'index_in_array': i, }) return axes def __call__(self, axes_manager=None): if axes_manager is None: axes_manager = self.axes_manager return self.data.__getitem__(axes_manager._getitem_tuple) def _get_hse_1D_explorer(self, *args, **kwargs): islice = self.axes_manager._slicing_axes[0].index_in_array inslice = self.axes_manager._non_slicing_axes[0].index_in_array if islice > inslice: return self.data.squeeze() else: return self.data.squeeze().T def _get_hse_2D_explorer(self, *args, **kwargs): islice = self.axes_manager._slicing_axes[0].index_in_array data = self.data.sum(islice) return data def _get_hie_explorer(self, *args, **kwargs): isslice = [ self.axes_manager._slicing_axes[0].index_in_array, self.axes_manager._slicing_axes[1].index_in_array ] isslice.sort() data = self.data.sum(isslice[1]).sum(isslice[0]) return data def _get_explorer(self, *args, **kwargs): nav_dim = self.axes_manager.navigation_dimension if self.axes_manager.signal_dimension == 1: if nav_dim == 1: return self._get_hse_1D_explorer(*args, **kwargs) elif nav_dim == 2: return self._get_hse_2D_explorer(*args, **kwargs) else: return None if self.axes_manager.signal_dimension == 2: if nav_dim == 1 or nav_dim == 2: return self._get_hie_explorer(*args, **kwargs) else: return None else: return None def plot(self, axes_manager=None): if self._plot is not None: try: self._plot.close() except: # If it was already closed it will raise an exception, # but we want to carry on... pass if axes_manager is None: axes_manager = self.axes_manager if axes_manager.signal_dimension == 1: # Hyperspectrum self._plot = mpl_hse.MPL_HyperSpectrum_Explorer() self._plot.spectrum_data_function = self.__call__ self._plot.spectrum_title = self.mapped_parameters.name self._plot.xlabel = '%s (%s)' % ( self.axes_manager._slicing_axes[0].name, self.axes_manager._slicing_axes[0].units) self._plot.ylabel = 'Intensity' self._plot.axes_manager = axes_manager self._plot.axis = self.axes_manager._slicing_axes[0].axis # Image properties if self.axes_manager._non_slicing_axes: self._plot.image_data_function = self._get_explorer self._plot.image_title = '' self._plot.pixel_size = \ self.axes_manager._non_slicing_axes[0].scale self._plot.pixel_units = \ self.axes_manager._non_slicing_axes[0].units self._plot.plot() elif axes_manager.signal_dimension == 2: # Mike's playground with new plotting toolkits - needs to be a # branch. """ if len(self.data.shape)==2: from drawing.guiqwt_hie import image_plot_2D image_plot_2D(self) import drawing.chaco_hie self._plot = drawing.chaco_hie.Chaco_HyperImage_Explorer(self) self._plot.configure_traits() """ self._plot = mpl_hie.MPL_HyperImage_Explorer() self._plot.image_data_function = self.__call__ self._plot.navigator_data_function = self._get_explorer self._plot.axes_manager = axes_manager self._plot.plot() else: messages.warning_exit('Plotting is not supported for this view') traits_view = tui.View( tui.Item('name'), tui.Item('physical_property'), tui.Item('units'), tui.Item('offset'), tui.Item('scale'), ) def plot_residual(self, axes_manager=None): """Plot the residual between original data and reconstructed data Requires you to have already run PCA or ICA, and to reconstruct data using either the pca_build_SI or ica_build_SI methods. """ if hasattr(self, 'residual'): self.residual.plot(axes_manager) else: print "Object does not have any residual information. Is it a \ reconstruction created using either pca_build_SI or ica_build_SI methods?" def save(self, filename, only_view=False, **kwds): """Saves the signal in the specified format. The function gets the format from the extension. You can use: - hdf5 for HDF5 - nc for NetCDF - msa for EMSA/MSA single spectrum saving. - bin to produce a raw binary file - Many image formats such as png, tiff, jpeg... Please note that not all the formats supports saving datasets of arbitrary dimensions, e.g. msa only suports 1D data. Parameters ---------- filename : str msa_format : {'Y', 'XY'} 'Y' will produce a file without the energy axis. 'XY' will also save another column with the energy axis. For compatibility with Gatan Digital Micrograph 'Y' is the default. only_view : bool If True, only the current view will be saved. Otherwise the full dataset is saved. Please note that not all the formats support this option at the moment. """ io.save(filename, self, **kwds) def _replot(self): if self._plot is not None: if self._plot.is_active() is True: self.plot() def get_dimensions_from_data(self): """Get the dimension parameters from the data_cube. Useful when the data_cube was externally modified, or when the SI was not loaded from a file """ dc = self.data for axis in self.axes_manager.axes: axis.size = int(dc.shape[axis.index_in_array]) print("%s size: %i" % (axis.name, dc.shape[axis.index_in_array])) self._replot() def crop_in_pixels(self, axis, i1=None, i2=None): """Crops the data in a given axis. The range is given in pixels axis : int i1 : int Start index i2 : int End index See also: --------- crop_in_units """ axis = self._get_positive_axis_index_index(axis) if i1 is not None: new_offset = self.axes_manager.axes[axis].axis[i1] # We take a copy to guarantee the continuity of the data self.data = self.data[(slice(None), ) * axis + (slice(i1, i2), Ellipsis)].copy() if i1 is not None: self.axes_manager.axes[axis].offset = new_offset self.get_dimensions_from_data() def crop_in_units(self, axis, x1=None, x2=None): """Crops the data in a given axis. The range is given in the units of the axis axis : int i1 : int Start index i2 : int End index See also: --------- crop_in_pixels """ i1 = self.axes_manager.axes[axis].value2index(x1) i2 = self.axes_manager.axes[axis].value2index(x2) self.crop_in_pixels(axis, i1, i2) def roll_xy(self, n_x, n_y=1): """Roll over the x axis n_x positions and n_y positions the former rows This method has the purpose of "fixing" a bug in the acquisition of the Orsay's microscopes and probably it does not have general interest Parameters ---------- n_x : int n_y : int Note: Useful to correct the SI column storing bug in Marcel's acquisition routines. """ self.data = np.roll(self.data, n_x, 0) self.data[:n_x, ...] = np.roll(self.data[:n_x, ...], n_y, 1) self._replot() # TODO: After using this function the plotting does not work def swap_axis(self, axis1, axis2): """Swaps the axes Parameters ---------- axis1 : positive int axis2 : positive int """ self.data = self.data.swapaxes(axis1, axis2) c1 = self.axes_manager.axes[axis1] c2 = self.axes_manager.axes[axis2] c1.index_in_array, c2.index_in_array = \ c2.index_in_array, c1.index_in_array self.axes_manager.axes[axis1] = c2 self.axes_manager.axes[axis2] = c1 self.axes_manager.set_signal_dimension() self._replot() def rebin(self, new_shape): """ Rebins the data to the new shape Parameters ---------- new_shape: tuple of ints The new shape must be a divisor of the original shape """ factors = np.array(self.data.shape) / np.array(new_shape) self.data = utils.rebin(self.data, new_shape) for axis in self.axes_manager.axes: axis.scale *= factors[axis.index_in_array] self.get_dimensions_from_data() def split_in(self, axis, number_of_parts=None, steps=None): """Splits the data The split can be defined either by the `number_of_parts` or by the `steps` size. Parameters ---------- number_of_parts : int or None Number of parts in which the SI will be splitted steps : int or None Size of the splitted parts axis : int The splitting axis Return ------ tuple with the splitted signals """ axis = self._get_positive_axis_index_index(axis) if number_of_parts is None and steps is None: if not self._splitting_steps: messages.warning_exit( "Please provide either number_of_parts or a steps list") else: steps = self._splitting_steps print "Splitting in ", steps elif number_of_parts is not None and steps is not None: print "Using the given steps list. number_of_parts dimissed" splitted = [] shape = self.data.shape if steps is None: rounded = (shape[axis] - (shape[axis] % number_of_parts)) step = rounded / number_of_parts cut_node = range(0, rounded + step, step) else: cut_node = np.array([0] + steps).cumsum() for i in xrange(len(cut_node) - 1): data = self.data[(slice(None), ) * axis + (slice(cut_node[i], cut_node[i + 1]), Ellipsis)] s = Signal({'data': data}) # TODO: When copying plotting does not work # s.axes = copy.deepcopy(self.axes_manager) s.get_dimensions_from_data() splitted.append(s) return splitted def unfold_if_multidim(self): """Unfold the datacube if it is >2D Returns ------- Boolean. True if the data was unfolded by the function. """ if len(self.axes_manager.axes) > 2: print "Automatically unfolding the data" self.unfold() return True else: return False def _unfold(self, steady_axes, unfolded_axis): """Modify the shape of the data by specifying the axes the axes which dimension do not change and the axis over which the remaining axes will be unfolded Parameters ---------- steady_axes : list The indexes of the axes which dimensions do not change unfolded_axis : int The index of the axis over which all the rest of the axes (except the steady axes) will be unfolded See also -------- fold """ # It doesn't make sense unfolding when dim < 3 if len(self.data.squeeze().shape) < 3: return False # We need to store the original shape and coordinates to be used by # the fold function only if it has not been already stored by a # previous unfold if self._shape_before_unfolding is None: self._shape_before_unfolding = self.data.shape self._axes_manager_before_unfolding = self.axes_manager new_shape = [1] * len(self.data.shape) for index in steady_axes: new_shape[index] = self.data.shape[index] new_shape[unfolded_axis] = -1 self.data = self.data.reshape(new_shape) self.axes_manager = self.axes_manager.deepcopy() i = 0 uname = '' uunits = '' to_remove = [] for axis, dim in zip(self.axes_manager.axes, new_shape): if dim == 1: uname += ',' + axis.name uunits = ',' + axis.units to_remove.append(axis) else: axis.index_in_array = i i += 1 self.axes_manager.axes[unfolded_axis].name += uname self.axes_manager.axes[unfolded_axis].units += uunits self.axes_manager.axes[unfolded_axis].size = \ self.data.shape[unfolded_axis] for axis in to_remove: self.axes_manager.axes.remove(axis) self.data = self.data.squeeze() self._replot() def unfold(self): """Modifies the shape of the data by unfolding the signal and navigation dimensions separaterly """ self.unfold_navigation_space() self.unfold_signal_space() def unfold_navigation_space(self): """Modify the shape of the data to obtain a navigation space of dimension 1 """ if self.axes_manager.navigation_dimension < 2: messages.information('Nothing done, the navigation dimension was ' 'already 1') return False steady_axes = [ axis.index_in_array for axis in self.axes_manager._slicing_axes ] unfolded_axis = self.axes_manager._non_slicing_axes[-1].index_in_array self._unfold(steady_axes, unfolded_axis) def unfold_signal_space(self): """Modify the shape of the data to obtain a signal space of dimension 1 """ if self.axes_manager.signal_dimension < 2: messages.information('Nothing done, the signal dimension was ' 'already 1') return False steady_axes = [ axis.index_in_array for axis in self.axes_manager._non_slicing_axes ] unfolded_axis = self.axes_manager._slicing_axes[-1].index_in_array self._unfold(steady_axes, unfolded_axis) def fold(self): """If the signal was previously unfolded, folds it back""" if self._shape_before_unfolding is not None: self.data = self.data.reshape(self._shape_before_unfolding) self.axes_manager = self._axes_manager_before_unfolding self._shape_before_unfolding = None self._axes_manager_before_unfolding = None self._replot() def _get_positive_axis_index_index(self, axis): if axis < 0: axis = len(self.data.shape) + axis return axis def iterate_axis(self, axis=-1): # We make a copy to guarantee that the data in contiguous, otherwise # it will not return a view of the data self.data = self.data.copy() axis = self._get_positive_axis_index_index(axis) unfolded_axis = axis - 1 new_shape = [1] * len(self.data.shape) new_shape[axis] = self.data.shape[axis] new_shape[unfolded_axis] = -1 # Warning! if the data is not contigous it will make a copy!! data = self.data.reshape(new_shape) for i in xrange(data.shape[unfolded_axis]): getitem = [0] * len(data.shape) getitem[axis] = slice(None) getitem[unfolded_axis] = i yield (data[getitem]) def sum(self, axis, return_signal=False): """Sum the data over the specify axis Parameters ---------- axis : int The axis over which the operation will be performed return_signal : bool If False the operation will be performed on the current object. If True, the current object will not be modified and the operation will be performed in a new signal object that will be returned. Returns ------- Depending on the value of the return_signal keyword, nothing or a signal instance See also -------- sum_in_mask, mean Usage ----- >>> import numpy as np >>> s = Signal({'data' : np.random.random((64,64,1024))}) >>> s.data.shape (64,64,1024) >>> s.sum(-1) >>> s.data.shape (64,64) # If we just want to plot the result of the operation s.sum(-1, True).plot() """ if return_signal is True: s = self.deepcopy() else: s = self s.data = s.data.sum(axis) s.axes_manager.axes.remove(s.axes_manager.axes[axis]) for _axis in s.axes_manager.axes: if _axis.index_in_array > axis: _axis.index_in_array -= 1 s.axes_manager.set_signal_dimension() if return_signal is True: return s def mean(self, axis, return_signal=False): """Average the data over the specify axis Parameters ---------- axis : int The axis over which the operation will be performed return_signal : bool If False the operation will be performed on the current object. If True, the current object will not be modified and the operation will be performed in a new signal object that will be returned. Returns ------- Depending on the value of the return_signal keyword, nothing or a signal instance See also -------- sum_in_mask, mean Usage ----- >>> import numpy as np >>> s = Signal({'data' : np.random.random((64,64,1024))}) >>> s.data.shape (64,64,1024) >>> s.mean(-1) >>> s.data.shape (64,64) # If we just want to plot the result of the operation s.mean(-1, True).plot() """ if return_signal is True: s = self.deepcopy() else: s = self s.data = s.data.mean(axis) s.axes_manager.axes.remove(s.axes_manager.axes[axis]) for _axis in s.axes_manager.axes: if _axis.index_in_array > axis: _axis.index_in_array -= 1 s.axes_manager.set_signal_dimension() if return_signal is True: return s def copy(self): return (copy.copy(self)) def deepcopy(self): return (copy.deepcopy(self)) # def sum_in_mask(self, mask): # """Returns the result of summing all the spectra in the mask. # # Parameters # ---------- # mask : boolean numpy array # # Returns # ------- # Spectrum # """ # dc = self.data_cube.copy() # mask3D = mask.reshape([1,] + list(mask.shape)) * np.ones(dc.shape) # dc = (mask3D*dc).sum(1).sum(1) / mask.sum() # s = Spectrum() # s.data_cube = dc.reshape((-1,1,1)) # s.get_dimensions_from_cube() # utils.copy_energy_calibration(self,s) # return s # # def mean(self, axis): # """Average the SI over the given axis # # Parameters # ---------- # axis : int # """ # dc = self.data_cube # dc = dc.mean(axis) # dc = dc.reshape(list(dc.shape) + [1,]) # self.data_cube = dc # self.get_dimensions_from_cube() # # def roll(self, axis = 2, shift = 1): # """Roll the SI. see numpy.roll # # Parameters # ---------- # axis : int # shift : int # """ # self.data_cube = np.roll(self.data_cube, shift, axis) # self._replot() # # # def get_calibration_from(self, s): # """Copy the calibration from another Spectrum instance # Parameters # ---------- # s : spectrum instance # """ # utils.copy_energy_calibration(s, self) # # def estimate_variance(self, dc = None, gaussian_noise_var = None): # """Variance estimation supposing Poissonian noise # # Parameters # ---------- # dc : None or numpy array # If None the SI is used to estimate its variance. Otherwise, the # provided array will be used. # Note # ---- # The gain_factor and gain_offset from the aquisition parameters are used # """ # print "Variace estimation using the following values:" # print "Gain factor = ", self.acquisition_parameters.gain_factor # print "Gain offset = ", self.acquisition_parameters.gain_offset # if dc is None: # dc = self.data_cube # gain_factor = self.acquisition_parameters.gain_factor # gain_offset = self.acquisition_parameters.gain_offset # self.variance = dc*gain_factor + gain_offset # if self.variance.min() < 0: # if gain_offset == 0 and gaussian_noise_var is None: # print "The variance estimation results in negative values" # print "Maybe the gain_offset is wrong?" # self.variance = None # return # elif gaussian_noise_var is None: # print "Clipping the variance to the gain_offset value" # self.variance = np.clip(self.variance, np.abs(gain_offset), # np.Inf) # else: # print "Clipping the variance to the gaussian_noise_var" # self.variance = np.clip(self.variance, gaussian_noise_var, # np.Inf) # # def calibrate(self, lcE = 642.6, rcE = 849.7, lc = 161.9, rc = 1137.6, # modify_calibration = True): # dispersion = (rcE - lcE) / (rc - lc) # origin = lcE - dispersion * lc # print "Energy step = ", dispersion # print "Energy origin = ", origin # if modify_calibration is True: # self.set_new_calibration(origin, dispersion) # return origin, dispersion # def _correct_navigation_mask_when_unfolded( self, navigation_mask=None, ): #if 'unfolded' in self.history: if navigation_mask is not None: navigation_mask = navigation_mask.reshape((-1, )) return navigation_mask