def test_configure_with_breakpoints(self):
     xs = LineGenerator("x", "mm", 0.0, 0.5, 3, alternate=True)
     ys = LineGenerator("y", "mm", 0.0, 0.1, 2)
     generator = CompoundGenerator([ys, xs], [], [], 0.1)
     generator.prepare()
     completed_steps = 0
     steps_to_do = 3
     info = ExposureDeadtimeInfo(0.01, 1000, 0.0)
     part_info = dict(anyname=[info])
     self.set_attributes(self.child, triggerMode="Internal")
     self.o.on_configure(
         self.context,
         completed_steps,
         steps_to_do,
         part_info,
         generator,
         fileDir="/tmp",
         breakpoints=[3, 3],
         exposure=info.calculate_exposure(generator.duration),
     )
     assert self.child.handled_requests.mock_calls == [
         call.put("arrayCallbacks", True),
         call.put("arrayCounter", 0),
         call.put("exposure", 0.1 - 0.01 - 0.0001),
         call.put("imageMode", "Multiple"),
         call.put("numImages", 3),
         call.put("acquirePeriod", 0.1 - 0.0001),
     ]
     assert not self.o.is_hardware_triggered
 def setUp(self):
     super().setUp()
     # In this case we only move x and expect energy to be moved in between runs
     outer = LineGenerator("energy", "keV", 10.0, 11.0, 5)
     self.steps_to_do = 10
     inner = LineGenerator("x", "mm", 0.0, 0.5, self.steps_to_do)
     self.generator = CompoundGenerator([outer, inner], [], [], 0.1)
     self.generator.prepare()
     self.completed_steps = 0
     self.info = ExposureDeadtimeInfo(0.01, 1000, 0.0)
     self.part_info = dict(anyname=[self.info])
    def test_post_run_armed_with_hardware_trigger_and_breakpoints(self):
        xs = LineGenerator("x", "mm", 0.0, 0.5, 100, alternate=True)
        ys = LineGenerator("y", "mm", 0.0, 0.1, 5)
        generator = CompoundGenerator([ys, xs], [], [], 0.1)
        generator.prepare()
        info = ExposureDeadtimeInfo(0.01, 1000, 0.0)
        part_info = dict(anyname=[info])
        breakpoints = [100, 400]

        # This would have been done by initial configure
        self.o.is_hardware_triggered = True
        self.o.done_when_reaches = 100

        self.o.on_post_run_armed(self.context, 100, 400, part_info, generator,
                                 breakpoints)
        assert self.child.handled_requests.mock_calls == [
            call.put("arrayCallbacks", True),
            call.put("arrayCounter", 100),
            call.put("imageMode", "Multiple"),
            call.put("numImages", 400),
            call.put("acquirePeriod", 0.1 - 0.0001),
            call.post("start"),
            call.when_value_matches("acquiring", True, None),
        ]
        assert self.o.done_when_reaches == 500
    def test_seek_with_hardware_trigger_and_breakpoints(self):
        self.o.is_hardware_triggered = True
        # Calling seek after 10 completed steps with 10 left until a breakpoint
        self.o.done_when_reaches = 10

        # Build our seek parameters
        xs = LineGenerator("x", "mm", 0.0, 0.5, 20, alternate=True)
        ys = LineGenerator("y", "mm", 0.0, 0.1, 3)
        generator = CompoundGenerator([ys, xs], [], [], 0.1)
        generator.prepare()
        completed_steps = 10
        steps_to_do = 10
        info = ExposureDeadtimeInfo(0.01, 1000, 0.0)
        part_info = dict(anyname=[info])
        breakpoints = [20, 20, 20]

        self.o.on_seek(
            self.context,
            completed_steps,
            steps_to_do,
            part_info,
            generator,
            fileDir="/tmp",
            breakpoints=breakpoints,
            exposure=info.calculate_exposure(generator.duration),
        )

        # Check we got the right calls to setup the driver
        assert self.child.handled_requests.mock_calls == [
            call.put("arrayCallbacks", True),
            call.put("arrayCounter", 10),
            call.put("exposure", 0.1 - 0.01 - 0.0001),
            call.put("imageMode", "Multiple"),
            call.put("numImages", 10),
            call.put("acquirePeriod", 0.1 - 0.0001),
            call.post("start"),
            call.when_value_matches("acquiring", True, None),
        ]
        assert self.o.done_when_reaches == 20
        assert len(self.context._subscriptions) == 0
class TestDetectorDriverPartNestedConfigure(TestDetectorDriverPart):
    def setUp(self):
        super().setUp()
        # In this case we only move x and expect energy to be moved in between runs
        outer = LineGenerator("energy", "keV", 10.0, 11.0, 5)
        self.steps_to_do = 10
        inner = LineGenerator("x", "mm", 0.0, 0.5, self.steps_to_do)
        self.generator = CompoundGenerator([outer, inner], [], [], 0.1)
        self.generator.prepare()
        self.completed_steps = 0
        self.info = ExposureDeadtimeInfo(0.01, 1000, 0.0)
        self.part_info = dict(anyname=[self.info])

    def configure(self):
        self.o.on_configure(
            self.context,
            self.completed_steps,
            self.steps_to_do,
            self.part_info,
            self.generator,
            fileDir="/tmp",
            exposure=self.info.calculate_exposure(self.generator.duration),
        )

    def test_nested_configure(self):
        self.set_attributes(self.child, triggerMode="Internal")
        self.configure()
        # When not hardware triggered we set numImages for the inner scan only
        assert self.child.handled_requests.mock_calls == [
            call.put("arrayCallbacks", True),
            call.put("arrayCounter", 0),
            call.put("exposure", 0.1 - 0.01 - 0.0001),
            call.put("imageMode", "Multiple"),
            call.put("numImages", 10),
            call.put("acquirePeriod", 0.1 - 0.0001),
        ]

    def test_nested_hardware_triggered_configure(self):
        self.set_attributes(self.child, triggerMode="External")
        self.configure()
        # When hardware triggered we set numImages to the total frames and call start
        assert self.child.handled_requests.mock_calls == [
            call.put("arrayCallbacks", True),
            call.put("arrayCounter", 0),
            call.put("exposure", 0.1 - 0.01 - 0.0001),
            call.put("imageMode", "Multiple"),
            call.put("numImages", 50),
            call.put("acquirePeriod", 0.1 - 0.0001),
            call.post("start"),
            call.when_value_matches("acquiring", True, None),
        ]
    def test_post_run_armed_with_hardware_trigger(self):
        xs = LineGenerator("x", "mm", 0.0, 0.5, 100, alternate=True)
        ys = LineGenerator("y", "mm", 0.0, 0.1, 5)
        generator = CompoundGenerator([ys, xs], [], [], 0.1)
        generator.prepare()
        info = ExposureDeadtimeInfo(0.01, 1000, 0.0)
        part_info = dict(anyname=[info])

        # This would have been done by initial configure
        self.o.is_hardware_triggered = True
        self.o.done_when_reaches = 100

        self.o.on_post_run_armed(self.context, 100, 100, part_info, generator)

        assert self.o.done_when_reaches == 200
 def setup_detector(
     self,
     context: Context,
     completed_steps: scanning.hooks.ACompletedSteps,
     steps_to_do: scanning.hooks.AStepsToDo,
     num_images: int,
     duration: float,
     part_info: scanning.hooks.APartInfo,
     initial_configure: bool = True,
     **kwargs: Any,
 ) -> None:
     child = context.block_view(self.mri)
     if initial_configure:
         # This is an initial configure, so reset arrayCounter to 0
         array_counter = 0
         self.done_when_reaches = steps_to_do
     else:
         # This is rewinding or setting up for another batch,
         # skip to a uniqueID that has not been produced yet
         array_counter = self.done_when_reaches
         self.done_when_reaches += steps_to_do
     self.uniqueid_offset = completed_steps - array_counter
     for k, v in dict(
             arrayCounter=array_counter,
             imageMode=self.multiple_image_mode,
             numImages=num_images,
             arrayCallbacks=True,
     ).items():
         if k not in kwargs and k in child:
             kwargs[k] = v
     child.put_attribute_values(kwargs)
     # Might need to reset acquirePeriod as it's sometimes wrong
     # in some detectors
     try:
         info: ExposureDeadtimeInfo = ExposureDeadtimeInfo.filter_single_value(
             part_info)
     except BadValueError:
         # This is ok, no exposure info
         pass
     else:
         exposure = kwargs.get("exposure",
                               info.calculate_exposure(duration))
         child.acquirePeriod.put_value(exposure + info.readout_time)