Пример #1
0
    def on_validate(
        self,
        generator: scanning.hooks.AGenerator,
        part_info: scanning.hooks.APartInfo,
        detectors: ADetectorTable,
    ) -> Optional[scanning.hooks.UParameterTweakInfos]:
        # Check the primary generator is static
        self._check_generator_is_static(generator.generators[0])

        # Calculate the time that should be spent at each position
        (
            time_at_diffraction_position,
            time_at_imaging_position,
        ) = self._get_time_at_positions(part_info, detectors)
        # Now calculate how long one cycle should take
        cycle_duration = self._calculate_cycle_duration(
            time_at_diffraction_position, time_at_imaging_position)
        # See if we need to tweak the generator
        if generator.duration != cycle_duration:
            # Return the generator with our cycle duration
            self.log.debug(
                f"{self.name}: tweaking generator duration from {generator.duration} "
                f"to {cycle_duration}")
            serialized = generator.to_dict()
            new_generator = CompoundGenerator.from_dict(serialized)
            new_generator.duration = cycle_duration
            return scanning.infos.ParameterTweakInfo("generator",
                                                     new_generator)
        else:
            return None
Пример #2
0
 def calculate_generator_duration(
     self,
     context: scanning.hooks.AContext,
     generator: scanning.hooks.AGenerator,
     part_info: scanning.hooks.APartInfo,
     motion_axes: List[str],
 ) -> Optional[float]:
     """
     Calculate a generator duration based on the generator and axes we are moving
     """
     child = context.block_view(self.mri)
     layout_table = child.layout.value
     # Check if we are moving axes in this scan
     if motion_axes:
         # TODO: what happens if axes are not mapped due to the current config on
         # the brick not matching the target config for the scan?
         try:
             axis_mapping = cs_axis_mapping(context, layout_table,
                                            motion_axes)
         except AssertionError:
             # We can't check the axes as they are not mapped, so don't tweak the
             # generator and just return
             self.log.debug(
                 f"{self.name}: can't guess generator duration during "
                 f"validate as axis mappings are not loaded or missing.")
             return None
         # Step scans and fly scans behave differently
         generator.prepare()
         if generator.continuous and generator.size > 1:
             # Estimate the duration for fly scans using the distance between the
             # first two points and max velocities of participating axes
             first_point = generator.get_point(0)
             second_point = generator.get_point(1)
             return self.calculate_duration_from_first_two_points(
                 axis_mapping,
                 first_point,
                 second_point,
             )
         else:
             # Step scans have turnarounds at each point so can use this value
             min_turnaround = get_min_turnaround(part_info)
             return min_turnaround.time
     else:
         # Not moving axes so just return time of one tick
         return TICK_S
Пример #3
0
    def on_validate(
        self,
        context: scanning.hooks.AContext,
        generator: scanning.hooks.AGenerator,
        axesToMove: scanning.hooks.AAxesToMove,
        part_info: scanning.hooks.APartInfo,
    ) -> scanning.hooks.UParameterTweakInfos:
        child = context.block_view(self.mri)
        # Check that we can move all the requested axes
        available = set(child.layout.value.name)
        motion_axes = get_motion_axes(generator, axesToMove)
        assert available.issuperset(
            motion_axes
        ), "Some of the requested axes %s are not on the motor list %s" % (
            list(axesToMove),
            sorted(available),
        )
        # Find the duration
        duration = generator.duration
        assert duration >= 0.0, f"{self.name}: negative duration is not supported"

        # Check if we should guess the duration
        if duration == 0.0:
            # We need to tweak the duration if we are going to take part
            if self.taking_part_in_scan(part_info, motion_axes):
                duration = self.calculate_generator_duration(
                    context, generator, part_info, motion_axes)
                # We may have not been able to tweak duration if axis mappings are
                # missing.
                if not duration:
                    return None
            else:
                return None

        # If GPIO is demanded for every point we need to align to the servo
        # cycle
        trigger = get_motion_trigger(part_info)
        if trigger == scanning.infos.MotionTrigger.EVERY_POINT:
            servo_freq = child.servoFrequency()
            duration = self.get_aligned_duration_with_servo_frequency(
                servo_freq, duration)

        # Check if the duration was tweaked and return
        if duration != generator.duration:
            self.log.debug(
                f"{self.name}: tweaking duration from {generator.duration} to "
                f"{duration}")
            serialized = generator.to_dict()
            new_generator = CompoundGenerator.from_dict(serialized)
            new_generator.duration = duration
            return scanning.infos.ParameterTweakInfo("generator",
                                                     new_generator)
        else:
            return None
Пример #4
0
 def on_validate(
     self, generator: scanning.hooks.AGenerator
 ) -> scanning.hooks.UParameterTweakInfos:
     duration = generator.duration
     if duration == 0.0:
         # We need to tweak the duration
         serialized = generator.to_dict()
         new_generator = CompoundGenerator.from_dict(serialized)
         # Set the duration to 2 clock cycles
         new_generator.duration = 2 * TICK
         return scanning.infos.ParameterTweakInfo("generator",
                                                  new_generator)
     else:
         assert (
             duration > 0
         ), f"Generator duration of {duration} must be > 0 to signify fixed exposure"
         return None
Пример #5
0
 def on_validate(
     self, generator: scanning.hooks.AGenerator
 ) -> scanning.hooks.UParameterTweakInfos:
     duration = generator.duration
     if duration == 0.0:
         # Set the duration for 2 samples (1 live 1 dead)
         serialized = generator.to_dict()
         new_generator = CompoundGenerator.from_dict(serialized)
         new_generator.duration = 2 / self.sample_freq
         return scanning.infos.ParameterTweakInfo("generator",
                                                  new_generator)
     else:
         assert (
             duration > 0
         ), f"Generator duration of {duration} must be > 0 to signify fixed exposure"
         assert (self._number_of_adc_samples(duration) > 0
                 ), f"Generator duration of {duration} gives < 1 ADC sample"
         return None
Пример #6
0
 def on_validate(
     self,
     context: scanning.hooks.AContext,
     generator: scanning.hooks.AGenerator,
     axesToMove: scanning.hooks.AAxesToMove,
     part_info: scanning.hooks.APartInfo,
 ) -> scanning.hooks.UParameterTweakInfos:
     child = context.block_view(self.mri)
     # Check that we can move all the requested axes
     available = set(child.layout.value.name)
     motion_axes = get_motion_axes(generator, axesToMove)
     assert available.issuperset(
         motion_axes
     ), "Some of the requested axes %s are not on the motor list %s" % (
         list(axesToMove),
         sorted(available),
     )
     # If GPIO not demanded for every point we don't need to align to the
     # servo cycle
     trigger = get_motion_trigger(part_info)
     if trigger != scanning.infos.MotionTrigger.EVERY_POINT:
         return None
     # Find the duration
     assert generator.duration > 0, "Can only do fixed duration at the moment"
     servo_freq = child.servoFrequency()
     # convert half an exposure to multiple of servo ticks, rounding down
     ticks = np.floor(servo_freq * 0.5 * generator.duration)
     if not np.isclose(servo_freq, 3200):
         # + 0.002 for some observed jitter in the servo frequency if I10
         # isn't a whole number of 1/4 us move timer ticks
         # (any frequency apart from 3.2 kHz)
         ticks += 0.002
     # convert to integer number of microseconds, rounding up
     micros = np.ceil(ticks / servo_freq * 1e6)
     # back to duration
     duration = 2 * float(micros) / 1e6
     if duration != generator.duration:
         serialized = generator.to_dict()
         new_generator = CompoundGenerator.from_dict(serialized)
         new_generator.duration = duration
         return scanning.infos.ParameterTweakInfo("generator",
                                                  new_generator)
     else:
         return None
Пример #7
0
 def on_validate(
     self,
     context: scanning.hooks.AContext,
     generator: scanning.hooks.AGenerator,
     exposure: scanning.hooks.AExposure = 0.0,
     frames_per_step: AFramesPerStep = 1,
 ) -> scanning.hooks.UParameterTweakInfos:
     # Get the duration per frame
     duration = generator.duration
     assert (
         duration >= 0
     ), f"Generator duration of {duration} must be >= 0 to signify fixed exposure"
     # As the Andor runnable block does not have an ExposureDeadTimePart, we handle
     # the case where we have been given an exposure time here
     if exposure > 0:
         # Grab the current value of the readout time and hope that the parameters
         # will not change before we actually run configure...
         child = context.block_view(self.mri)
         driver_readout_time = child.andorReadoutTime.value
         # Add the exposure time and multiply up to get the total generator duration
         duration_per_frame = exposure + driver_readout_time
         # Check if we need to guess the duration
         if duration == 0.0:
             serialized = generator.to_dict()
             new_generator = CompoundGenerator.from_dict(serialized)
             # Multiply the duration per frame up
             duration_per_point = duration_per_frame * frames_per_step
             new_generator.duration = duration_per_frame * frames_per_step
             self.log.debug(
                 f"{self.name}: tweaking generator duration from "
                 f"{generator.duration} to {duration_per_point}")
             return scanning.hooks.ParameterTweakInfo(
                 "generator", new_generator)
         # Otherwise we just want to check if we can achieve the exposure expected
         else:
             assert duration_per_frame <= duration, (
                 f"{self.name}: cannot achieve exposure of {exposure} with per frame"
                 f" duration of {duration}")
             return None
     # Otherwise just let the DetectorDriverPart validate for us
     else:
         return super().on_validate(context,
                                    generator,
                                    frames_per_step=frames_per_step)
Пример #8
0
 def on_configure(
     self,
     context: scanning.hooks.AContext,
     completed_steps: scanning.hooks.ACompletedSteps,
     steps_to_do: scanning.hooks.AStepsToDo,
     # The following were passed from user calling configure()
     generator: scanning.hooks.AGenerator,
     axesToMove: scanning.hooks.AAxesToMove,
     exceptionStep: AExceptionStep = 0,
 ) -> None:
     child = context.block_view(self.mri)
     # Store the generator and place we need to start
     self._generator = generator
     self._completed_steps = completed_steps
     self._steps_to_do = steps_to_do
     self._exception_step = exceptionStep
     self._axes_to_move = axesToMove
     self._movers = {axis: MaybeMover(child, axis) for axis in axesToMove}
     # Move to start (instantly)
     first_point = generator.get_point(completed_steps)
     fs: List[Future] = []
     for axis, mover in self._movers.items():
         mover.maybe_move_async(fs, first_point.lower[axis])
     context.wait_all_futures(fs)
Пример #9
0
    def on_validate(
        self,
        context: scanning.hooks.AContext,
        generator: scanning.hooks.AGenerator,
        frames_per_step: ADetectorFramesPerStep = 1,
    ) -> scanning.hooks.UParameterTweakInfos:
        # Check if we have a minimum acquire period
        if self.required_version is not None:
            child = context.block_view(self.mri)
            check_driver_version(child.driverVersion.value,
                                 self.required_version)

        if self.min_acquire_period > 0.0:
            duration = generator.duration
            # Check if we need to guess the generator duration
            if duration == 0.0:
                # Use the minimum acquire period as an estimate of readout time. We
                # also need to multiple by frames_per_step as the DetectorChildPart
                # divides the generator down to the duration for a single detector
                # frame.
                duration = self.min_acquire_period * frames_per_step
                serialized = generator.to_dict()
                new_generator = CompoundGenerator.from_dict(serialized)
                new_generator.duration = duration
                self.log.debug(
                    f"{self.name}: tweaking generator duration from "
                    f"{generator.duration} to {duration}")
                return scanning.hooks.ParameterTweakInfo(
                    "generator", new_generator)
            # Otherwise check the provided duration is long enough
            else:
                assert generator.duration >= self.min_acquire_period, (
                    f"Duration {generator.duration} per frame is less than minimum "
                    f"acquire period {self.min_acquire_period}s")
                return None
        return None
Пример #10
0
    def on_configure(
        self,
        context: scanning.hooks.AContext,
        completed_steps: scanning.hooks.ACompletedSteps,
        steps_to_do: scanning.hooks.AStepsToDo,
        part_info: scanning.hooks.APartInfo,
        generator: scanning.hooks.AGenerator,
        axesToMove: scanning.hooks.AAxesToMove,
    ) -> None:

        # Double the number of cycles to get rotations
        static_axis = generator.generators[0]
        assert isinstance(static_axis, StaticPointGenerator
                          ), "Static Point Generator not configured correctly"
        static_axis = StaticPointGenerator(size=static_axis.size * 2)
        steps_to_do *= 2

        # Create a linear scan axis (proper rotation)
        selector_axis = LineGenerator(self.selectorAxis,
                                      "deg",
                                      self.tomoAngle,
                                      self.diffAngle,
                                      1,
                                      alternate=True)
        axesToMove = [self.selectorAxis]

        def get_minturnaround():
            # See if there is a minimum turnaround
            infos = scanning.infos.MinTurnaroundInfo.filter_values(part_info)
            if infos:
                assert (
                    len(infos) == 1
                ), "Expected 0 or 1 MinTurnaroundInfos, got %d" % len(infos)
                min_turnaround = max(MIN_TIME, infos[0].gap)
                min_interval = infos[0].interval
            else:
                min_turnaround = MIN_TIME
                min_interval = MIN_INTERVAL

            return min_turnaround, min_interval

        # Calculate the exposure time
        min_turnaround = get_minturnaround()[0]
        cycle_duration = generator.duration
        exposure_time = cycle_duration / 2 - self.move_time
        if exposure_time < min_turnaround:
            exposure_time = min_turnaround

        new_generator = CompoundGenerator(
            [static_axis, selector_axis],
            [],
            [],
            duration=self.move_time,
            continuous=True,
            delay_after=exposure_time,
        )
        new_generator.prepare()

        # Reduce the exposure of the camera/detector
        generator.duration = exposure_time

        super().on_configure(context, completed_steps, steps_to_do, part_info,
                             new_generator, axesToMove)