diff --git a/src/sardana/macroserver/macros/scan.py b/src/sardana/macroserver/macros/scan.py index f71f85094d..970f4e7cb9 100644 --- a/src/sardana/macroserver/macros/scan.py +++ b/src/sardana/macroserver/macros/scan.py @@ -127,7 +127,16 @@ def _prepare(self, motorlist, startlist, endlist, scan_length, integ_time, self.motors = motorlist self.starts = numpy.array(startlist, dtype='d') - self.finals = numpy.array(endlist, dtype='d') + if scan_length: + self.finals = numpy.array(endlist, dtype='d') + else: + self.finals = numpy.array(startlist, dtype='d') + if scan_length < 0: + self.do_last_point = False + scan_length = -scan_length + else: + self.do_last_point = True + self.mode = mode self.integ_time = integ_time self.opts = opts @@ -156,7 +165,11 @@ def _prepare(self, motorlist, startlist, endlist, scan_length, integ_time, if mode == StepMode: self.nr_interv = scan_length self.nb_points = self.nr_interv + 1 - self.interv_sizes = (self.finals - self.starts) / self.nr_interv + if self.nr_interv: + self.interv_sizes = (self.finals - self.starts) / self.nr_interv + else: + self.interv_sizes = (self.finals - self.starts) * 0 + self.name = opts.get('name', 'a%iscan' % self.N) self._gScan = SScan(self, self._stepGenerator, moveables, env, constrains, extrainfodesc) @@ -310,7 +323,10 @@ def getTimeEstimation(self): path = MotionPath(v_motor, 0, length) max_step0_time = max(max_step0_time, path0.duration) max_step_time = max(max_step_time, path.duration) - motion_time = max_step0_time + self.nr_interv * max_step_time + if self.nr_interv: + motion_time = max_step0_time + self.nr_interv * max_step_time + else: + motion_time = max_step0_time # calculate acquisition time acq_time = self.nb_points * self.integ_time total_time = motion_time + acq_time @@ -332,7 +348,10 @@ def _fill_missing_records(self): nb_of_points = self.nb_points scan = self._gScan nb_of_records = len(scan.data.records) - missing_records = nb_of_points - nb_of_records + if not hasattr(self, "do_last_point") or self.do_last_point: + missing_records = nb_of_points - nb_of_records + else: + missing_records = nb_of_points - nb_of_records - 1 scan.data.initRecords(missing_records) def _get_nr_points(self): @@ -1936,6 +1955,7 @@ def prepare(self, m1, m1_start_pos, m1_final_pos, m1_nr_interv, self.starts = numpy.array([m1_start_pos, m2_start_pos], dtype='d') self.finals = numpy.array([m1_final_pos, m2_final_pos], dtype='d') self.nr_intervs = numpy.array([m1_nr_interv, m2_nr_interv], dtype='i') + self.do_last_point = True # Number of intervals of the first motor which is doing the # continuous scan. @@ -2055,7 +2075,7 @@ def _get_nr_points(self): class timescan(Macro, Hookable): """Do a time scan over the specified time intervals. The scan starts - immediately. The number of data points collected will be nr_interv + 1. + immediately. The number of data points collected will be nr_points. Count time is given by integ_time. Latency time will be the longer one of latency_time and measurement group latency time. """ @@ -2064,13 +2084,13 @@ class timescan(Macro, Hookable): 'post-acq', 'post-scan')} param_def = [ - ['nr_interv', Type.Integer, None, 'Number of scan intervals'], + ['nb_points', Type.Integer, None, 'Number of scan points'], ['integ_time', Type.Float, None, 'Integration time'], ['latency_time', Type.Float, 0, 'Latency time']] - def prepare(self, nr_interv, integ_time, latency_time): - self.nr_interv = nr_interv - self.nb_points = nr_interv + 1 + def prepare(self, nb_points, integ_time, latency_time): + self.nr_interv = nb_points - 1 + self.nb_points = nb_points self.integ_time = integ_time self.latency_time = latency_time self._gScan = TScan(self) diff --git a/src/sardana/macroserver/macros/test/test_scan.py b/src/sardana/macroserver/macros/test/test_scan.py index fcbca22e45..c8ef29e3f0 100644 --- a/src/sardana/macroserver/macros/test/test_scan.py +++ b/src/sardana/macroserver/macros/test/test_scan.py @@ -144,6 +144,61 @@ def macro_runs(self, macro_params=None, wait_timeout=30.0): self.assertAlmostEqual(data[last_pos][value], expected_final_pos, 7, "Final possition differs from set value (using getLog)") +@testRun(macro_params=[_m1, '0', '5', '0', '.1'], wait_timeout=30.0) +@testStop(macro_params=[_m1, '0', '5', '0', '.1']) +class AscanOnePointTest(ANscanTest, unittest.TestCase): + + """Test of ascan macro. See :class:`ANscanTest` for requirements. + It verifies that macro ascan can be executed and stoped and tests + the output of the ascan using data from log system and macro data. + """ + macro_name = 'ascan' + + def macro_runs(self, macro_params=None, wait_timeout=30.0): + """Reimplementation of macro_runs method for ascan macro. + It verifies using double checking, with log output and data from + the macro: + + - The motor initial and final positions of the scan are the + ones given as input. + + - Intervals in terms of motor position between one point and + the next one are equidistant. + """ + # call the parent class implementation + ANscanTest.macro_runs(self, macro_params=macro_params, + wait_timeout=wait_timeout) + + mot_name = macro_params[0] + expected_init_pos = float(macro_params[1]) + expected_final_pos = float(macro_params[2]) + self.steps = int(macro_params[-2]) + + # Test data from macro (macro_executor.getData()) + data = self.macro_executor.getData() + mot_init_pos = data[min(data.keys())].data[mot_name] + mot_final_pos = data[max(data.keys())].data[mot_name] + pre = mot_init_pos + + + self.assertAlmostEqual(mot_init_pos, expected_init_pos, 7, + "Initial possition differs from set value (using getData)") + self.assertAlmostEqual(mot_final_pos, expected_init_pos, 7, + "Final possition differs from set value (using getData)") + + # Test data from log_output (macro_executor.getLog('output')) + log_output = self.macro_executor.getLog('output') + data = parsing_log_output(log_output) + init_pos = 0 + last_pos = -1 + value = 1 + pre = data[init_pos] + + self.assertAlmostEqual(data[init_pos][value], expected_init_pos, 7, + "Initial possition differs from set value (using getLog)") + self.assertAlmostEqual(data[last_pos][value], expected_init_pos, 7, + "Final possition differs from set value (using getLog)") + @testRun(macro_params=[_m1, '-1', '1', '2', '.1'], wait_timeout=30) @testStop(macro_params=[_m1, '1', '-1', '3', '.1']) diff --git a/src/sardana/macroserver/macros/test/test_scanct.py b/src/sardana/macroserver/macros/test/test_scanct.py index 702013cadb..b6ab60da48 100644 --- a/src/sardana/macroserver/macros/test/test_scanct.py +++ b/src/sardana/macroserver/macros/test/test_scanct.py @@ -126,8 +126,9 @@ def check_using_output(self, expected_nb_points): "Checked using macro output") self.assertEqual(obtained_nb_points, expected_nb_points, msg) - msg = "Scan points are NOT in good order.\nChecked using macro output" - self.assertTrue(ordered_points, msg) + if expected_nb_points > 1: + msg = "Scan points are NOT in good order.\nChecked using macro output" + self.assertTrue(ordered_points, msg) def check_using_data(self, expected_nb_points): # Test data from macro (macro_executor.getData()) @@ -145,9 +146,10 @@ def check_using_data(self, expected_nb_points): "\nChecked using macro data.") self.assertEqual(obtained_nb_points_data, expected_nb_points, msg) - msg = ("Scan points are NOT in good order." - "\nChecked using macro data.") - self.assertTrue(order_points_data, msg) + if expected_nb_points > 1: + msg = ("Scan points are NOT in good order." + "\nChecked using macro data.") + self.assertTrue(order_points_data, msg) def check_stopped(self): self.assertStopped('Macro %s did not stop' % self.macro_name) @@ -225,6 +227,7 @@ def tearDown(self): } } ascanct_params_1 = ['_test_mt_1_1', '0', '10', '100', '0.1'] +ascanct_params_2 = ['_test_mt_1_1', '10', '10', '0', '0.1'] @testRun(meas_config=mg_config1, macro_params=ascanct_params_1, @@ -235,6 +238,8 @@ def tearDown(self): wait_timeout=30) @testRun(meas_config=mg_config4, macro_params=ascanct_params_1, wait_timeout=30) +@testRun(meas_config=mg_config4, macro_params=ascanct_params_2, + wait_timeout=30) @testStop(meas_config=mg_config1, macro_params=ascanct_params_1, stop_delay=5, wait_timeout=20) class AscanctTest(ScanctTest, unittest.TestCase): diff --git a/src/sardana/macroserver/scan/gscan.py b/src/sardana/macroserver/scan/gscan.py index fe0fe16421..174decbfe9 100644 --- a/src/sardana/macroserver/scan/gscan.py +++ b/src/sardana/macroserver/scan/gscan.py @@ -1264,7 +1264,9 @@ def stepUp(self, n, step, lstep): if 'extrainfo' in step: data_line.update(step['extrainfo']) - self.data.addRecord(data_line) + if not hasattr(self.macro, "do_last_point") or \ + self.macro.do_last_point or n + 1 != self.macro.nb_points: + self.data.addRecord(data_line) # post-step hooks for hook in step.get('post-step-hooks', ()): @@ -1904,7 +1906,6 @@ def scan_loop(self): # start move & acquisition as close as possible # from this point on synchronization becomes critical manager.add_job(self.go_through_waypoints) - while not self._all_waypoints_finished: # wait for motor to reach start position @@ -2002,7 +2003,9 @@ def scan_loop(self): if 'extrainfo' in step: data_line.update(step['extrainfo']) - self.data.addRecord(data_line) + if not hasattr(self.macro, "do_last_point") or \ + self.macro.do_last_point or point_nb + 1 != nb_points: + self.data.addRecord(data_line) if scream: yield ((point_nb + 1) / nb_points) * 100 @@ -2052,6 +2055,9 @@ def value_buffer_changed(self, channel, value_buffer): return full_name = channel.getFullName() + if hasattr(self.macro, "do_last_point") and not self.macro.do_last_point \ + and self.macro.nb_points - 1 in value_buffer['index']: + return info = {'label': full_name} if self._index_offset != 0: @@ -2080,6 +2086,9 @@ def value_ref_buffer_changed(self, channel, value_ref_buffer): full_name = channel.getFullName() info = {'label': full_name} + if hasattr(self.macro, "do_last_point") and not self.macro.do_last_point \ + and self.macro.nb_points - 1 in value_ref_buffer['index']: + return if self._index_offset != 0: idx = np.array(value_ref_buffer['index']) idx += self._index_offset @@ -2251,7 +2260,6 @@ def prepare_waypoint(self, waypoint, start_positions, iterate_only=False): time of all the motors - active_time: time interval while all the physical motors will maintain constant velocity""" - positions = waypoint['positions'] active_time = waypoint["active_time"] @@ -2279,10 +2287,14 @@ def prepare_waypoint(self, waypoint, start_positions, iterate_only=False): zip(self._physical_moveables, start_positions, positions): total_displacement = abs(end - start) direction = 1 if end > start else -1 - interval_displacement = total_displacement / self.macro.nr_interv + if self.macro.nr_interv: + interval_displacement = total_displacement / self.macro.nr_interv + else: + interval_displacement = 0. # move further in order to acquire the last point at constant # velocity - end = end + direction * interval_displacement + if not hasattr(self.macro, "do_last_point") or self.macro.do_last_point: + end = end + direction * interval_displacement base_vel = moveable.getBaseRate() ideal_vmotor = VMotor(accel_time=acc_time, @@ -2294,7 +2306,7 @@ def prepare_waypoint(self, waypoint, start_positions, iterate_only=False): backup_vel = moveable.getVelocity(force=True) ideal_max_vel = try_vel = ideal_path.max_vel try: - while True: + while self.macro.nr_interv: moveable.setVelocity(try_vel) get_vel = moveable.getVelocity(force=True) if get_vel < ideal_max_vel: @@ -2356,6 +2368,9 @@ def _go_through_waypoints(self): " theoretical values" ) for i, waypoint in waypoints: + if hasattr(self.macro, "do_last_point") and \ + not self.macro.do_last_point and i + 1 == self.macro.nb_points: + break self.macro.debug("Waypoint iteration...") start_positions = waypoint.get('start_positions') @@ -2400,7 +2415,7 @@ def _go_through_waypoints(self): return # at least one motor must have different start and final positions - if all(self.macro.starts == self.macro.finals): + if self.macro.nb_points > 1 and all(self.macro.starts == self.macro.finals): if len(self.macro.starts) > 1: msg = "Scan start and end must be different for at " \ "least one motor" @@ -2434,7 +2449,10 @@ def _go_through_waypoints(self): final = path._final_user_pos total_position = (final - start) / repeats initial_position = start - total_time = abs(total_position) / path.max_vel + if self.macro.nb_points > 1: + total_time = abs(total_position) / path.max_vel + else: + total_time = 0. delay_time = path.max_vel_time delay_position = start - path.initial_user_pos synch = [ @@ -2514,8 +2532,11 @@ def _go_through_waypoints(self): nb_points) theoretical_timestamps = generate_timestamps(synch, dt_timestamp) for index, data in list(theoretical_positions.items()): - data.update(theoretical_timestamps[index]) - initial_data[index + self._index_offset] = data + if not hasattr(self.macro, "do_last_point") or \ + self.macro.do_last_point or index + 1 != len(theoretical_positions): + data.update(theoretical_timestamps[index]) + initial_data[index + self._index_offset] = data + # TODO: this changes the initial data on-the-fly - seems like not # the best practice self.data.initial_data = initial_data @@ -2741,7 +2762,9 @@ def stepUp(self, n, step, lstep): if 'extrainfo' in step: data_line.update(step['extrainfo']) - self.data.addRecord(data_line) + if not hasattr(self.macro, "do_last_point") or \ + self.macro.do_last_point or n + 1 != self.macro.nb_points: + self.data.addRecord(data_line) # post-step hooks for hook in step.get('post-step-hooks', ()): @@ -2858,7 +2881,10 @@ def _fill_missing_records(self): # fill record list with dummy records for the final padding nb_points = self.macro.nb_points records = len(self.data.records) - missing_records = nb_points - records + if not hasattr(self.macro, "do_last_point") or self.macro.do_last_point: + missing_records = nb_points - records + else: + missing_records = nb_points - records - 1 self.data.initRecords(missing_records) def _estimate(self):