class Scientifica(Stage): """ A Scientifica motorized device. This class supports PatchStar, MicroStar, SliceScope, objective changers, etc. The device may be identified either by its serial port or by its description string: port: <serial port> # eg. 'COM1' or '/dev/ttyACM0' name: <string> # eg. 'SliceScope' or 'MicroStar 2' baudrate: <int> # may be 9600 or 38400 The optional 'baudrate' parameter is used to set the baudrate of the device. Both valid rates will be attempted when initially connecting. """ def __init__(self, man, config, name): # can specify port = config.pop('port', None) name = config.pop('name', None) self.scale = config.pop('scale', (1e-6, 1e-6, 1e-6)) baudrate = config.pop('baudrate', None) ctrl_version = config.pop('version', 2) try: self.dev = ScientificaDriver(port=port, name=name, baudrate=baudrate, ctrl_version=ctrl_version) except RuntimeError as err: if hasattr(err, 'dev_version'): raise RuntimeError(err.message + " You must add `version=%d` to the configuration for this device and double-check any speed/acceleration parameters." % int(err.dev_version)) else: raise # Controllers reset their baud to 9600 after power cycle if baudrate is not None and self.dev.getBaudrate() != baudrate: self.dev.setBaudrate(baudrate) self._lastMove = None man.sigAbortAll.connect(self.abort) Stage.__init__(self, man, config, name) # clear cached position for this device and re-read to generate an initial position update self._lastPos = None self.getPosition(refresh=True) # Set approach angle # Disabled--this toggles the approach bit and we can't reconfigure it from here :( # approach = self.dev.send('APPROACH') # self.dev.send('ANGLE %f' % self.pitch) # self.dev.send('APPROACH %s' % approach) # reset approach bit; setting angle enables it # set any extra parameters specified in the config params = config.get('params', {}) for param, val in params.items(): if param == 'currents': assert len(val) == 2 self.dev.setCurrents(*val) elif param == 'axisScale': assert len(val) == 3 for i, x in enumerate(val): self.dev.setAxisScale(i, x) else: self.dev.setParam(param, val) self.setUserSpeed(config.get('userSpeed', self.dev.getSpeed() * abs(self.scale[0]))) # whether to monitor for changes to a MOC self.monitorObj = config.get('monitorObjective', False) if self.monitorObj is True: if self.dev._version < 3: raise TypeError("Scientifica motion card version %s does not support reading objective position." % self.dev._version) self.objectiveState = None self._checkObjective() # thread for polling position changes self.monitor = MonitorThread(self, self.monitorObj) self.monitor.start() def capabilities(self): """Return a structure describing the capabilities of this device""" if 'capabilities' in self.config: return self.config['capabilities'] else: return { 'getPos': (True, True, True), 'setPos': (True, True, True), 'limits': (False, False, False), } def stop(self): """Stop the manipulator immediately. """ with self.lock: self.dev.stop() if self._lastMove is not None: self._lastMove._stopped() self._lastMove = None def abort(self): """Stop the manipulator immediately. """ self.dev.stop() if self._lastMove is not None: self._lastMove._stopped() self._lastMove = None def setUserSpeed(self, v): """Set the maximum speed of the stage (m/sec) when under manual control. The stage's maximum speed is reset to this value when it is not under programmed control. """ self.userSpeed = v self.dev.setSpeed(v / abs(self.scale[0])) def _getPosition(self): # Called by superclass when user requests position refresh with self.lock: pos = self.dev.getPos() pos = [pos[i] * self.scale[i] for i in (0, 1, 2)] if pos != self._lastPos: self._lastPos = pos emit = True else: emit = False if emit: # don't emit signal while locked self.posChanged(pos) return pos def targetPosition(self): with self.lock: if self._lastMove is None or self._lastMove.isDone(): return self.getPosition() else: return self._lastMove.targetPos def quit(self): self.monitor.stop() Stage.quit(self) def _move(self, abs, rel, speed, linear): with self.lock: if self._lastMove is not None and not self._lastMove.isDone(): self.stop() pos = self._toAbsolutePosition(abs, rel) speed = self._interpretSpeed(speed) self._lastMove = ScientificaMoveFuture(self, pos, speed, self.userSpeed) return self._lastMove def deviceInterface(self, win): return ScientificaGUI(self, win) def startMoving(self, vel): """Begin moving the stage at a continuous velocity. """ s = [int(-v * 1000. / 67. / self.scale[i]) for i,v in enumerate(vel)] print(s) self.dev.send('VJ %d %d %d C' % tuple(s)) def _checkObjective(self): with self.lock: obj = int(self.dev.send('obj')) if obj != self.objectiveState: self.objectiveState = obj self.sigSwitchChanged.emit(self, {'objective': obj}) def getSwitch(self, name): if name == 'objective' and self.monitorObj: return self.objectiveState else: return Stage.getSwitch(self, name)
print( "Usage: python -i test.py com4 [9600|38400]\n python -i test.py PatchStar1" ) sys.exit(-1) baudrate = int(sys.argv[2]) if len(sys.argv) > 2 else None devname = sys.argv[1] if devname.lower().startswith('com') or devname.startswith('/dev/'): ps = Scientifica(port=devname, baudrate=baudrate, ctrl_version=None) else: ps = Scientifica(name=devname, baudrate=baudrate, ctrl_version=None) print("Device type: %s Description: %s" % (ps.getType(), ps.getDescription())) print("Firmware version: %r" % ps.getFirmwareVersion()) print("Position: %r" % ps.getPos()) print("Max speed: %r um/sec" % ps.getSpeed()) if ps._version < 3: print("Min speed: %r um/sec" % (ps.getParam('minSpeed') / (2. * ps.getAxisScale(0)))) print("Acceleration: %r um^2/sec" % (ps.getParam('accel') * 250. / ps.getAxisScale(0))) else: print("Min speed: %r um/sec" % ps.getParam('minSpeed')) print("Acceleration: %r um^2/sec" % ps.getParam('accel')) # pos1 = ps.getPos() # pos2 = [None, None, pos1[2]] # pos2[2] += 1000 # print("Move %s => %s" % (pos1, pos2)) # ps.moveTo(pos2, speed=300)