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
Beispiel #2
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
Beispiel #3
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
 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
Beispiel #5
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
 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)
    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