Ejemplo n.º 1
0
    def __init__(self, positioner, data_processor, reader, writer=None, before_measurement_executor=None,
                 after_measurement_executor=None, initialization_executor=None, finalization_executor=None,
                 data_validator=None, settings=None, before_move_executor=None, after_move_executor=None):
        """
        Initialize scanner.
        :param positioner: Positioner should provide a generator to get the positions to move to.
        :param writer: Object that implements the write(position) method and sets the positions.
        :param data_processor: How to store and handle the data.
        :param reader: Object that implements the read() method to return data to the data_processor.
        :param before_measurement_executor: Callbacks executor that executed before measurements.
        :param after_measurement_executor: Callbacks executor that executed after measurements.
        :param before_move_executor: Callbacks executor that executes before each move.
        :param after_move_executor: Callbacks executor that executes after each move.
        """
        self.positioner = positioner
        self.writer = writer
        self.data_processor = data_processor
        self.reader = reader
        self.before_measurement_executor = before_measurement_executor
        self.after_measurement_executor = after_measurement_executor
        self.initialization_executor = initialization_executor
        self.finalization_executor = finalization_executor
        self.settings = settings or scan_settings()
        self.before_move_executor = before_move_executor
        self.after_move_executor = after_move_executor

        # If no data validator is provided, data is always valid.
        self.data_validator = data_validator or (lambda position, data: True)

        self._user_abort_scan_flag = False
        self._user_pause_scan_flag = False

        self._status = STATUS_INITIALIZED
Ejemplo n.º 2
0
    def test_bsread_positioner(self):
        config.bs_connection_mode = "pull"
        config.bs_default_host = "localhost"
        config.bs_default_port = 9999

        settling_time = 0.5

        n_messages = 10
        positioner = BsreadPositioner(n_messages)

        readables = ["bs://CAMERA1:X"]

        settings = scan_settings(settling_time=settling_time)
        time.sleep(1)

        # settings = scan_settings(bs_read_filter=mock_filter)
        result = scan(positioner=positioner,
                      readables=readables,
                      settings=settings)

        first_message_offset = result[0][0]

        # Expect to receive all messages from bsread from a pulse_id on; [[11], [12], [13], ...]
        expected_results = [[index + first_message_offset]
                            for index in range(n_messages)]

        self.assertListEqual(result, expected_results)
Ejemplo n.º 3
0
    def test_mulitple_messages_on_same_position(self):
        # DO NOT INCLUDE IN README - default.
        config.bs_connection_mode = "pull"
        config.bs_default_host = "localhost"
        config.bs_default_port = 9999
        config.bs_default_missing_property_value = Exception

        # Lets acquire 10 messages from the stream.
        n_images = 5
        n_measurements = 3
        positioner = StaticPositioner(n_images)
        settings = scan_settings(n_measurements=n_measurements)

        # Read camera X and Y value.
        readables = [bs_property("CAMERA1:X"), bs_property("CAMERA1:Y")]

        # The result will have 10 consecutive, valid, messages from the stream.
        result = scan(positioner=positioner,
                      readables=readables,
                      settings=settings)

        self.assertEqual(n_images, len(result))

        for position_index in range(n_images):
            first = result[position_index][0]

            for measurement_index in range(1, n_measurements):
                self.assertNotEqual(first,
                                    result[position_index][measurement_index])
Ejemplo n.º 4
0
    def test_bs_read_filter(self):
        config.bs_connection_mode = "pull"
        config.bs_default_host = "localhost"
        config.bs_default_port = 9999

        n_images = 10
        positioner = StaticPositioner(n_images)
        readables = ["bs://CAMERA1:X"]

        # Count how many messages passed.
        def mock_filter(message):
            if message:
                nonlocal filter_pass
                filter_pass += 1
            return True

        filter_pass = 0

        settings = scan_settings(bs_read_filter=mock_filter)

        # settings = scan_settings(bs_read_filter=mock_filter)
        result = scan(positioner=positioner,
                      readables=readables,
                      settings=settings)

        # The length should still be the same - the filter just throws away messages we do not want.
        self.assertEqual(len(result), n_images)

        self.assertTrue(filter_pass >= n_images,
                        "The filter passed less then the received messages.")
Ejemplo n.º 5
0
    def test_bsread_timestamp_read(self):
        config.bs_connection_mode = "pull"
        config.bs_default_host = "localhost"
        config.bs_default_port = 9999

        settling_time = 0.2
        messages_expected_factor = settling_time / MOCK_SENDER_INTERVAL

        n_images = 10
        positioner = StaticPositioner(n_images)
        readables = ["bs://CAMERA1:X"]

        settings = scan_settings(settling_time=settling_time)

        # settings = scan_settings(bs_read_filter=mock_filter)
        result = scan(positioner=positioner,
                      readables=readables,
                      settings=settings)

        # For messages_expected_factor=4; [[4], [8], [12], ...]
        expected_results = [[x]
                            for x in [(index + 1) * messages_expected_factor
                                      for index in range(n_images)]]

        self.assertListEqual(result, expected_results)
Ejemplo n.º 6
0
    def test_progress_monitor(self):

        current_index = []
        total_positions = 0
        current_percentage = []

        def progress(current_position, max_position):
            current_index.append(current_position)
            current_percentage.append(100 * (current_position / max_position))

            nonlocal total_positions
            total_positions = max_position

        positions = [1, 2, 3, 4, 5]
        positioner = VectorPositioner(positions)
        writables = epics_pv("PYSCAN:TEST:MOTOR1:SET",
                             "PYSCAN:TEST:MOTOR1:GET")
        readables = epics_pv("PYSCAN:TEST:OBS1")
        settings = scan_settings(progress_callback=progress)

        scan(positioner, readables, writables, settings=settings)

        self.assertEqual(
            len(positions) + 1, len(current_index),
            "The number of reported positions is wrong.")
        self.assertEqual(total_positions, 5,
                         "The number of total positions is wrong.")
        self.assertEqual(current_index, [0, 1, 2, 3, 4, 5],
                         "The reported percentage is wrong.")
        self.assertEqual(current_percentage, [0, 20, 40, 60, 80, 100],
                         "The reported percentage is wrong.")
Ejemplo n.º 7
0
    def test_same_condition_multiple_times(self):
        config.bs_connection_mode = "pull"
        config.bs_default_host = "localhost"
        config.bs_default_port = 9999

        positioner = StaticPositioner(5)

        readables = [bs_property("CAMERA1:X")]

        conditions = [
            bs_condition("CAMERA1:VALID",
                         10,
                         operation=ConditionComparison.EQUAL),
            bs_condition("CAMERA1:VALID",
                         10,
                         operation=ConditionComparison.EQUAL)
        ]

        result = scan(positioner=positioner,
                      readables=readables,
                      conditions=conditions,
                      settings=scan_settings(measurement_interval=0.25,
                                             n_measurements=1))

        self.assertTrue(all(x[0] == i + 1 for i, x in enumerate(result)),
                        "The result is wrong.")
Ejemplo n.º 8
0
    def test_multiple_conditions(self):
        config.bs_connection_mode = "pull"
        config.bs_default_host = "localhost"
        config.bs_default_port = 9999

        positioner = StaticPositioner(5)

        initialization = [action_set_epics_pv("PYSCAN:TEST:VALID1", 778)]

        readables = [bs_property("CAMERA1:X")]

        conditions = [
            bs_condition("CAMERA1:VALID",
                         10,
                         operation=ConditionComparison.EQUAL),
            bs_condition("CAMERA1:VALID",
                         11,
                         operation=ConditionComparison.LOWER),
            epics_condition("PYSCAN:TEST:VALID1",
                            778,
                            operation=ConditionComparison.LOWER_OR_EQUAL),
            epics_condition("PYSCAN:TEST:VALID1",
                            779,
                            operation=ConditionComparison.LOWER)
        ]

        result = scan(positioner=positioner,
                      readables=readables,
                      conditions=conditions,
                      initialization=initialization,
                      settings=scan_settings(measurement_interval=0.25,
                                             n_measurements=1))

        self.assertTrue(all(x[0] == i + 1 for i, x in enumerate(result)),
                        "The result is wrong.")
Ejemplo n.º 9
0
    def test_actions(self):
        positions = [[1, 1], [2, 2]]
        positioner = VectorPositioner(positions)

        writables = [
            epics_pv("PYSCAN:TEST:MOTOR1:SET", "PYSCAN:TEST:MOTOR1:GET"),
            epics_pv("PYSCAN:TEST:MOTOR2:SET", "PYSCAN:TEST:MOTOR2:GET")
        ]

        readables = [epics_pv("PYSCAN:TEST:OBS1")]

        # MOTOR1 initial values should be -11, MOTOR2 -22.
        cached_initial_values["PYSCAN:TEST:MOTOR1:SET"] = -11
        cached_initial_values["PYSCAN:TEST:MOTOR2:SET"] = -22
        initialization = [action_set_epics_pv("PYSCAN:TEST:OBS1", -33)]
        finalization = [action_restore(writables)]

        result = scan(positioner=positioner,
                      readables=readables,
                      writables=writables,
                      initialization=initialization,
                      finalization=finalization,
                      settings=scan_settings(measurement_interval=0.25,
                                             n_measurements=1))

        self.assertEqual(pv_cache["PYSCAN:TEST:MOTOR1:SET"][0].value, -11,
                         "Finalization did not restore original value.")
        self.assertEqual(pv_cache["PYSCAN:TEST:MOTOR2:SET"][0].value, -22,
                         "Finalization did not restore original value.")

        self.assertEqual(result[0][0], -33,
                         "Initialization action did not work.")
Ejemplo n.º 10
0
    def test_bsread_positioner_multi_measurements(self):

        with self.assertRaisesRegex(
                ValueError,
                "When using BsreadPositioner the maximum number of n_measurements = 1"
        ):
            scan(readables=[epics_pv("something")],
                 positioner=BsreadPositioner(10),
                 settings=scan_settings(n_measurements=2))
Ejemplo n.º 11
0
    def test_mixed_sources(self):
        config.bs_connection_mode = "pull"
        config.bs_default_host = "localhost"
        config.bs_default_port = 9999

        positions = [[1, 1], [2, 2], [3, 3], [4, 4]]
        positioner = VectorPositioner(positions)

        writables = [
            epics_pv("PYSCAN:TEST:MOTOR1:SET", "PYSCAN:TEST:MOTOR1:GET"),
            epics_pv("PYSCAN:TEST:MOTOR2:SET", "PYSCAN:TEST:MOTOR2:GET")
        ]

        readables = [
            bs_property("CAMERA1:X"),
            bs_property("CAMERA1:Y"),
            epics_pv("PYSCAN:TEST:OBS1")
        ]

        conditions = [
            epics_condition("PYSCAN:TEST:VALID1", 10),
            bs_condition("CAMERA1:VALID", 10)
        ]

        initialization = [
            action_set_epics_pv("PYSCAN:TEST:PRE1:SET", 1,
                                "PYSCAN:TEST:PRE1:GET")
        ]

        finalization = [
            action_set_epics_pv("PYSCAN:TEST:PRE1:SET", 0,
                                "PYSCAN:TEST:PRE1:GET"),
            action_restore(writables)
        ]

        result = scan(positioner=positioner,
                      readables=readables,
                      writables=writables,
                      conditions=conditions,
                      initialization=initialization,
                      finalization=finalization,
                      settings=scan_settings(measurement_interval=0.25,
                                             n_measurements=1))

        self.assertEqual(len(result), len(positions),
                         "Not the expected number of results.")

        # The first 2 attributes are from bs_read, they should be equal to the pulse ID processed.
        self.assertTrue(all(x[0] == x[1] and x[2] == 1 for x in result),
                        "The result is wrong.")
Ejemplo n.º 12
0
def _generate_scan_parameters(relative, writables, latency):
    # If the scan is relative we collect the initial writables offset, and restore the state at the end of the scan.
    offsets = None
    finalization_action = []
    if relative:
        pv_names = [x.pv_name for x in convert_to_list(writables) or []]
        reader = EPICS_READER(pv_names)
        offsets = reader.read()
        reader.close()

        finalization_action.append(action_restore(writables))

    settings = scan_settings(settling_time=latency)

    return offsets, finalization_action, settings
Ejemplo n.º 13
0
    def test_bsread_dal_cache_invalidation(self):
        config.bs_connection_mode = "pull"
        config.bs_default_host = "localhost"
        config.bs_default_port = 9999

        settling_time = 0.15

        n_messages = 10
        positioner = BsreadPositioner(n_messages)

        readables = ["bs://CAMERA1:X"]

        counter = 0

        def odd_condition(message):
            nonlocal counter
            counter += 1

            if counter % 2 == 0:
                return True

            return False

        conditions = [
            function_condition(odd_condition, action=ConditionAction.Retry)
        ]

        settings = scan_settings(settling_time=settling_time)
        time.sleep(1)

        # settings = scan_settings(bs_read_filter=mock_filter)
        result = scan(positioner=positioner,
                      readables=readables,
                      conditions=conditions,
                      settings=settings)

        first_message_offset = result[0][0]

        # Expect to receive all odd messages from bsread from a pulse_id on; [[10], [12], [14], ...]
        expected_results = [[first_message_offset + (2 * index)]
                            for index in range(n_messages)]

        self.assertListEqual(result, expected_results)
Ejemplo n.º 14
0
def scanner(positioner,
            readables,
            writables=None,
            conditions=None,
            before_read=None,
            after_read=None,
            initialization=None,
            finalization=None,
            settings=None,
            data_processor=None,
            before_move=None,
            after_move=None):
    # Allow a list or a single value to be passed. Initialize None values.
    writables = convert_input(convert_to_list(writables) or [])
    readables = convert_input(convert_to_list(readables) or [])
    conditions = convert_conditions(convert_to_list(conditions) or [])
    before_read = convert_to_list(before_read) or []
    after_read = convert_to_list(after_read) or []
    before_move = convert_to_list(before_move) or []
    after_move = convert_to_list(after_move) or []
    initialization = convert_to_list(initialization) or []
    finalization = convert_to_list(finalization) or []
    settings = settings or scan_settings()

    # TODO: Ugly. The scanner should not depend on a particular positioner implementation.
    if isinstance(positioner,
                  BsreadPositioner) and settings.n_measurements > 1:
        raise ValueError(
            "When using BsreadPositioner the maximum number of n_measurements = 1."
        )

    bs_reader = _initialize_bs_dal(readables, conditions,
                                   settings.bs_read_filter, positioner)

    epics_writer, epics_pv_reader, epics_condition_reader = _initialize_epics_dal(
        writables, readables, conditions, settings)

    function_writer, function_reader, function_condition = _initialize_function_dal(
        writables, readables, conditions)

    writables_order = [type(writable) for writable in writables]

    # Write function needs to merge PV and function proxy data.
    def write_data(positions):
        positions = convert_to_list(positions)
        pv_values = [
            x for x, source in zip(positions, writables_order)
            if source == EPICS_PV
        ]
        function_values = [
            x for x, source in zip(positions, writables_order)
            if source == FUNCTION_VALUE
        ]

        if epics_writer:
            epics_writer.set_and_match(pv_values)

        if function_writer:
            function_writer.write(function_values)

    # Order of value sources, needed to reconstruct the correct order of the result.
    readables_order = [type(readable) for readable in readables]

    # Read function needs to merge BS, PV, and function proxy data.
    def read_data(current_position_index, retry=False):
        _logger.debug("Reading data for position index %s." %
                      current_position_index)

        bs_values = iter(
            bs_reader.read(current_position_index, retry) if bs_reader else [])
        epics_values = iter(
            epics_pv_reader.read(current_position_index
                                 ) if epics_pv_reader else [])
        function_values = iter(
            function_reader.read(current_position_index
                                 ) if function_reader else [])

        # Interleave the values correctly.
        result = []
        for source in readables_order:
            if source == BS_PROPERTY:
                next_result = next(bs_values)
            elif source == EPICS_PV:
                next_result = next(epics_values)
            elif source == FUNCTION_VALUE:
                next_result = next(function_values)
            else:
                raise ValueError("Unknown type of readable %s used." % source)

            # We flatten the result, whenever possible.
            if isinstance(next_result, list) and source != FUNCTION_VALUE:
                result.extend(next_result)
            else:
                result.append(next_result)

        return result

    # Order of value sources, needed to reconstruct the correct order of the result.
    conditions_order = [type(condition) for condition in conditions]

    # Validate function needs to validate both BS, PV, and function proxy data.
    def validate_data(current_position_index, data):
        _logger.debug("Reading data for position index %s." %
                      current_position_index)

        bs_values = iter(
            bs_reader.read_cached_conditions() if bs_reader else [])
        epics_values = iter(
            epics_condition_reader.read(current_position_index
                                        ) if epics_condition_reader else [])
        function_values = iter(
            function_condition.read(current_position_index
                                    ) if function_condition else [])

        for index, source in enumerate(conditions_order):

            if source == BS_CONDITION:
                value = next(bs_values)
            elif source == EPICS_CONDITION:
                value = next(epics_values)
            elif source == FUNCTION_CONDITION:
                value = next(function_values)
            else:
                raise ValueError("Unknown type of condition %s used." % source)

            value_valid = False

            # Function conditions are self contained.
            if source == FUNCTION_CONDITION:
                if value:
                    value_valid = True

            else:
                expected_value = conditions[index].value
                tolerance = conditions[index].tolerance
                operation = conditions[index].operation

                if compare_channel_value(value, expected_value, tolerance,
                                         operation):
                    value_valid = True

            if not value_valid:

                if conditions[index].action == ConditionAction.Retry:
                    return False

                if source == FUNCTION_CONDITION:
                    raise ValueError("Function condition %s returned False." %
                                     conditions[index].identifier)

                else:
                    raise ValueError(
                        "Condition %s failed, expected value %s, actual value %s, "
                        "tolerance %s, operation %s." %
                        (conditions[index].identifier, conditions[index].value,
                         value, conditions[index].tolerance,
                         conditions[index].operation))

        return True

    if not data_processor:
        data_processor = DATA_PROCESSOR()

    # Before acquisition hook.
    before_measurement_executor = None
    if before_read:
        before_measurement_executor = ACTION_EXECUTOR(before_read).execute

    # After acquisition hook.
    after_measurement_executor = None
    if after_read:
        after_measurement_executor = ACTION_EXECUTOR(after_read).execute

    # Executor before each move.
    before_move_executor = None
    if before_move:
        before_move_executor = ACTION_EXECUTOR(before_move).execute

    # Executor after each move.
    after_move_executor = None
    if after_move:
        after_move_executor = ACTION_EXECUTOR(after_move).execute

    # Initialization (before move to first position) hook.
    initialization_executor = None
    if initialization:
        initialization_executor = ACTION_EXECUTOR(initialization).execute

    # Finalization (after last acquisition AND on error) hook.
    finalization_executor = None
    if finalization:
        finalization_executor = ACTION_EXECUTOR(finalization).execute

    scanner = Scanner(positioner=positioner,
                      data_processor=data_processor,
                      reader=read_data,
                      writer=write_data,
                      before_measurement_executor=before_measurement_executor,
                      after_measurement_executor=after_measurement_executor,
                      initialization_executor=initialization_executor,
                      finalization_executor=finalization_executor,
                      data_validator=validate_data,
                      settings=settings,
                      before_move_executor=before_move_executor,
                      after_move_executor=after_move_executor)

    return scanner
Ejemplo n.º 15
0
    def execute_scan(self):

        after_executor = self.get_action_executor("In-loopPostAction")

        # Wrap the post action executor to update the number of completed scans.
        def progress_after_executor(scanner_instance, data):
            # Execute other post actions.
            after_executor(scanner_instance)

            # Update progress.
            self.n_done_measurements += 1
            self.ProgDisp.Progress = 100.0 * (self.n_done_measurements /
                                              self.n_total_positions)

        def prepare_monitors(reader):
            # If there are no monitors defined we have nothing to validate.
            if not self.dimensions[-1]["Monitor"]:
                return None

            def validate_monitors(position, data):
                monitor_values = reader.read()
                combined_data = zip(self.dimensions[-1]['Monitor'],
                                    self.dimensions[-1]['MonitorValue'],
                                    self.dimensions[-1]['MonitorTolerance'],
                                    self.dimensions[-1]['MonitorAction'],
                                    self.dimensions[-1]['MonitorTimeout'],
                                    monitor_values)

                for pv, expected_value, tolerance, action, timeout, value in combined_data:
                    # Monitor value does not match.
                    if not compare_channel_value(value, expected_value,
                                                 tolerance):
                        if action == "Abort":
                            raise ValueError(
                                "Monitor %s, expected value %s, tolerance %s, has value %s. Aborting."
                                % (pv, expected_value, tolerance, value))
                        elif action == "WaitAndAbort":
                            return False
                        else:
                            raise ValueError(
                                "MonitorAction %s, on PV %s, is not supported."
                                % (pv, action))

                return True

            return validate_monitors

        # Setup scan settings.
        settings = scan_settings(
            settling_time=self.dimensions[-1]["KnobWaitingExtra"],
            n_measurements=self.dimensions[-1]["NumberOfMeasurements"],
            measurement_interval=self.dimensions[-1]["Waiting"])

        data_processor = PyScanDataProcessor(
            self.outdict,
            n_readbacks=self.n_readbacks,
            n_validations=self.n_validations,
            n_observables=self.n_observables,
            n_measurements=settings.n_measurements)

        self.scanner = Scanner(
            positioner=self.get_positioner(),
            data_processor=data_processor,
            reader=self.epics_dal.get_group(READ_GROUP).read,
            writer=self.epics_dal.get_group(WRITE_GROUP).set_and_match,
            before_measurement_executor=self.get_action_executor(
                "In-loopPreAction"),
            after_measurement_executor=progress_after_executor,
            initialization_executor=self.get_action_executor("PreAction"),
            finalization_executor=self.get_action_executor("PostAction"),
            data_validator=prepare_monitors(
                self.epics_dal.get_group(MONITOR_GROUP)),
            settings=settings)

        self.outdict.update(self.scanner.discrete_scan())