def __init__(self, port, smcID=(1, ), **kwargs): """ If backlash_compensation is False, no backlash compensation will be done. If silent is False, then additional output will be emitted to aid in debugging. If sleepfunc is not None, then it will be used instead of time.sleep. It will be given the number of seconds (float) to sleep for, and is provided for ease integration with single threaded GUIs. Note that this method only connects to the controller, it otherwise makes no attempt to home or configure the controller for the attached stage. This delibrate to minimise realworld side effects. If the controller has previously been configured, it will suffice to simply call home() to take the controller out of not referenced mode. For a brand new controller, call reset_and_configure(). """ self.port_settings = dict(baudrate=57600, bytesize=8, stopbits=1, parity='N', xonxoff=True, timeout=0.050) SerialInstrument.__init__(self, port) Stage.__init__(self) # self._logger.debug('Connecting to SMC100 on %s' % (port)) self.software_home = None self._last_sendcmd_time = 0 if not hasattr(smcID, '__iter__'): smcID = (smcID, ) self._smcID = list(smcID) self.axis_names = () for id in self._smcID: self.axis_names += (str(id), ) self._send_cmd('ID', id, '?', True) # Just testing the connection
def __init__(self,port=None,device_index=0,debug=0): self.debug = debug self.termination_character = '\n' self.port_settings = dict(baudrate=9600, bytesize=8, stopbits=1, parity='N', timeout=2, writeTimeout=2, xonxoff=False ) SerialInstrument.__init__(self, port) Stage.__init__(self) self.ui = None #configure stage parameters if str(device_index) not in Thorlabs_ELL8K.VALID_DEVICE_IDs: raise ValueError("Device ID: {} is not valid!".format(device_index)) self.device_index = device_index configuration = self.get_device_info() self.TRAVEL = configuration["travel"] self.PULSES_PER_REVOLUTION = configuration["pulses"] if self.debug > 0: print "Travel (degrees):", self.TRAVEL print "Pulses per revolution", self.PULSES_PER_REVOLUTION print "Device status:",self.get_device_status()
def __init__(self, SerialNum, HWType=22): Stage.__init__(self, unit="u") APTMotor.__init__(self, SerialNum=SerialNum, HWTYPE=HWType) self.axis_names = ["deg"] self.zero_pos = 0.0 self.serial_num = SerialNum self.ui = None
def __init__(self, port=None, source=0x01, destination=None, use_si_units=False, stay_alive=False, unit='m', **kwargs): """ Set up the serial port, setting source and destinations, and hardware info. """ APT_VCP.__init__(self, port=port, source=source, destination=destination, use_si_units=use_si_units, stay_alive=stay_alive) # this opens the port Stage.__init__(self, unit=unit) if self.model[1] in DC_status_motors: # Set the bit mask for DC controllers self.status_bit_mask = np.array( [[0x00000001, 'forward hardware limit switch is active'], [0x00000002, 'reverse hardware limit switch is active'], [0x00000010, 'in motion, moving forward'], [0x00000020, 'in motion, moving reverse'], [0x00000040, 'in motion, jogging forward'], [0x00000080, 'in motion, jogging reverse'], [0x00000200, 'in motion, homing'], [0x00000400, 'homed (homing has been completed)'], [0x00001000, 'tracking'], [0x00002000, 'settled'], [0x00004000, 'motion error (excessive position error)'], [0x01000000, 'motor current limit reached'], [0x80000000, 'channel is enabled']]) self.velocity_scaling_factor = 204.8 # for converting velocity to mm/sec else: # Set the bit mask for normal motor controllers self.status_bit_mask = { 0x00000001: 'forward (CW) hardware limit switch is active', 0x00000002: 'reverse (CCW) hardware limit switch is active', 0x00000004: 'forward (CW) software limit switch is active', 0x00000008: 'reverse (CCW) software limit switch is active', 0x00000010: 'in motion, moving forward (CW)', 0x00000020: 'in motion, moving reverse (CCW)', 0x00000040: 'in motion, jogging forward (CW)', 0x00000080: 'in motion, jogging reverse (CCW)', 0x00000100: 'motor connected', 0x00000200: 'in motion, homing', 0x00000400: 'homed (homing has been completed)', 0x00001000: 'interlock state (1 = enabled)' } # delattr(self, 'get_qt_ui') if type(destination) != dict and len(self.destination) == 1: self.destination = {'x': destination} else: self.axis_names = tuple(destination.keys()) self.destination = destination self.make_all_parameters()
def __init__(self,port='/dev/ttyUSB1', source=0x01, destination=0x50): Stage.__init__(self,unit="u") APT_VCP_motor.__init__(self,port=port,destination=destination,source=source) self.axis_names=["x"] self.set_channel_state(1,1) self.zero_pos = 0.0 self.ui = None print("initialized NR360SM") self.set_motion_params() # self.get_home_parameters() self.set_home_parameters() self.get_home_parameters()
def __init__(self, address, **kwargs): self.port_settings = dict(baudrate=38400, bytesize=8, stopbits=1, parity='N', xonxoff=True, timeout=0.5, writeTimeout=0.5, rtscts=True) SerialInstrument.__init__(self, address) self.termination_character = '\r\n' Stage.__init__(self, unit="step")
def __init__(self): ScanningExperiment.__init__(self) TimedScan.__init__(self) self.stage = Stage() self.stage_units = 1 self.axes = list(self.stage.axis_names) self.axes_names = list(str(ax) for ax in self.stage.axis_names) self.size = 1. * np.ones(len(self.axes), dtype=np.float64) self.step = 0.05 * np.ones(len(self.axes), dtype=np.float64) self.init = np.zeros(len(self.axes), dtype=np.float64) self.scan_axes = None # underscored attributes are made into properties self._num_axes = len(self.axes) self._unit_conversion = {'nm': 1e-9, 'um': 1e-6, 'mm': 1e-3} self._size_unit, self._step_unit, self._init_unit = ('um', 'um', 'um') self.grid_shape = (0,0)
def __init__(self, address, **kwargs): self.port_settings = dict(baudrate=9600, bytesize=8, stopbits=1, parity='N', xonxoff=True, timeout=0.5, writeTimeout=0.5, rtscts=True) SerialInstrument.__init__(self, address) self.termination_character = '\r\n' Stage.__init__(self) if 'offsetOrigin' in kwargs: self.offsetOrigin(kwargs['offsetOrigin']) # 20000) if 'home_on_start' in list(kwargs.keys()): if kwargs['home_on_start']: self.MechanicalHome()
def __init__(self, port=None,unit='u'): '''Set up baudrate etc and recenters the stage to the center of it's range (50um) Args: port(int/str): The port the device is connected to in any of the accepted serial formats ''' self.termination_character = '\n' self.port_settings = { 'baudrate':115200, 'bytesize':serial.EIGHTBITS, 'parity':serial.PARITY_NONE, 'stopbits':serial.STOPBITS_ONE, 'timeout':1, #wait at most one second for a response # 'writeTimeout':1, #similarly, fail if writing takes >1s # 'xonxoff':False, 'rtscts':False, 'dsrdtr':False, } si.SerialInstrument.__init__(self,port=port) Stage.__init__(self) self.unit = unit #This can be 'u' for micron or 'n' for nano self.recenter()
def __init__(self, camera=None, stage=None): # If no camera or stage is supplied, attempt to retrieve them - but crash with an exception if they don't exist. if camera is None: camera = Camera.get_instance(create=False) if stage is None: stage = Stage.get_instance(create=False) self.camera = camera self.stage = stage self.filter_images = False Instrument.__init__(self) shape = self.camera.color_image().shape self.datum_pixel = np.array(shape[:2])/2.0 # Default to using the centre of the image as the datum point # self.camera.set_legacy_click_callback(self.move_to_feature_pixel) self.camera.set_legacy_click_callback(self.move_to_pixel)
class GridScan(ScanningExperiment, TimedScan): """ Note that the axes (x,y,z) will relate to the indices (z,y,x) as per array standards. """ def __init__(self): ScanningExperiment.__init__(self) TimedScan.__init__(self) self.stage = Stage() self.stage_units = 1 self.axes = list(self.stage.axis_names) self.axes_names = list(str(ax) for ax in self.stage.axis_names) self.size = 1. * np.ones(len(self.axes), dtype=np.float64) self.step = 0.05 * np.ones(len(self.axes), dtype=np.float64) self.init = np.zeros(len(self.axes), dtype=np.float64) self.scan_axes = None # underscored attributes are made into properties self._num_axes = len(self.axes) self._unit_conversion = {'nm': 1e-9, 'um': 1e-6, 'mm': 1e-3} self._size_unit, self._step_unit, self._init_unit = ('um', 'um', 'um') self.grid_shape = (0,0) #self.init_grid(self.axes, self.size, self.step, self.init) def _update_axes(self, num_axes): """ Updates all axes related objects (and sequence lengths) when the number of axes is changed. :param num_axes: the new number of axes to scan :return: """ self._num_axes = num_axes # lists can be reassigned to copy current_axes = self.axes current_axes_names = self.axes_names # numpy arrays must be explicitly copied current_size, current_step, current_init = (self.size.copy(), self.step.copy(), self.init.copy()) if self.num_axes > len(current_axes): self.axes = ['']*self.num_axes self.axes_names = ['']*self.num_axes self.size, self.step, self.init = (np.zeros(self.num_axes), np.zeros(self.num_axes), np.zeros(self.num_axes)) self.axes[:len(current_axes)] = current_axes self.axes_names[:len(current_axes)] = current_axes_names self.size[:len(current_axes)] = current_size self.step[:len(current_axes)] = current_step self.init[:len(current_axes)] = current_init else: self.axes = current_axes[:self.num_axes] self.axes_names = current_axes_names[:self.num_axes] self.size = current_size[:self.num_axes] self.step = current_step[:self.num_axes] self.init = current_init[:self.num_axes] def rescale_parameter(self, param, value): """ Rescales the parameter (size, step or init) if its units are changed. :param param: :param value: :return: """ if value not in self._unit_conversion: raise ValueError('a valid unit must be supplied') unit_param = '_%s_unit' % param old_value = getattr(self, unit_param) if hasattr(self, unit_param) else value setattr(self, unit_param, value) a = getattr(self, param) a *= old_div(self._unit_conversion[old_value], self._unit_conversion[value]) num_axes = property(fget=lambda self: self._num_axes, fset=_update_axes) size_unit = property(fget=lambda self: self._size_unit, fset=lambda self, value: self.rescale_parameter('size', value)) step_unit = property(fget=lambda self: self._step_unit, fset=lambda self, value: self.rescale_parameter('step', value)) init_unit = property(fget=lambda self: self._init_unit, fset=lambda self, value: self.rescale_parameter('init', value)) si_size = property(fget=lambda self: self.size*self._unit_conversion[self.size_unit]) si_step = property(fget=lambda self: self.step*self._unit_conversion[self.step_unit]) si_init = property(fget=lambda self: self.init*self._unit_conversion[self.init_unit]) def run(self): """ Starts the grid scan in its own thread and runs the update function at the specified rate whilst acquiring the data. :param rate: the update period in seconds :return: """ if isinstance(self.acquisition_thread, threading.Thread) and self.acquisition_thread.is_alive(): print('scan already running') return self.init_scan() self.acquisition_thread = threading.Thread(target=self.scan, args=(self.axes, self.size, self.step, self.init)) self.acquisition_thread.start() def init_grid(self, axes, size, step, init): """Create a grid on which to scan.""" scan_axes = [] for i in range(len(axes)): s = size[i] * self._unit_conversion[self.size_unit] st = step[i] * self._unit_conversion[self.step_unit] s0 = init[i] * self._unit_conversion[self.init_unit] ax = np.arange(0, s+st/2., st) - s/2. + s0 scan_axes.append(ax) self.grid_shape = tuple(ax.size for ax in scan_axes) self.total_points = reduce(operator.mul, self.grid_shape, 1) self.scan_axes = scan_axes return scan_axes def init_current_grid(self): """Convenience method that initialises a grid based on current parameters.""" axes, size, step, init = (self.axes[::-1], self.size[::-1], self.step[::-1], self.init[::-1]) self.init_grid(axes, size, step, init) def set_stage(self, stage, axes=None): """ Sets the stage and move methods. :param axes: sequence of axes """ assert isinstance(stage, Stage), "stage must be an instance of Stage." self.stage = stage #self.move = self.stage.move if axes is None: self.axes = list(self.stage.axis_names[:self.num_axes]) else: for ax in axes: if ax not in self.stage.axis_names: raise ValueError('one of the supplied axes are invalid (not found in the stage axis names)') self.num_axes = len(axes) self.axes = list(axes) self.set_init_to_current_position() def move(self, position, axis): """Move to a position along a given axis.""" self.stage.move(old_div(position,self.stage_units), axis=axis) def get_position(self, axis): return self.stage.get_position(axis=axis) * self.stage_units def outer_loop_start(self): """This function is called before the scan happens, for each value of the outermost variable (usually Z)""" pass def middle_loop_start(self): """This function is called before the scan happens, for each value of the second-outermost variable (usually Y)""" pass def outer_loop_end(self): """This function is called after the scan happens, for each value of the outermost variable (usually Z)""" pass def middle_loop_end(self): """This function is called after the scan happens, for each value of the second-outermost variable (usually Y)""" pass def scan(self, axes, size, step, init): """Scans a grid, applying a function at each position.""" self.abort_requested = False axes, size, step, init = (axes[::-1], size[::-1], step[::-1], init[::-1]) scan_axes = self.init_grid(axes, size, step, init) print(scan_axes) self.open_scan() # get the indices of points along each of the scan axes for use with snaking over array pnts = [list(range(axis.size)) for axis in scan_axes] self.indices = [-1,] * len(axes) self._index = 0 self._step_times = np.zeros(self.grid_shape) self._step_times.fill(np.nan) self.status = 'acquiring data' self.acquiring.set() scan_start_time = time.time() for k in pnts[0]: # outer most axis self.indices = list(self.indices) self.indices[0] = k # Make sure indices is always up-to-date, for the drift compensation if self.abort_requested: break self.outer_loop_start() self.status = 'Scanning layer {0:d}/{1:d}'.format(k + 1, len(pnts[0])) self.move(scan_axes[0][k], axes[0]) pnts[1] = pnts[1][::-1] # reverse which way is iterated over each time for j in pnts[1]: if self.abort_requested: break self.move(scan_axes[1][j], axes[1]) self.indices = list(self.indices) self.indices[1] = j if len(axes) == 3: # for 3d grid (volume) scans self.middle_loop_start() pnts[2] = pnts[2][::-1] # reverse which way is iterated over each time for i in pnts[2]: if self.abort_requested: break self.move(scan_axes[2][i], axes[2]) self.indices[2] = i # These two lines are redundant. TODO: pick one... #self.indices = (k, j, i) # keeping it as a list allows index assignment self.scan_function(k, j, i) self._step_times[k,j,i] = time.time() self._index += 1 self.middle_loop_end() elif len(axes) == 2: # for regular 2d grid scans ignore third axis i self.indices = (k, j) self.scan_function(k, j) self._step_times[k,j] = time.time() self._index += 1 self.outer_loop_end() self.print_scan_time(time.time() - scan_start_time) self.acquiring.clear() # move back to initial positions for i in range(len(axes)): self.move(init[i]*self._unit_conversion[self._init_unit], axes[i]) # finish the scan self.analyse_scan() self.close_scan() self.status = 'scan complete' def vary_axes(self, name, multiplier=2.): if 'increase_size' in name: self.size *= multiplier elif 'decrease_size' in name: self.size /= multiplier elif 'increase_step' in name: self.step *= multiplier elif 'decrease_step' in name: self.step /= multiplier def set_init_to_current_position(self): for i, ax in enumerate(self.axes): self.init[i] = old_div(self.get_position(ax), self._unit_conversion[self.init_unit])