GFclient direct adaptation instantiates

This commit is contained in:
Mohacsi Istvan
2024-06-25 16:00:36 +02:00
committed by mohacsi_i
parent ba819d6df8
commit dc6a8eea0a
3 changed files with 319 additions and 36 deletions

View File

@@ -10,6 +10,7 @@ class GfStatus(Enum):
ACQUIRING = 3
CONFIGURED = 4
STOPPED = 5
INIT = 6
# BACKEND ADDRESSES
BE1_DAFL_CLIENT = 'http://xbl-daq-33:8080'
@@ -28,12 +29,23 @@ BE3_SOUTH_MAC = [0x50, 0x65, 0xf3, 0x81, 0xd5, 0x31] # ens4
BE3_NORTH_IP = [10, 4, 0, 101]
BE3_SOUTH_IP = [10, 0, 0, 101]
BE999_DAFL_CLIENT = "http://xbl-daq-28:8080"
BE999_SOUTH_MAC = [0x9C, 0xDC, 0x71, 0x47, 0xE5, 0xD1] # 9c:dc:71:47:e5:d1
BE999_NORTH_MAC = [0x9C, 0xDC, 0x71, 0x47, 0xE5, 0xDD] # 9c:dc:71:47:e5:dd
BE999_NORTH_IP = [10, 4, 0, 101]
BE999_SOUTH_IP = [10, 0, 0, 101]
# GF Names
GF1 = 'gf1'
GF2 = 'gf2'
GF3 = 'gf3'
# CAMERA
ERROR_BACKEND_NAME = 'Backend not recognized.'

View File

@@ -0,0 +1,244 @@
import inspect
import os
import sys
import jsonschema
_min_exposure_ms = 0.002
_max_exposure_ms = 40
_min_roix = 48
_max_roix = 2016
_step_roix = 48
_min_roiy = 4
_max_roiy = 2016
_step_roiy = 4
valid_roix = range(_min_roix, _max_roix + 1, _step_roix)
valid_roiy = range(_min_roiy, _max_roiy + 1, _step_roiy)
class NoTraceBackWithLineNumber(Exception):
def __init__(self, msg):
if type(msg).__name__ in ["ConnectionError", "ReadTimeout"]:
print("\n ConnectionError/ReadTimeout: it seems that the server "
"is not running/responding.\n")
try:
ln = sys.exc_info()[-1].tb_lineno
except AttributeError:
ln = inspect.currentframe().f_back.f_lineno
self.args = "{0.__name__} (line {1}): {2}".format(type(self), ln, msg),
sys.tracebacklimit = None
return None
class GfError(NoTraceBackWithLineNumber):
pass
class GfWarning(NoTraceBackWithLineNumber):
pass
class GfNotAValidConfig(NoTraceBackWithLineNumber):
pass
class GfCamNotFound(NoTraceBackWithLineNumber):
pass
def is_valid_url(url):
return(url.startswith('http://')) # FIXME: do more checks?
def is_valid_exposure_ms(e):
"""check if an exposure time e is valid for gigafrost
e: exposure time in milliseconds
"""
return e >= _min_exposure_ms and e <= _max_exposure_ms
def port2byte(port):
return [(port >> 8) & 0xff, port & 0xff]
def extend_header_table(table, mac, destination_ip, destination_port,
source_port):
"""
Extend the header table by a further entry.
Parameters
----------
table :
The table to be extended
mac :
The mac address for the new entry
destination_ip :
The destination IP address for the new entry
destination_port :
The destination port for the new entry
source_port :
The source port for the new entry
"""
table.extend(mac)
table.extend(destination_ip)
table.extend(port2byte(destination_port))
table.extend(port2byte(source_port))
return table
def is_valid_roi(roiy, roix):
return roiy in valid_roiy and roix in valid_roix
def _print_max_framerate(exposure, roix, roiy):
print("roiy=%4i roix=%4i exposure=%6.3fms: %8.1fHz" %
(roiy, roix, exposure,
max_framerate_Hz(exposure, roix=roix, roiy=roiy)))
def print_max_framerate(exposure_ms=_min_exposure_ms, shape='square'):
valid_shapes = ['square', 'landscape', 'portrait']
if shape not in valid_shapes:
raise ValueError("shape must be one of %s" % valid_shapes)
if shape == 'square':
for r in valid_roix:
_print_max_framerate(exposure_ms, r, r)
if shape == 'portrait':
for x in valid_roix:
_print_max_framerate(exposure_ms, roix=x, roiy=_max_roiy)
if shape == 'landscape':
for y in valid_roix: # valid_roix is a subset of valid_roiy. Use the smaller set to get a more manageable amount of output lines
_print_max_framerate(exposure_ms, roix=_max_roix, roiy=y)
def max_framerate_Hz(exposure_ms=_min_exposure_ms,
roix=_max_roix, roiy=_max_roiy, clk_mhz=62.5):
"""
returns maximum achievable frame rate in auto mode in Hz
Gerd Theidel wrote:
Hallo zusammen,
hier wie besprochen die Info zu den Zeiten.
Im Anhang findet ihr ein Python Beispiel zur
Berechnung der maximalen Framerate im auto trigger mode.
Bei den anderen modes sollte man etwas darunter bleiben,
wie auch im PCO Manual beschrieben.
Zusammengefasst mit den aktuellen Konstanten:
exposure_ms : 0.002 ... 40 (ms)
clk_mhz : 62.5 | 55.0 | 52.5 | 50.0 (MHz)
t_readout = ( ((roi_x / 24) + 14) * (roi_y / 4) + 405) / (1e6 * clk_mhz)
t_exp_sys = (exposure_ms / 1000.0) + (261 / (1e6 * clk_mhz))
framerate = 1.0 / max(t_readout, t_exp_sys)
Gruss,
Gerd
"""
if exposure_ms < 0.002 or exposure_ms > 40:
raise ValueError('exposure_ms not in interval [0.002, 40.]')
valid_clock_values = [62.5, 55.0, 52.5, 50.0]
if clk_mhz not in valid_clock_values:
raise ValueError('clock rate not in %s' % valid_clock_values)
# Constants
PLB_IMG_SENS_COUNT_X_OFFS = 2
PLB_IMG_SENS_COUNT_Y_OFFS = 1
# Constant Clock Cycles
CC_T2 = 88 # 99
CC_T3 = 125
CC_T4 = 1
CC_T10 = 2
CC_T11 = 4
CC_T5_MINUS_T11 = 20
CC_T15_MINUS_T10 = 3
CC_TR3 = 1
CC_T13_MINUS_TR3 = 2
CC_150NS = 7 # additional delay through states
CC_DELAY_BEFORE_RESET = 4 # at least 50 ns
CC_ADDITIONAL_TSYS = 10
CC_PIX_RESET_LENGTH = 40 # at least 40 CLK Cycles at 62.5 MHz
CC_COUNT_X_MAX = 84
CC_ROW_OVERHEAD_TIME = (11 + CC_TR3 + CC_T13_MINUS_TR3)
CC_FRAME_OVERHEAD_TIME = (
8 + CC_T15_MINUS_T10 + CC_T10 + CC_T2 + CC_T3 + CC_T4 + CC_T5_MINUS_T11 + CC_T11)
CC_INTEGRATION_START_DELAY = CC_COUNT_X_MAX + CC_ROW_OVERHEAD_TIME + \
CC_DELAY_BEFORE_RESET + CC_PIX_RESET_LENGTH + CC_150NS + 5
CC_TIME_TSYS = CC_FRAME_OVERHEAD_TIME + CC_ADDITIONAL_TSYS
# 12 pixel blocks per quarter
roix = max(((roix + 47) / 48) * 48, PLB_IMG_SENS_COUNT_X_OFFS * 24)
# 2 line blocks per quarter
roiy = max(((roiy + 3) / 4) * 4, PLB_IMG_SENS_COUNT_Y_OFFS * 4)
# print("CC_INTEGRATION_START_DELAY + CC_FRAME_OVERHEAD_TIME",CC_INTEGRATION_START_DELAY + CC_FRAME_OVERHEAD_TIME)
# print("CC_ROW_OVERHEAD_TIME",CC_ROW_OVERHEAD_TIME)
# print("CC_TIME_TSYS",CC_TIME_TSYS)
t_readout = (CC_INTEGRATION_START_DELAY + CC_FRAME_OVERHEAD_TIME +
((roix / 24) + CC_ROW_OVERHEAD_TIME) * (roiy / 4)) / (1e6 * clk_mhz)
t_exp_sys = (exposure_ms / 1000.0) + (CC_TIME_TSYS / (1e6 * clk_mhz))
# with constants as of time of writing:
# t_readout = ( ((roix / 24) + 14) * (roiy / 4) + 405) / (1e6 * clk_mhz)
# t_exp_sys = (exposure_ms / 1000.0) + (261 / (1e6 * clk_mhz))
framerate = 1.0 / max(t_readout, t_exp_sys)
return (framerate)
layoutSchema = {
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"patternProperties":
{
"^[a-zA-Z0-9_.-]*$":
{
"type": "object",
"required":["writer","DaflClient", "zmq_stream", "live_preview", "ioc_name", "description"],
"properties":{
"writer": {
"type": "string"
},
"DaflClient": {
"type": "string"
},
"zmq_stream": {
"type": "string"
},
"live_preview": {
"type": "string"
},
"ioc_name": {
"type": "string"
},
"description": {
"type": "string"
}
}
}
},
"additionalProperties": False
}
def validateJson(jsonData):
try:
jsonschema.validate(instance=jsonData, schema=layoutSchema)
except jsonschema.exceptions.ValidationError:
return False
return True

View File

@@ -8,7 +8,8 @@ import time
from typing import Union
from collections import OrderedDict
import gfconstants as const
from gfutils import extend_header_table, port2byte
class GigaFrostClient(Device):
"""
@@ -18,7 +19,10 @@ class GigaFrostClient(Device):
This means that the camera behaves differently than the SF cameras and
it has a lot of Tomcat specific additions.
"""
cfgConnectionParam = Component(EpicsSignalRO, "CONN_PARM", string=True, auto_monitor=True)
infoBusyFlag = Component(EpicsSignalRO, "BUSY_STAT", auto_monitor=True)
infoSyncFlag = Component(EpicsSignalRO, "SYNC_FLAG", auto_monitor=True)
cmdSyncHw = Component(EpicsSignal, "SYNC_SWHW.PROC", put_complete=True)
cfgConnectionParam = Component(EpicsSignal, "CONN_PARM", string=True, put_complete=True)
cmdStartCamera = Component(EpicsSignal, "START_CAM", put_complete=True)
cmdSetParam = Component(EpicsSignal, "SET_PARAM.PROC", put_complete=True)
@@ -43,22 +47,22 @@ class GigaFrostClient(Device):
cmdSoftExposure = Component(EpicsSignal, "SOFT_EXP", put_complete=True)
# Trigger configuration PVs
cfgCntStartBit = Component(EpicsSignal, "CNT_STARTBIT", read_pv="CNT_STARTBIT_RBV", put_complete=True)
cfgCntEndBit = Component(EpicsSignal, "CNT_ENDBIT", read_pv="CNT_ENDBIT_RBV", put_complete=True)
cfgCntStartBit = Component(EpicsSignal, "CNT_STARTBIT_RBV", write_pv="CNT_STARTBIT", put_complete=True)
cfgCntEndBit = Component(EpicsSignal, "CNT_ENDBIT_RBV", write_pv="CNT_ENDBIT", put_complete=True)
# Enable modes
cfgTrigEnableExt = Component(EpicsSignal, "MODE_ENBL_EXT", read_pv="MODE_ENBL_EXT_RBV", put_complete=True)
cfgTrigEnableSoft = Component(EpicsSignal, "MODE_ENBL_SOFT", read_pv="MODE_ENBL_SOFT_RBV", put_complete=True)
cfgTrigEnableAuto = Component(EpicsSignal, "MODE_ENBL_AUTO", read_pv="MODE_ENBL_AUTO_RBV", put_complete=True)
cfgTrigVirtEnable = Component(EpicsSignal, "MODE_ENBL_EXP", read_pv="MODE_ENBL_EXP_RBV", put_complete=True)
cfgTrigEnableExt = Component(EpicsSignal, "MODE_ENBL_EXT_RBV", write_pv="MODE_ENBL_EXT", put_complete=True)
cfgTrigEnableSoft = Component(EpicsSignal, "MODE_ENBL_SOFT_RBV", write_pv="MODE_ENBL_SOFT", put_complete=True)
cfgTrigEnableAuto = Component(EpicsSignal, "MODE_ENBL_AUTO_RBV", write_pv="MODE_ENBL_AUTO", put_complete=True)
cfgTrigVirtEnable = Component(EpicsSignal, "MODE_ENBL_EXP_RBV", write_pv="MODE_ENBL_EXP", put_complete=True)
# Trigger modes
cfgTrigExt = Component(EpicsSignal, "MODE_TRIG_EXT", read_pv="MODE_TRIG_EXT_RBV", put_complete=True)
cfgTrigSoft = Component(EpicsSignal, "MODE_TRIG_SOFT", read_pv="MODE_TRIG_SOFT_RBV", put_complete=True)
cfgTrigTimer = Component(EpicsSignal, "MODE_TRIG_TIMER", read_pv="MODE_TRIG_TIMER_RBV", put_complete=True)
cfgTrigAuto = Component(EpicsSignal, "MODE_TRIG_AUTO", read_pv="MODE_TRIG_AUTO_RBV", put_complete=True)
cfgTrigExt = Component(EpicsSignal, "MODE_TRIG_EXT_RBV", write_pv="MODE_TRIG_EXT", put_complete=True)
cfgTrigSoft = Component(EpicsSignal, "MODE_TRIG_SOFT_RBV", write_pv="MODE_TRIG_SOFT", put_complete=True)
cfgTrigTimer = Component(EpicsSignal, "MODE_TRIG_TIMER_RBV", write_pv="MODE_TRIG_TIMER", put_complete=True)
cfgTrigAuto = Component(EpicsSignal, "MODE_TRIG_AUTO_RBV", write_pv="MODE_TRIG_AUTO", put_complete=True)
# Exposure modes
cfgTrigExpExt = Component(EpicsSignal, "MODE_EXP_EXT", read_pv="MODE_EXP_EXT_RBV", put_complete=True)
cfgTrigExpSoft = Component(EpicsSignal, "MODE_EXP_SOFT", read_pv="MODE_EXP_SOFT_RBV", put_complete=True)
cfgTrigExpTimer = Component(EpicsSignal, "MODE_EXP_TIMER", read_pv="MODE_EXP_TIMER_RBV", put_complete=True)
cfgTrigExpExt = Component(EpicsSignal, "MODE_EXP_EXT_RBV", write_pv="MODE_EXP_EXT", put_complete=True)
cfgTrigExpSoft = Component(EpicsSignal, "MODE_EXP_SOFT_RBV", write_pv="MODE_EXP_SOFT", put_complete=True)
cfgTrigExpTimer = Component(EpicsSignal, "MODE_EXP_TIMER_RBV", write_pv="MODE_EXP_TIMER", put_complete=True)
# Line swap selection
cfgLineSwapSW = Component(EpicsSignal, "LS_SW", put_complete=True)
@@ -66,13 +70,25 @@ class GigaFrostClient(Device):
cfgLineSwapSE = Component(EpicsSignal, "LS_SE", put_complete=True)
cfgLineSwapNE = Component(EpicsSignal, "LS_NE", put_complete=True)
# HW settings as read only
cfgSyncFlag = Component(EpicsSignalRO, "PIXRATE", auto_monitor=True)
cfgTrigDelay = Component(EpicsSignalRO, "TRIG_DELAY", auto_monitor=True)
cfgSyncoutDelay = Component(EpicsSignalRO, "SYNCOUT_DLY", auto_monitor=True)
cfgOutputPolarity0 = Component(EpicsSignalRO, "BNC0_RBV", auto_monitor=True)
cfgOutputPolarity1 = Component(EpicsSignalRO, "BNC1_RBV", auto_monitor=True)
cfgOutputPolarity2 = Component(EpicsSignalRO, "BNC2_RBV", auto_monitor=True)
cfgOutputPolarity3 = Component(EpicsSignalRO, "BNC3_RBV", auto_monitor=True)
cfgInputPolarity1 = Component(EpicsSignalRO, "BNC4_RBV", auto_monitor=True)
cfgInputPolarity2 = Component(EpicsSignalRO, "BNC5_RBV", auto_monitor=True)
infoBoardTemp = Component(EpicsSignalRO, "T_BOARD", auto_monitor=True)
def __init__(self, prefix="", *, name, kind=None, read_attrs=None, configuration_attrs=None, parent=None, **kwargs):
def __init__(self, prefix="", *, name, backend_url=const.BE999_DAFL_CLIENT,
kind=None, read_attrs=None, configuration_attrs=None, parent=None, **kwargs):
super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs)
self.oldInitializer()
self.oldInitializer(backend_url=backend_url)
def oldInitializer(self, use_soft_enable=False,
def oldInitializer(self, auto_soft_enable=False,
timeout=10.0, backend_url=const.BE1_DAFL_CLIENT):
"""
Class to control the Gigafrost camera and readout system.
@@ -88,14 +104,11 @@ class GigaFrostClient(Device):
(default: http://xbl-daq-23:8080)
"""
self.name = name
self._auto_soft_enable = auto_soft_enable
self._timeout = timeout
self._backend_url = backend_url
self.state = const.GfStatus.NEW
self._original_soft_enable = self.cmdSoftEnable.get()
self._settings = None
self._north_mac, self._south_mac = self._define_backend_mac()
self._north_ip, self._south_ip = self._define_backend_ip()
@@ -128,7 +141,7 @@ class GigaFrostClient(Device):
# activate changes
self.cmdWriteService.set(1).wait()
if self._use_soft_enable:
if self._auto_soft_enable:
# trigger modes
self.cfgCntStartBit.set(1).wait()
self.cfgCntEndBit.set(0).wait()
@@ -152,7 +165,7 @@ class GigaFrostClient(Device):
# sets the basic settings
self._settings = {'backend_url' : self._backend_url,
'use_soft_enable' : self._use_soft_enable,
'auto_soft_enable' : self._auto_soft_enable,
'ioc_name' : self.prefix}
@@ -193,7 +206,7 @@ class GigaFrostClient(Device):
# switch to idle
## Stop acquisition
self.cmdStartCamera.set(0).wait()
if self._use_soft_enable:
if self._auto_soft_enable:
self.set_soft_enable(0)
# change settings
@@ -217,7 +230,7 @@ class GigaFrostClient(Device):
'scanid' : scanid,
'correction_mode' : correction_mode,
'backend_url' : self._backend_url,
'use_soft_enable' : self._use_soft_enable,
'auto_soft_enable' : self._auto_soft_enable,
'ioc_name' : self.name
}
@@ -228,7 +241,7 @@ class GigaFrostClient(Device):
# change to running
self.cmdStartCamera.set(1).wait()
# soft trigger on
if self._use_soft_enable:
if self._auto_soft_enable:
self.cmdSoftEnable.set(1).wait()
self.state = const.GfStatus.OPEN
return super().stage()
@@ -239,7 +252,7 @@ class GigaFrostClient(Device):
"""
# switch to idle
self.cmdStartCamera.set(0).wait()
if self._use_soft_enable:
if self._auto_soft_enable:
self.cmdSoftEnable.set(0).wait()
self.state = const.GfStatus.CLOSED
return super().unstage()
@@ -446,6 +459,18 @@ class GigaFrostClient(Device):
def get_backend_url(self):
return self._backend_url
def set_backend_ip(self, north, south):
"""
Method to manually set the backend ip
"""
self._north_ip, self._south_ip = north, south
def set_backend_mac(self, north, south):
"""
Method to manually set the backend mac
"""
self._north_mac, self._south_mac = north, south
def _build_udp_header_table(self):
"""
Build the header table for the communication
@@ -470,16 +495,18 @@ class GigaFrostClient(Device):
def _define_backend_ip(self):
if self._backend_url == const.BE3_DAFL_CLIENT: # xbl-daq-33
return const.BE3_NORTH_IP, const.BE3_SOUTH_IP
elif self._backend_url == const.BE999_DAFL_CLIENT:
return const.BE999_NORTH_IP, const.BE999_SOUTH_IP
else:
raise RuntimeError(const.ERROR_BACKEND_NAME % (
const.GF1, const.GF2, const.GF3))
raise RuntimeError(f"Backend not recognized. {(const.GF1, const.GF2, const.GF3)}")
def _define_backend_mac(self):
if self._backend_url == const.BE3_DAFL_CLIENT: # xbl-daq-33
return const.BE3_NORTH_MAC, const.BE3_SOUTH_MAC
elif self._backend_url == const.BE999_DAFL_CLIENT:
return const.BE999_NORTH_MAC, const.BE999_SOUTH_MAC
else:
raise RuntimeError(const.ERROR_BACKEND_NAME % (
const.GF1, const.GF2, const.GF3))
raise RuntimeError(f"Backend not recognized. {(const.GF1, const.GF2, const.GF3)}")
def _set_udp_header_table(self):
"""
@@ -491,7 +518,7 @@ class GigaFrostClient(Device):
# Automatically start simulation if directly invoked
if __name__ == "__main__":
gf = GigaFrostClient("X02DA-CAM-GF2")
gf = GigaFrostClient("X02DA-CAM-GF2:", name="gf2")
gf.wait_for_connection()