def execute(self, namespace): from PYME.Analysis.points.traveling_salesperson import sort points = namespace[self.input] try: positions = np.stack([points['x_um'], points['y_um']], axis=1) except KeyError: # units don't matter for these calculations, but we want to preserve them on the other side positions = np.stack([points['x'], points['y']], axis=1) / 1e3 start_index = 0 if not self.start_from_corner else np.argmin(positions.sum(axis=1)) positions, ogd, final_distance = sort.tsp_sort(positions, start_index, self.epsilon, return_path_length=True) out = tabular.DictSource({'x_um': positions[:, 0], 'y_um': positions[:, 1]}) out.mdh = MetaDataHandler.NestedClassMDHandler() try: out.mdh.copyEntriesFrom(points.mdh) except AttributeError: pass out.mdh['TravelingSalesperson.Distance'] = final_distance out.mdh['TravelingSalesperson.OriginalDistance'] = ogd namespace[self.output] = out
def test_tsp_sort(): from PYME.Analysis.points.traveling_salesperson import sort n = 500 x = np.random.rand(n) * 4e3 y = np.random.rand(n) * 4e3 og_positions = np.stack([x, y], axis=1) positions, og_distance, final_distance = sort.tsp_sort(og_positions, return_path_length=True) np.testing.assert_array_equal(np.unique(positions), np.unique(og_positions)) assert final_distance < og_distance
def measure_offsets(self, optimize_path=True, use_previous_scan=True): """ Visit each position and log the offset Parameters ---------- optimize_path : bool Flag to toggle visiting the positions in an order which minimizes the path relative to the microscope starting position. """ from PYME.Analysis.points.traveling_salesperson import sort n_positions = len(self._positions) offset = np.zeros(len(self._positions), dtype=float) lock_ok = np.ones(len(self._positions), dtype=bool) x, y = np.zeros_like(offset), np.zeros_like(offset) positions = np.zeros((n_positions, 2), dtype=float) for ind in range(n_positions): positions[ind, :] = (self._positions[ind]['x'], self._positions[ind]['y']) if optimize_path: current_pos = self._scope.GetPos() positions = sort.tsp_sort(positions, start=(current_pos['x'], current_pos['y'])) for ind in range(n_positions): self._scope.SetPos(x=positions[ind, 0], y=positions[ind, 1]) time.sleep(self._pause_on_relocate) if hasattr(self, '_focus_lock') and not self._focus_lock.LockOK(): logger.debug('focus lock not OK, scanning offset') if use_previous_scan: try: start_at = self.lookup_offset(positions[ind, 0], positions[ind, 1]) except: start_at = -25 else: start_at = -25 self._focus_lock.ReacquireLock(start_at=start_at) time.sleep(1.) if self._focus_lock.LockOK(): time.sleep(1.) actual = self._scope.GetPos() x[ind], y[ind] = actual['x'], actual['y'] offset[ind] = self._offset_piezo.GetOffset() try: lock_ok[ind] = self._focus_lock.LockOK() logger.debug('lock OK %s, x %.1f, y %.1f, offset %.1f' % (lock_ok[ind], x[ind], y[ind], offset[ind])) except AttributeError: logger.debug('x %.1f, y %.1f, offset %.1f' % (x[ind], y[ind], offset[ind])) self._scans.append({ 'x': x[lock_ok], 'y': y[lock_ok], 'offset': offset[lock_ok] })
def save(self, namespace, context={}): """ Parameters ---------- namespace : dict The recipe namespace context : dict Information about the source file to allow pattern substitution to generate the output name. At least 'basedir' (which is the fully resolved directory name in which the input file resides) and 'file_stub' (which is the filename without any extension) should be resolved. Notes ----- str spool_settings values can context-substitute templated parameters, e.g. spool_settings = {'subdirectory': '{file_stub}', 'extra_metadata: {'Samples.Well': '{file_stub}'}} """ # substitute spool settings spool_settings = self.spool_settings.copy() format_values(spool_settings, context) try: # get positions in units of micrometers positions = np.stack((namespace[self.input_positions]['x_um'], namespace[self.input_positions]['y_um']), axis=1) # (N, 2), [um] except KeyError: # assume x and y are in nanometers positions = np.stack((namespace[self.input_positions]['x'], namespace[self.input_positions]['y']), axis=1) / 1e3 # (N, 2), [nm] -> [um] if self.optimize_path: from PYME.Analysis.points.traveling_salesperson import sort start = -1 if self.lifo else 0 positions = sort.tsp_sort(positions, start) else: positions = positions[::-1, :] if self.lifo else positions dest = self.action_server_url + '/queue_actions' session = requests.Session() actions = list() for ri in range(positions.shape[0]): actions.append({ 'CentreROIOn': { 'x': positions[ri, 0], 'y': positions[ri, 1], 'then': { 'SpoolSeries': spool_settings } } }) session.post(dest + '?timeout=%f&nice=%d&max_duration=%f' % (self.timeout, self.nice, self.max_duration), data=json.dumps(actions), headers={'Content-Type': 'application/json'}) # queue a high-nice call to shut off all lasers when we're done # FIXME - there has to be a better way of handling this! # a) protocols should turn lasers off anyway # b) maybe have a a specific 'safe state' fallback in the action manager for when the queue is empty? session.post(dest + '?timeout=%f&nice=%d&max_duration=%f' % (self.timeout, 20, self.max_duration), data=json.dumps([{ 'FunctionAction': { 'functionName': 'turnAllLasersOff', 'args': {} } }]), headers={'Content-Type': 'application/json'})