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
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
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
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