From 19b57642b50b2b912add6aa1014e5058f018af62 Mon Sep 17 00:00:00 2001 From: gac-x05la Date: Tue, 12 Nov 2024 12:26:16 +0100 Subject: [PATCH] External enable works, trigger input seems dead --- .../devices/gigafrost/gigafrostcamera.py | 31 ++- tomcat_bec/scans/tomcat_scans.py | 216 +++++++++--------- 2 files changed, 139 insertions(+), 108 deletions(-) diff --git a/tomcat_bec/devices/gigafrost/gigafrostcamera.py b/tomcat_bec/devices/gigafrost/gigafrostcamera.py index d90f16b..9c81f46 100644 --- a/tomcat_bec/devices/gigafrost/gigafrostcamera.py +++ b/tomcat_bec/devices/gigafrost/gigafrostcamera.py @@ -545,14 +545,29 @@ class GigaFrostCamera(PSIDetectorBase): self.cfgCntNum.set(num_images).wait() self.cfgCorrMode.set(correction_mode).wait() - # if trigger_mode is not None: - # self.set_trigger_mode(str(trigger_mode)) + if trigger_mode is not None: + self.set_trigger_mode(str(trigger_mode)) # Commit parameter self.cmdSetParam.set(1).wait() def set_trigger_mode(self, trigger_mode): - if trigger_mode == "soft": + """ + + NOTE: The trigger input appears to be dead, it completely ignores the + supplied signal. Use external enable instead, that works! + """ + + if trigger_mode == "default": + # trigger modes + self.cfgCntStartBit.set(1).wait() + self.cfgCntEndBit.set(0).wait() + + # set modes + self.enable_mode = "soft" + self.trigger_mode = "auto" + self.exposure_mode = "timer" + elif trigger_mode == "soft": # Switch to physical enable signal self.cfgEnableScheme.set(0).wait() # Set enable signal to always @@ -590,6 +605,16 @@ class GigaFrostCamera(PSIDetectorBase): # Set trigger edge to fixed frames on posedge self.cfgCntStartBit.set(1).wait() self.cfgCntEndBit.set(0).wait() + elif trigger_mode in ["ext_enable", "external_enable"]: + # Switch to physical enable signal + self.cfgEnableScheme.set(0).wait() + # Trigger modes + self.cfgCntStartBit.set(1).wait() + self.cfgCntEndBit.set(0).wait() + # Set modes + self.enable_mode = "external" + self.trigger_mode = "auto" + self.exposure_mode = "timer" else: raise RuntimeError(f"Unsupported trigger mode: {trigger_mode}") diff --git a/tomcat_bec/scans/tomcat_scans.py b/tomcat_bec/scans/tomcat_scans.py index 5ad5fe1..f60ce2e 100644 --- a/tomcat_bec/scans/tomcat_scans.py +++ b/tomcat_bec/scans/tomcat_scans.py @@ -2,7 +2,12 @@ """ Tomcat scan base class examples A collection of example scan base classes using Automation1 rotation stage, -GigaFrost camera and the StandardDAQ pipeline. +GigaFrost camera and the StandardDAQ pipeline. + +NOTE: As an explicit request from Tomcat, all devices must be freely +interchangeable, including the simultaneous use of multiple devices. Therefore +the devices were prepared in such a way, that the scans need to address +devices. Created on Mon Sep 16 16:45:11 2024 @@ -18,23 +23,23 @@ from bec_server.scan_server.scans import AsyncFlyScanBase, ScanBase logger = bec_logger.logger - class TomcatStepScan(ScanBase): """Simple software step scan forTomcat - Example class for simple BEC-based step scans using the low-level API. All it does is - translate conventional kwargs to tomcat specific naming scheme. + Example class for simple BEC-based step scan using the low-level API. + All it does is translate conventional kwargs to tomcat specific device + pareameters and launches the standard step scan. Example ------- - >>> scans.gigastep(scan_start=-25, scan_end=155, steps=180, exp_time=0.005, exp_burst=5) + >>> scans.tomcatstepscan(scan_start=-25, scan_end=155, steps=180, exp_time=0.005, exp_burst=5) """ scan_name = "tomcatstepscan" required_kwargs = ["scan_start", "scan_end", "steps"] gui_config = { "Movement parameters": ["steps"], - "Acquisition parameters": ["exp_time", "burst_at_each_point", "roix", "roiy"], + "Acquisition parameters": ["exp_time", "exp_burst", "roix", "roiy"], } def _get_scan_motors(self): @@ -47,7 +52,7 @@ class TomcatStepScan(ScanBase): steps: int, exp_time=0.005, settling_time=0.2, - burst_at_each_point=1, + exp_burst=1, roix=2016, roiy=2016, sync="event", @@ -55,18 +60,18 @@ class TomcatStepScan(ScanBase): ): # Converting generic kwargs to tomcat device configuration parameters # Used by gigafrost - kwargs['parameter']['kwargs']["exposure_time_ms"] = 1000 * exp_time - kwargs['parameter']['kwargs']["exposure_period_ms"] = 2 * 1000 * exp_time - kwargs['parameter']['kwargs']["exposure_num_burst"] = burst_at_each_point - kwargs['parameter']['kwargs']["image_width"] = roix - kwargs['parameter']['kwargs']["image_height"] = roiy + kwargs["parameter"]["kwargs"]["exposure_time_ms"] = 1000 * exp_time + kwargs["parameter"]["kwargs"]["exposure_period_ms"] = 2 * 1000 * exp_time + kwargs["parameter"]["kwargs"]["exposure_num_burst"] = exp_burst + kwargs["parameter"]["kwargs"]["image_width"] = roix + kwargs["parameter"]["kwargs"]["image_height"] = roiy # Used by stdDAQ and DDC - kwargs['parameter']['kwargs']["num_points_total"] = burst_at_each_point * (steps + 1) + kwargs["parameter"]["kwargs"]["num_points_total"] = exp_burst * (steps + 1) t_modes = {"pso": 0, "event": 1, "inp0": 2, "inp1": 4} ddc_trigger = t_modes[sync] - kwargs['parameter']['kwargs']["ddc_trigger"] = ddc_trigger + kwargs["parameter"]["kwargs"]["ddc_trigger"] = ddc_trigger # Use PSO trigger - kwargs['parameter']['kwargs']["pso_wavemode"] = "pulsed" + kwargs["parameter"]["kwargs"]["pso_wavemode"] = "pulsed" super().__init__( exp_time=exp_time, @@ -84,21 +89,17 @@ class TomcatStepScan(ScanBase): self.scan_steps = steps self.scan_stepsize = (scan_end - scan_start) / steps - def _calculate_positions(self) -> None: """Pre-calculate scan positions""" for ii in range(self.scan_steps + 1): self.positions.append(self.scan_start + ii * self.scan_stepsize) - - - class TomcatSnapNStep(AsyncFlyScanBase): """Simple software step scan forTomcat - Example class for simple BEC-based step scans using the low-level API. All it does is - translate conventional kwargs to tomcat specific naming scheme. + Example class for simple BEC-based step scans using the low-level API. All it does is + translate conventional kwargs to tomcat specific naming scheme. Example ------- @@ -109,7 +110,7 @@ class TomcatSnapNStep(AsyncFlyScanBase): required_kwargs = ["scan_start", "scan_end", "steps"] gui_config = { "Movement parameters": ["steps"], - "Acquisition parameters": ["exp_time", "burst_at_each_point", "roix", "roiy"], + "Acquisition parameters": ["exp_time", "exp_burst", "roix", "roiy"], } def _get_scan_motors(self): @@ -122,7 +123,7 @@ class TomcatSnapNStep(AsyncFlyScanBase): steps: int, exp_time=0.005, settling_time=0.2, - burst_at_each_point=1, + exp_burst=1, roix=2016, roiy=2016, sync="event", @@ -130,44 +131,35 @@ class TomcatSnapNStep(AsyncFlyScanBase): ): # Converting generic kwargs to tomcat device configuration parameters # Used by gigafrost - kwargs['parameter']['kwargs']["exposure_time_ms"] = 1000 * exp_time - kwargs['parameter']['kwargs']["exposure_period_ms"] = 2 * 1000 * exp_time - kwargs['parameter']['kwargs']["exposure_num_burst"] = burst_at_each_point - kwargs['parameter']['kwargs']["image_width"] = roix - kwargs['parameter']['kwargs']["image_height"] = roiy + kwargs["parameter"]["kwargs"]["exposure_time_ms"] = 1000 * exp_time + kwargs["parameter"]["kwargs"]["exposure_period_ms"] = 2 * 1000 * exp_time + kwargs["parameter"]["kwargs"]["exposure_num_burst"] = exp_burst + kwargs["parameter"]["kwargs"]["image_width"] = roix + kwargs["parameter"]["kwargs"]["image_height"] = roiy # Used by stdDAQ and DDC - kwargs['parameter']['kwargs']["num_points_total"] = burst_at_each_point * (steps + 1) + kwargs["parameter"]["kwargs"]["num_points_total"] = exp_burst * (steps + 1) t_modes = {"pso": 0, "event": 1, "inp0": 2, "inp1": 4} ddc_trigger = t_modes[sync] - kwargs['parameter']['kwargs']["ddc_trigger"] = ddc_trigger + kwargs["parameter"]["kwargs"]["ddc_trigger"] = ddc_trigger # Use PSO trigger (disable triggering) - kwargs['parameter']['kwargs']["pso_distance"] = 0 + kwargs["parameter"]["kwargs"]["pso_distance"] = 0 # For position calculation self.scan_start = scan_start self.scan_end = scan_end self.scan_steps = steps self.scan_stepsize = (scan_end - scan_start) / steps - self.scan_ntotal = burst_at_each_point * (steps + 1) + self.scan_ntotal = exp_burst * (steps + 1) + self.exp_burst = exp_burst + self.settling_time = settling_time + self.scan_sync = sync - # Aerotech DDC settings: Internal event trigger: PsoEvent = 1 - p_modes = {"pso": "PsoOutput", "event": "PsoEvent", "inp0": "HighSpeedInput0RisingEdge", "inp1": "HighSpeedInput1RisingEdge",} + # Used for Aeroscript file substitutions for the task interface filename = "AerotechSnapAndStepTemplate.ascript" - filesubs = { - "startpos": self.scan_start, - "stepsize": self.scan_stepsize, - "numsteps": self.scan_steps, - "exptime": 2 * exp_time * burst_at_each_point, - "settling": settling_time, - "psotrigger": p_modes[sync], - "npoints": self.scan_ntotal, - } + filesubs = self.get_filesubs() filetext = self.render_file(filename, filesubs) - - # Task inteface - kwargs['parameter']['kwargs']["script_text"] = filetext - kwargs['parameter']['kwargs']["script_file"] = "bec.ascript" - + kwargs["parameter"]["kwargs"]["script_text"] = filetext + kwargs["parameter"]["kwargs"]["script_file"] = "bec.ascript" super().__init__( exp_time=exp_time, @@ -178,13 +170,30 @@ class TomcatSnapNStep(AsyncFlyScanBase): **kwargs, ) + def get_filesubs(self) -> dict: + """Generate jinja template substitutions.""" + p_modes = { + "pso": "PsoOutput", + "event": "PsoEvent", + "inp0": "HighSpeedInput0RisingEdge", + "inp1": "HighSpeedInput1RisingEdge", + } + filesubs = { + "startpos": self.scan_start, + "stepsize": self.scan_stepsize, + "numsteps": self.scan_steps, + "exptime": 2 * self.exp_time * self.exp_burst, + "settling": self.settling_time, + "psotrigger": p_modes[self.scan_sync], + "npoints": self.scan_ntotal, + } + return filesubs + def render_file(self, filename, filesubs): """Prepare action: render AeroScript file""" # Load the test file if filename is not None: - filename = os.path.join( - os.path.dirname(__file__), "../devices/aerotech/" + filename - ) + filename = os.path.join(os.path.dirname(__file__), "../devices/aerotech/" + filename) logger.info(f"Attempting to load file {filename}") with open(filename) as f: templatetext = f.read() @@ -219,26 +228,21 @@ class TomcatSnapNStep(AsyncFlyScanBase): # positions = yield from self.stubs.send_rpc_and_wait(self.daqname, "collect") # logger.info(f"Finished scan with collected positions: {positions}") - def cleanup(self): """Set scan progress to 1 to finish the scan""" self.num_pos = 1 return super().cleanup() - - - - class TomcatSimpleSequence(AsyncFlyScanBase): """Simple software step scan forTomcat - Example class for simple BEC-based step scans using the low-level API. All it does is - translate conventional kwargs to tomcat specific naming scheme. + Example class for simple AeroScript-based step scan using the low-level API. All it does is + translate meaningful kwargs to device specific naming scheme. Example ------- - >>> scans.tomcatsimplesequencescan(33, 180, 180, exp_time=0.005, exp_frames=1800, repeats=10) + >>> scans.tomcatsimplesequencescan(33, 180, 180, exp_time=0.005, exp_burst=1800, repeats=10) """ scan_name = "tomcatsimplesequencescan" @@ -250,7 +254,7 @@ class TomcatSimpleSequence(AsyncFlyScanBase): "gate_high", "gate_low", "exp_time", - "exp_frames", + "exp_burst", "roix", "roiy", "sync", @@ -265,13 +269,13 @@ class TomcatSimpleSequence(AsyncFlyScanBase): scan_start: float, gate_high: float, gate_low: float, - repeats: int=1, - repmode: str="PosNeg", - exp_time: float=0.005, - exp_frames: float=180, - roix: int=2016, - roiy: int=2016, - sync: str="pso", + repeats: int = 1, + repmode: str = "PosNeg", + exp_time: float = 0.005, + exp_burst: float = 180, + roix: int = 2016, + roiy: int = 2016, + sync: str = "pso", **kwargs, ): # Auto-setup configuration parameters from input @@ -281,63 +285,47 @@ class TomcatSimpleSequence(AsyncFlyScanBase): self.scan_repnum = repeats self.scan_repmode = repmode.upper() self.exp_time = exp_time - self.exp_frames = exp_frames + self.exp_burst = exp_burst + self.scan_sync = sync # Synthetic values - self.scan_velocity = gate_high / (exp_time * exp_frames) + self.scan_ntotal = exp_burst * repeats + self.scan_velocity = gate_high / (exp_time * exp_burst) self.scan_acceleration = 500 self.scan_safedistance = 10 self.scan_accdistance = ( self.scan_safedistance + 0.5 * self.scan_velocity * self.scan_velocity / self.scan_acceleration ) - self.scan_ntotal = exp_frames * repeats if self.scan_repmode in ("POS", "NEG"): self.scan_range = repeats * (gate_high + gate_low) elif self.scan_repmode in ("POSNEG", "NEGPOS"): self.scan_range = gate_high + gate_low else: - raise RuntimeError(f"Unsupported repetition mode: {repmode}") - - + raise RuntimeError(f"Unsupported repetition mode: {self.scan_repmode}") # Converting generic kwargs to tomcat device configuration parameters # Used by gigafrost - kwargs['parameter']['kwargs']["exposure_time_ms"] = 1000 * exp_time - kwargs['parameter']['kwargs']["exposure_period_ms"] = 2 * 1000 * exp_time - kwargs['parameter']['kwargs']["exposure_num_burst"] = exp_frames - kwargs['parameter']['kwargs']["image_width"] = roix - kwargs['parameter']['kwargs']["image_height"] = roiy + kwargs["parameter"]["kwargs"]["exposure_time_ms"] = 1000 * exp_time + kwargs["parameter"]["kwargs"]["exposure_period_ms"] = 2 * 1000 * exp_time + kwargs["parameter"]["kwargs"]["exposure_num_burst"] = exp_burst + kwargs["parameter"]["kwargs"]["image_width"] = roix + kwargs["parameter"]["kwargs"]["image_height"] = roiy # Used by stdDAQ and DDC - kwargs['parameter']['kwargs']["num_points_total"] = exp_frames * (self.scan_repnum + 1) + kwargs["parameter"]["kwargs"]["num_points_total"] = exp_burst * (self.scan_repnum + 1) t_modes = {"pso": 0, "event": 1, "inp0": 2, "inp1": 4} ddc_trigger = t_modes[sync] - kwargs['parameter']['kwargs']["ddc_trigger"] = ddc_trigger + kwargs["parameter"]["kwargs"]["ddc_trigger"] = ddc_trigger # Use PSO trigger (disable triggering) - kwargs['parameter']['kwargs']["pso_distance"] = 0 + kwargs["parameter"]["kwargs"]["pso_distance"] = 0 - # Aerotech DDC settings: Internal event trigger: PsoEvent = 1 - p_modes = {"pso": "PsoOutput", "event": "PsoEvent", "inp0": "HighSpeedInput0RisingEdge", "inp1": "HighSpeedInput1RisingEdge",} + # Used for Aeroscript file substitutions for the task interface filename = "AerotechSimpleSequenceTemplate.ascript" - filesubs = { - "startpos": self.scan_start, - "scanrange": self.scan_range, - "nrepeat": self.scan_repnum, - "scanvel": self.scan_velocity, - "scanacc": self.scan_acceleration, - "accdist": self.scan_accdistance, - "npoints": self.scan_ntotal, - "scandir": self.scan_repmode.upper(), - "psotrigger": p_modes[sync], - } - filesubs = self.get_positions(filesubs) + filesubs = self.get_filesubs() filetext = self.render_file(filename, filesubs) - - # Task inteface - kwargs['parameter']['kwargs']["script_text"] = filetext - kwargs['parameter']['kwargs']["script_file"] = "bec.ascript" - + kwargs["parameter"]["kwargs"]["script_text"] = filetext + kwargs["parameter"]["kwargs"]["script_file"] = "bec.ascript" super().__init__( exp_time=exp_time, @@ -352,9 +340,7 @@ class TomcatSimpleSequence(AsyncFlyScanBase): """Prepare action: render AeroScript file""" # Load the test file if filename is not None: - filename = os.path.join( - os.path.dirname(__file__), "../devices/aerotech/" + filename - ) + filename = os.path.join(os.path.dirname(__file__), "../devices/aerotech/" + filename) logger.info(f"Attempting to load file {filename}") with open(filename) as f: templatetext = f.read() @@ -389,7 +375,27 @@ class TomcatSimpleSequence(AsyncFlyScanBase): # positions = yield from self.stubs.send_rpc_and_wait(self.daqname, "collect") # logger.info(f"Finished scan with collected positions: {positions}") - def get_positions(self, filesubs: dict={}): + def get_filesubs(self): + """Generate jinja template substitutions.""" + # Aerotech DDC settings: Internal event trigger: PsoEvent = 1 + p_modes = { + "pso": "PsoOutput", + "event": "PsoEvent", + "inp0": "HighSpeedInput0RisingEdge", + "inp1": "HighSpeedInput1RisingEdge", + } + filesubs = { + "startpos": self.scan_start, + "scanrange": self.scan_range, + "nrepeat": self.scan_repnum, + "scanvel": self.scan_velocity, + "scanacc": self.scan_acceleration, + "accdist": self.scan_accdistance, + "npoints": self.scan_ntotal, + "scandir": self.scan_repmode, + "psotrigger": p_modes[self.scan_sync], + } + # Fill PSO vectors according to scan direction # NOTE: Distance counter is bidirectional pso_bounds_pos = [] @@ -412,4 +418,4 @@ class TomcatSimpleSequence(AsyncFlyScanBase): def cleanup(self): """Set scan progress to 1 to finish the scan""" self.num_pos = 1 - return super().cleanup() \ No newline at end of file + return super().cleanup()