diff --git a/tomcat_bec/Readme.md b/tomcat_bec/Readme.md index 0520800..38e93f7 100644 --- a/tomcat_bec/Readme.md +++ b/tomcat_bec/Readme.md @@ -139,6 +139,8 @@ beamline console running the contros stack by: ``` caqtdm -macro "CAM=X02DA-CAM-GF2" X_X02DA_GIGAFROST_camControl_user.ui caqtdm -macro "P=X02DA-ES1-SMP1" aeroauto1_ControllerOverview.ui +# PCO Edge camera panel +caqtdm -macro "NAME=X02DA-CCDCAM2" CameraExpert.ui ``` The std-DAQ doesn't have control panels, but the BEC can display the hard-coded preview streams. @@ -154,57 +156,74 @@ gui.panels['tomolive'].add_widget('BECFigure').image('daq_stream0') The concept of scans is slightly different in bluesky and BEC. Bluesky strictly separates the device logic in ophyd from the scan logic in plans. Devices are passed in as arguments -allowing finetuning their roles. +i.e. their behavior is fully managed by the called scan plan in a single layer. On the other hand, BEC has two scan interfaces and additional device specific scan logic is -placed on the ophyd device itself. The general idea is that the lower-level BEC scans should -be completely generic with absolutely minimal logic (although some examples counter this -argument). BEC devices live on the DeviceServer, and automatically participate in scans -whenever they are enabled. This puts the configuration role either on the ophyd device from -the scainfo or in the high level interface that anyways manages the enabling/disabling of devices. +placed on the ophyd device itself (total of three levels). Theese are: + - Device self-configuration from the scaninfo on the ophyd device + - Low-level scan API on the ScanServer + - High-level scripting in the BEC console + +The general idea is that the lower-level scan API should be generic with minimal device specific +logic (although some examples counter this argument), so the same scan can be re-used across +multiple devices. Device specific scan behavior and configuration is defined either on the ophyd +device or in the high level interface. In practice, the former means that a device must fish for +relevant parameters in the 'scaninfo' message, that's passed around the beginning of each scan. + +Another major difference is that **ALL devices** live on the DeviceServer, and automatically +participate in scans whenever they are enabled. This adds the role of scope management to the +scripting interface, i.e. enabling/disabling of devices for the particular scan. The current repository has several implemented scans to test compliance with standard BEC interfaces and partly cover the old EPICS IOC functionality. - - If a device is enabled for a scan it will be staged - - I.e. by the time stage finishes it must be configured - - This can be either manual or from the scan parameters - - Some scan parameters will overwrite the configuration (can be removed...) - - Certain scan kwargs will trigger the reconfiguration of a device + - ALL enabled devices will participate in the scan + - If a device is enabled for a scan it will be staged (i.e. launch acquisition, script, etc...) + - Yes, you have to actively manage what's enabled and what's not + - Even a motor+diode scan must disable not relevant devices across the beamline + - Latest by the time stage finishes it must be configured + - Either manually by the preparation or scan routine + - Or from fishing it out from the scan parameters (generally by looking for specific kwargs) + - Some fished scan parameters might overwrite prior configuration - The device will not try to make sure that the received configuration is correct + - So tough luck if you're reusing the same parameters across different scans - parameters + ### anotherstepscan -Simple software step scan in the low-level API that almost calls the standard BEC scan routine. Its -only difference is that it overrides the burst to use Gigafrost's HW burst mode. The ophyd devices -are capable to allocate the required number of points for DAQ and position readback. +Simple software step scan in the low-level API that almost calls the standard BEC scan routine. +Its only difference is that it overrides the per-point behavior to use Gigafrost's HW burst mode +instead of sending multiple software triggers. The ophyd devices are capable to allocate the +required number of points for DAQ and position readback. ``` -def demosanotherstepscantepscan(scan_start, scan_end, steps, exp_time=0.005, burst_at_each_point=1, settling_time=0, sync='event') +demosanotherstepscantepscan(scan_start, scan_end, steps, exp_time=0.005, burst_at_each_point=1, settling_time=0, sync='event') ``` ### snapnstep AeroScript controlled high-speed step-scan that sets up the devices and calls the corresponding -low-level scan API to substitute the jinja2 template file and launch the script. PSO and data +low-level scan API to substitute the jinja2 template file and launch the script. The substitution +logic was placed in the low-level API, as it's very similar to other AeroScript scans. +The actual scan logic is in the **AerotechSnapAndStepTemplate.ascript** file. PSO and data readback are configured and launched internally. It also waits for the script to finish. ``` -anothersnapnstepscan(33, 180, 180, exp_time=0.005, exp_frames=1800, repeats=10) +anothersnapnstepscan(scan_start, scan_end, 180, exp_time=0.005, exp_burst=1800, repeats=10) ``` ### anothersequencescan AeroScript controlled fly scan that sets up the devices and calls the corresponding low-level scan -to substitute the jinja2 template file and launch the script. PSO and data readback are configured +to substitute the jinja2 template file and launch the script. The actual scan logic is in the +**AerotechSimpleSequenceTemplate.ascript** file. PSO and data readback are configured and launched internally. It also waits for the script to finish. It might have problems with configuring the readback of the number of repeats because that's done on the IOC, ``` -anothersequencescan(33, 180, 180, exp_time=0.005, exp_frames=1800, repeats=10) +anothersequencescan(scan_start, 180, 180, exp_time=0.005, exp_burst=1800, repeats=10) ``` diff --git a/tomcat_bec/devices/gigafrost/gigafrostcamera.py b/tomcat_bec/devices/gigafrost/gigafrostcamera.py index 3820df5..b60c603 100644 --- a/tomcat_bec/devices/gigafrost/gigafrostcamera.py +++ b/tomcat_bec/devices/gigafrost/gigafrostcamera.py @@ -166,18 +166,18 @@ class GigaFrostCameraMixin(CustomDetectorMixin): d = {} if 'kwargs' in scanparam: scanargs = scanparam['kwargs'] - if 'image_width' in scanargs: + if 'image_width' in scanargs and scanargs['image_width']!=None: d['image_width'] = scanargs['image_width'] - if 'image_height' in scanargs: + if 'image_height' in scanargs and scanargs['image_height']!=None: d['image_height'] = scanargs['image_height'] - if 'exp_time' in scanargs: - d['exposure_time_ms'] = 1000*scanargs['exp_time'] - if 'exp_burst' in scanargs: + if 'exp_time' in scanargs and scanargs['exp_time']!=None: + d['exposure_time_ms'] = scanargs['exp_time'] + if 'exp_burst' in scanargs and scanargs['exp_burst']!=None: d['exposure_num_burst'] = scanargs['exp_burst'] - if 'acq_mode' in scanargs: + if 'acq_mode' in scanargs and scanargs['acq_mode']!=None: d['acq_mode'] = scanargs['acq_mode'] - elif self.parent.scaninfo.scan_type == "step": - d['acq_mode'] = "default" + # elif self.parent.scaninfo.scan_type == "step": + # d['acq_mode'] = "default" # Perform bluesky-style configuration if len(d) > 0: @@ -533,7 +533,7 @@ class GigaFrostCamera(PSIDetectorBase): if d['image_height']%16 !=0: raise RuntimeError(f"[{self.name}] image_height must be divisible by 16") self.cfgRoiY.set(d['image_height']).wait() - # Dont change theese + # Dont change these scanid = d.get('scanid', 0) correction_mode = d.get('correction_mode', 5) self.cfgScanId.set(scanid).wait() @@ -561,11 +561,14 @@ class GigaFrostCamera(PSIDetectorBase): """ if acq_mode == "default": - # trigger modes + # Trigger parameters self.cfgCntStartBit.set(1).wait() self.cfgCntEndBit.set(0).wait() - # set modes + # Switch to physical enable signal + self.cfgEnableScheme.set(0).wait() + + # Set modes self.enable_mode = "soft" self.trigger_mode = "auto" self.exposure_mode = "timer" @@ -788,7 +791,7 @@ class GigaFrostCamera(PSIDetectorBase): @property def enable_mode(self): - """Return the enable mode set in the GigaFRost camera. + """Return the enable mode set in the GigaFRoST camera. Returns ------- @@ -811,15 +814,33 @@ class GigaFrostCamera(PSIDetectorBase): @enable_mode.setter def enable_mode(self, mode): - """Apply the enable mode for the GigaFRoST camera. + """ + Set the enable mode for the GigaFRoST camera. NOTE: Always does not seem to work and Enablesoft works like a trigger Parameters ---------- mode : {'soft', 'external', 'soft+ext', 'always'} - The enable mode to be applied. + The GigaFRoST enable mode. Valid arguments are: + + * 'soft': + The GigaFRoST enable signal is supplied through a software + signal + * 'external': + The GigaFRoST enable signal is supplied through an external TTL + gating signal from the rotaiton stage or some other control + unit + * 'soft+ext': + The GigaFRoST enable signal can be supplied either via the + software signal or externally. The two signals are combined + with a logical OR gate. + * 'always': + The GigaFRoST is always enabled. + CAUTION: This mode is not compatible with the fixed number of + frames modes! """ + if mode not in const.gf_valid_enable_modes: raise ValueError( "Invalid enable mode! Valid modes are:\n{const.gf_valid_enable_modes}" diff --git a/tomcat_bec/devices/gigafrost/readme.md b/tomcat_bec/devices/gigafrost/readme.md index f838e7a..b513672 100644 --- a/tomcat_bec/devices/gigafrost/readme.md +++ b/tomcat_bec/devices/gigafrost/readme.md @@ -24,3 +24,10 @@ The CaQtDM panel can be opened by: ''' caqtdm -macro "CAM=X02DA-CAM-GF2" X_X02DA_GIGAFROST_camControl_user.ui & ''' + +# Opening PCO Edge panel + +The CaQtDM panel can be opened by: +''' +caqtdm -macro "NAME=X02DA-CCDCAM2" CameraExpert.ui +''' diff --git a/tomcat_bec/devices/gigafrost/stddaq_client.py b/tomcat_bec/devices/gigafrost/stddaq_client.py index 2a5834d..432d0ab 100644 --- a/tomcat_bec/devices/gigafrost/stddaq_client.py +++ b/tomcat_bec/devices/gigafrost/stddaq_client.py @@ -40,19 +40,20 @@ class StdDaqMixin(CustomDeviceMixin): d = {} if 'kwargs' in scanparam: scanargs = scanparam['kwargs'] - if 'image_width' in scanargs: + if 'image_width' in scanargs and scanargs['image_width'] != None: d['image_width'] = scanargs['image_width'] - if 'image_height' in scanargs: + if 'image_height' in scanargs and scanargs['image_height'] != None: d['image_height'] = scanargs['image_height'] # NOTE: Scans don't have to fully configure the device - if "steps" in scanargs and "exp_burst" in scanargs: - scan_steps = scanargs["steps"] - scan_burst = scanargs["exp_burst"] - d["num_points_total"] = (scan_steps+1) * scan_burst - elif "exp_burst" in scanargs: - d["num_points_total"] = scanargs["exp_burst"] - elif "steps" in scanargs: - d["num_points_total"] = scanargs["steps"] + points_total = 1 + if 'steps' in scanargs and scanargs['steps'] != None: + points_total *= scanargs['steps'] + if 'exp_burst' in scanargs and scanargs['exp_burst'] != None: + points_total *= scanargs['exp_burst'] + if 'repeats' in scanargs and scanargs['repeats']!= None: + points_total *= scanargs['repeats'] + if points_total != 1: + d['num_points_total'] = points_total # Perform bluesky-style configuration if len(d) > 0: diff --git a/tomcat_bec/scripts/scans_fede.py b/tomcat_bec/scripts/scans_fede.py index 6c9f127..373ab99 100644 --- a/tomcat_bec/scripts/scans_fede.py +++ b/tomcat_bec/scripts/scans_fede.py @@ -1,29 +1,41 @@ -def fede_darks(num, exp_time=None, exp_period=None, roix=None, roiy=None): - """ Fede dark scan +def fede_darks(nimages_dark, exposure_time=None, exposure_period=None, roix=None, roiy=None, acq_mode=None): + """ + Acquire a set of dark images with shutters closed. - This is a small BEC user-space scan that sets up the gigafrost and calls - the low-level dark collection scan. + Parameters + ---------- + nimages_dark : int + Number of dark images to acquire (no default) + exposure_time : float, optional + Exposure time [ms]. If not specified, the currently configured value on the camera will be used + exposure_period : float, optional + Exposure period [ms] + roix : int, optional + ROI size in the x-direction [pixels] + roiy : int, optional + ROI size in the y-direction [pixels] + acq_mode : str, optional + Predefined acquisition mode (default=) + + Example: -------- tutorialdarks(num=100, exp_time=5) """ - ###### TODO check how to pass only the required arguments - ###### TODO what we want is a function common to PCO.edge and GF - # Configure gigafrost - cfg = {} - if exp_time != None: - cfg.update({"exposure_time_ms": exp_time}) - if exp_period != None: - cfg.update({"exposure_period_ms": exp_period}) - if roix != None: - cfg.update({"image_width": roix}) - if roiy != None: - cfg.update({"image_height": roiy}) - - dev.gfcam.configure(d=cfg) + dev.es1_tasks.enabled = False + dev.es1_psod.enabled = False + dev.es1_ddaq.enabled = False + dev.es1_ismc.enabled = False + dev.es1_roty.enabled = False + dev.gfcam.enabled = True + dev.gfdaq.enabled = True + dev.daq_stream0.enabled = True + dev.daq_stream1.enabled = False + dev.gfcam.cfgAcqMode.set(1).wait() + dev.gfcam.cmdSetParam.set(1).wait() dev.gfcam.cfgEnableExt.set(0).wait() dev.gfcam.cfgEnableSoft.set(0).wait() dev.gfcam.cfgEnableAlways.set(1).wait() @@ -42,8 +54,8 @@ def fede_darks(num, exp_time=None, exp_period=None, roix=None, roiy=None): # Commit changes to GF dev.gfcam.cmdSetParam.set(1).wait() - + ### TODO: camera reset print("Handing over to 'scans.acquire_dark") - scans.acquire_dark(num=num) + scans.acquire_dark(num=1, exp_burst=nimages_dark, exp_time=exposure_time, exp_period=exposure_period, image_width=roix, image_height=roiy)