Added PCO panel

This commit is contained in:
gac-x05la
2024-12-13 14:18:41 +01:00
committed by mohacsi_i
parent b80ed2d71c
commit 39403229f6
5 changed files with 124 additions and 64 deletions

View File

@@ -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)
```

View File

@@ -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}"

View File

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

View File

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

View File

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