Codestyle

This commit is contained in:
mohacsi_i 2022-11-14 18:00:18 +01:00
parent c7867a910f
commit 3a7dd4e3cf
5 changed files with 107 additions and 98 deletions

View File

@ -4,11 +4,11 @@ from ophyd import PVPositioner, Component, EpicsSignal, EpicsSignalRO, Kind
class InsertionDevice(PVPositioner): class InsertionDevice(PVPositioner):
"""Python wrapper for the CSAXS insertion device control """Python wrapper for the CSAXS insertion device control
This wrapper provides a positioner interface for the ID control. This wrapper provides a positioner interface for the ID control.
is completely custom XBPM with templates directly in the is completely custom XBPM with templates directly in the
VME repo. Thus it needs a custom ophyd template as well... VME repo. Thus it needs a custom ophyd template as well...
WARN: The x and y are not updated by the IOC WARN: The x and y are not updated by the IOC
""" """
status = Component(EpicsSignalRO, "-USER:STATUS", auto_monitor=True) status = Component(EpicsSignalRO, "-USER:STATUS", auto_monitor=True)
errorSource = Component(EpicsSignalRO, "-USER:ERROR-SOURCE", auto_monitor=True) errorSource = Component(EpicsSignalRO, "-USER:ERROR-SOURCE", auto_monitor=True)
@ -23,4 +23,4 @@ class InsertionDevice(PVPositioner):
# Automatically start simulation if directly invoked # Automatically start simulation if directly invoked
# (NA for important devices) # (NA for important devices)
if __name__ == "__main__": if __name__ == "__main__":
pass pass

View File

@ -6,12 +6,12 @@ import matplotlib.pyplot as plt
class SpmBase(Device): class SpmBase(Device):
"""Python wrapper for the Staggered Blade Pair Monitors """Python wrapper for the Staggered Blade Pair Monitors
SPM's consist of a set of four horizontal tungsten blades and are SPM's consist of a set of four horizontal tungsten blades and are
used to monitor the beam height (only Y) for the bending magnet used to monitor the beam height (only Y) for the bending magnet
beamlines of SLS. beamlines of SLS.
Note: EPICS provided signals are read only, but the user can Note: EPICS provided signals are read only, but the user can
change the beam position offset. change the beam position offset.
""" """
# Motor interface # Motor interface
s1 = Component(EpicsSignalRO, "Current1", auto_monitor=True) s1 = Component(EpicsSignalRO, "Current1", auto_monitor=True)
@ -27,12 +27,12 @@ class SpmBase(Device):
class SpmSim(SpmBase): class SpmSim(SpmBase):
"""Python wrapper for simulated Staggered Blade Pair Monitors """Python wrapper for simulated Staggered Blade Pair Monitors
SPM's consist of a set of four horizontal tungsten blades and are SPM's consist of a set of four horizontal tungsten blades and are
used to monitor the beam height (only Y) for the bending magnet used to monitor the beam height (only Y) for the bending magnet
beamlines of SLS. beamlines of SLS.
This simulation device extends the basic proxy with a script that This simulation device extends the basic proxy with a script that
fills signals with quasi-randomized values. fills signals with quasi-randomized values.
""" """
# Motor interface # Motor interface
s1w = Component(EpicsSignal, "Current1:RAW.VAL", auto_monitor=False) s1w = Component(EpicsSignal, "Current1:RAW.VAL", auto_monitor=False)
@ -53,13 +53,13 @@ class SpmSim(SpmBase):
def _simFrame(self): def _simFrame(self):
"""Generator to simulate a jumping gaussian""" """Generator to simulate a jumping gaussian"""
# define normalized 2D gaussian # Define normalized 2D gaussian
def gaus2d(x=0, y=0, mx=0, my=0, sx=1, sy=1): def gaus2d(x=0, y=0, mx=0, my=0, sx=1, sy=1):
return np.exp(-((x - mx)**2. / (2. * sx**2.) + (y - my)**2. / (2. * sy**2.))) return np.exp(-((x - mx)**2. / (2. * sx**2.) + (y - my)**2. / (2. * sy**2.)))
#Generator for dynamic values # Generator for dynamic values
self._MX = 0.75 * self._MX + 0.25 * (10.0 * np.random.random()-5.0) self._MX = 0.75 * self._MX + 0.25 * (10.0 * np.random.random() - 5.0)
self._MY = 0.75 * self._MY + 0.25 * (10.0 * np.random.random()-5.0) self._MY = 0.75 * self._MY + 0.25 * (10.0 * np.random.random() - 5.0)
self._I0 = 0.75 * self._I0 + 0.25 * (255.0 * np.random.random()) self._I0 = 0.75 * self._I0 + 0.25 * (255.0 * np.random.random())
arr = self._I0 * gaus2d(self._x, self._y, self._MX, self._MY) arr = self._I0 * gaus2d(self._x, self._y, self._MX, self._MY)
@ -68,12 +68,12 @@ class SpmSim(SpmBase):
def sim(self): def sim(self):
# Get next frame # Get next frame
beam = self._simFrame() beam = self._simFrame()
total = np.sum(beam) - np.sum(beam[24:48,:]) total = np.sum(beam) - np.sum(beam[24:48, :])
rnge = np.floor(np.log10(total) - 0.0 ) rnge = np.floor(np.log10(total) - 0.0)
s1 = np.sum(beam[0:16,:]) / 10**rnge s1 = np.sum(beam[0:16, :]) / 10**rnge
s2 = np.sum(beam[16:24,:]) / 10**rnge s2 = np.sum(beam[16:24, :]) / 10**rnge
s3 = np.sum(beam[40:48,:]) / 10**rnge s3 = np.sum(beam[40:48, :]) / 10**rnge
s4 = np.sum(beam[48:64,:]) / 10**rnge s4 = np.sum(beam[48:64, :]) / 10**rnge
self.s1w.set(s1).wait() self.s1w.set(s1).wait()
self.s2w.set(s2).wait() self.s2w.set(s2).wait()
@ -82,23 +82,23 @@ class SpmSim(SpmBase):
self.rangew.set(rnge).wait() self.rangew.set(rnge).wait()
# Print debug info # Print debug info
print(f"Raw signals: R={rnge}\t{s1}\t{s2}\t{s3}\t{s4}") print(f"Raw signals: R={rnge}\t{s1}\t{s2}\t{s3}\t{s4}")
#plt.imshow(beam) # plt.imshow(beam)
#plt.show(block=False) # plt.show(block=False)
plt.pause(0.5) plt.pause(0.5)
# Automatically start simulation if directly invoked # Automatically start simulation if directly invoked
if __name__ == "__main__": if __name__ == "__main__":
spm1 = SpmSim("X06D-FE-BM1:", name="spm1") spm1 = SpmSim("X06D-FE-BM1:", name="spm1")
spm2 = SpmSim("X06D-FE-BM2:", name="spm2") spm2 = SpmSim("X06D-FE-BM2:", name="spm2")
spm1.wait_for_connection(timeout=5) spm1.wait_for_connection(timeout=5)
spm2.wait_for_connection(timeout=5) spm2.wait_for_connection(timeout=5)
spm1.rangew.set(1).wait() spm1.rangew.set(1).wait()
spm2.rangew.set(1).wait() spm2.rangew.set(1).wait()
while True: while True:
print("---") print("---")
spm1.sim() spm1.sim()
spm2.sim() spm2.sim()

View File

@ -6,10 +6,10 @@ import matplotlib.pyplot as plt
class XbpmCsaxsOp(Device): class XbpmCsaxsOp(Device):
"""Python wrapper for custom XBPMs in the cSAXS optics hutch """Python wrapper for custom XBPMs in the cSAXS optics hutch
This is completely custom XBPM with templates directly in the This is completely custom XBPM with templates directly in the
VME repo. Thus it needs a custom ophyd template as well... VME repo. Thus it needs a custom ophyd template as well...
WARN: The x and y are not updated by the IOC WARN: The x and y are not updated by the IOC
""" """
sum = Component(EpicsSignalRO, "SUM", auto_monitor=True) sum = Component(EpicsSignalRO, "SUM", auto_monitor=True)
x = Component(EpicsSignalRO, "POSH", auto_monitor=True) x = Component(EpicsSignalRO, "POSH", auto_monitor=True)
@ -23,14 +23,14 @@ class XbpmCsaxsOp(Device):
class XbpmBase(Device): class XbpmBase(Device):
"""Python wrapper for X-ray Beam Position Monitors """Python wrapper for X-ray Beam Position Monitors
XBPM's consist of a metal-coated diamond window that ejects XBPM's consist of a metal-coated diamond window that ejects
photoelectrons from the incoming X-ray beam. These electons photoelectrons from the incoming X-ray beam. These electons
are collected and their current is measured. Effectively are collected and their current is measured. Effectively
they act as four quadrant photodiodes and are used as BPMs they act as four quadrant photodiodes and are used as BPMs
at the undulator beamlines of SLS. at the undulator beamlines of SLS.
Note: EPICS provided signals are read only, but the user can Note: EPICS provided signals are read only, but the user can
change the beam position offset. change the beam position offset.
""" """
# Motor interface # Motor interface
s1 = Component(EpicsSignalRO, "Current1", auto_monitor=True) s1 = Component(EpicsSignalRO, "Current1", auto_monitor=True)
@ -51,17 +51,17 @@ class XbpmBase(Device):
class XbpmSim(XbpmBase): class XbpmSim(XbpmBase):
"""Python wrapper for simulated X-ray Beam Position Monitors """Python wrapper for simulated X-ray Beam Position Monitors
XBPM's consist of a metal-coated diamond window that ejects XBPM's consist of a metal-coated diamond window that ejects
photoelectrons from the incoming X-ray beam. These electons photoelectrons from the incoming X-ray beam. These electons
are collected and their current is measured. Effectively are collected and their current is measured. Effectively
they act as four quadrant photodiodes and are used as BPMs they act as four quadrant photodiodes and are used as BPMs
at the undulator beamlines of SLS. at the undulator beamlines of SLS.
Note: EPICS provided signals are read only, but the user can Note: EPICS provided signals are read only, but the user can
change the beam position offset. change the beam position offset.
This simulation device extends the basic proxy with a script that This simulation device extends the basic proxy with a script that
fills signals with quasi-randomized values. fills signals with quasi-randomized values.
""" """
# Motor interface # Motor interface
s1w = Component(EpicsSignal, "Current1:RAW.VAL", auto_monitor=False) s1w = Component(EpicsSignal, "Current1:RAW.VAL", auto_monitor=False)
@ -118,16 +118,16 @@ class XbpmSim(XbpmBase):
# Automatically start simulation if directly invoked # Automatically start simulation if directly invoked
if __name__ == "__main__": if __name__ == "__main__":
xbpm1 = XbpmSim("X01DA-FE-XBPM1:", name="xbpm1") xbpm1 = XbpmSim("X01DA-FE-XBPM1:", name="xbpm1")
xbpm2 = XbpmSim("X01DA-FE-XBPM2:", name="xbpm2") xbpm2 = XbpmSim("X01DA-FE-XBPM2:", name="xbpm2")
xbpm1.wait_for_connection(timeout=5) xbpm1.wait_for_connection(timeout=5)
xbpm2.wait_for_connection(timeout=5) xbpm2.wait_for_connection(timeout=5)
xbpm1.rangew.set(1).wait() xbpm1.rangew.set(1).wait()
xbpm2.rangew.set(1).wait() xbpm2.rangew.set(1).wait()
while True: while True:
print("---") print("---")
xbpm1.sim() xbpm1.sim()
xbpm2.sim() xbpm2.sim()

View File

@ -9,14 +9,15 @@ IMPORTANT: Virtual monochromator axes should be implemented already in EPICS!!!
import numpy as np import numpy as np
from math import isclose from math import isclose
from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor, PseudoPositioner, PseudoSingle, Device, Component, Kind from ophyd import (EpicsSignal, EpicsSignalRO, EpicsMotor, PseudoPositioner,
PseudoSingle, Device, Component, Kind)
from ophyd.pseudopos import pseudo_position_argument, real_position_argument from ophyd.pseudopos import pseudo_position_argument, real_position_argument
from ophyd.sim import SynAxis, Syn2DGauss from ophyd.sim import SynAxis, Syn2DGauss
LN_CORR = 2e-4 LN_CORR = 2e-4
def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False): def a2e(angle, hkl=[1, 1, 1], lnc=False, bent=False, deg=False):
"""Convert between angle and energy for Si monchromators """Convert between angle and energy for Si monchromators
ATTENTION: 'angle' must be in radians, not degrees! ATTENTION: 'angle' must be in radians, not degrees!
""" """
@ -24,7 +25,7 @@ def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False):
angle = angle*np.pi/180 if deg else angle angle = angle*np.pi/180 if deg else angle
# Lattice constant along direction # Lattice constant along direction
d0 = 5.43102 * (1.0-lncorr) / np.linalg.norm(hkl) d0 = 5.43102 * (1.0 - lncorr) / np.linalg.norm(hkl)
energy = 12.39842 / (2.0 * d0 * np.sin(angle)) energy = 12.39842 / (2.0 * d0 * np.sin(angle))
return energy return energy
@ -41,22 +42,22 @@ def w2e(wwl):
return 12398.42 * 0.1 / wwl return 12398.42 * 0.1 / wwl
def e2a(energy, hkl=[1,1,1], lnc=False, bent=False): def e2a(energy, hkl=[1, 1, 1], lnc=False, bent=False):
"""Convert between energy and angle for Si monchromators """Convert between energy and angle for Si monchromators
ATTENTION: 'angle' must be in radians, not degrees! ATTENTION: 'angle' must be in radians, not degrees!
""" """
lncorr = LN_CORR if lnc else 0.0 lncorr = LN_CORR if lnc else 0.0
# Lattice constant along direction # Lattice constant along direction
d0 = 2*5.43102 * (1.0-lncorr) / np.linalg.norm(hkl) d0 = 2 * 5.43102 * (1.0 - lncorr) / np.linalg.norm(hkl)
angle = np.arcsin(12.39842/d0/energy) angle = np.arcsin(12.39842 / d0 / energy)
# Rfine for bent mirror # Rfine for bent mirror
if bent: if bent:
rho = 2 * 19.65 * 8.35 / 28 * np.sin(angle) rho = 2 * 19.65 * 8.35 / 28 * np.sin(angle)
dt = 0.2e-3 / rho * 0.279 dt = 0.2e-3 / rho * 0.279
d0 = 2 * 5.43102 * (1.0+dt) / np.linalg.norm(hkl) d0 = 2 * 5.43102 * (1.0 + dt) / np.linalg.norm(hkl)
angle = np.arcsin(12.39842/d0/energy) angle = np.arcsin(12.39842 / d0 / energy)
return angle return angle
@ -64,8 +65,8 @@ def e2a(energy, hkl=[1,1,1], lnc=False, bent=False):
class MonoMotor(PseudoPositioner): class MonoMotor(PseudoPositioner):
"""Monochromator axis """Monochromator axis
Small wrapper to combine a real angular axis with the corresponding energy. Small wrapper to combine a real angular axis with the corresponding energy.
ATTENTION: 'angle' is in degrees, at least for PXIII ATTENTION: 'angle' is in degrees, at least for PXIII
""" """
# Real axis (in degrees) # Real axis (in degrees)
angle = Component(EpicsMotor, "", name='angle') angle = Component(EpicsMotor, "", name='angle')
@ -86,10 +87,10 @@ class MonoMotor(PseudoPositioner):
class MonoDccm(PseudoPositioner): class MonoDccm(PseudoPositioner):
"""Combined DCCM monochromator """Combined DCCM monochromator
The first crystal selects the energy, the second one is only following. The first crystal selects the energy, the second one is only following.
DCCMs are quite simple in terms that they can't crash and we don't DCCMs are quite simple in terms that they can't crash and we don't
have a beam offset. have a beam offset.
ATTENTION: 'angle' is in degrees, at least for PXIII ATTENTION: 'angle' is in degrees, at least for PXIII
""" """
# Real axis (in degrees) # Real axis (in degrees)
@ -102,22 +103,30 @@ class MonoDccm(PseudoPositioner):
energy = Component(PseudoSingle, name='energy', kind=Kind.hinted) energy = Component(PseudoSingle, name='energy', kind=Kind.hinted)
# Other parameters # Other parameters
#feedback = Component(EpicsSignal, "MONOBEAM", name="feedback") # feedback = Component(EpicsSignal, "MONOBEAM", name="feedback")
#enc1 = Component(EpicsSignalRO, "1:EXC1", name="enc1") # enc1 = Component(EpicsSignalRO, "1:EXC1", name="enc1")
#enc2 = Component(EpicsSignalRO, "1:EXC2", name="enc2") # enc2 = Component(EpicsSignalRO, "1:EXC2", name="enc2")
@pseudo_position_argument @pseudo_position_argument
def forward(self, pseudo_pos): def forward(self, pseudo_pos):
"""WARNING: We have an overdefined system! Not sure if common crystal movement is reliable without retuning""" """WARNING: We have an overdefined system! Not sure if common crystal movement is reliable without retuning"""
if abs(pseudo_pos.energy-self.energy.position) > 0.0001 and abs(pseudo_pos.en1-self.en1.position) < 0.0001 and abs(pseudo_pos.en2-self.en2.position) < 0.0001: if abs(pseudo_pos.energy-self.energy.position) > 0.0001 and abs(pseudo_pos.en1-self.en1.position) < 0.0001 and abs(pseudo_pos.en2-self.en2.position) < 0.0001:
# Probably the common energy was changed # Probably the common energy was changed
return self.RealPosition(th1=-180.0*e2a(pseudo_pos.energy)/3.141592, th2=180.0*e2a(pseudo_pos.energy)/3.141592) return self.RealPosition(
th1=-180.0 * e2a(pseudo_pos.energy) / 3.141592,
th2=180.0 * e2a(pseudo_pos.energy) / 3.141592
)
else: else:
# Probably the individual axes was changes # Probably the individual axes was changes
return self.RealPosition(th1=-180.0*e2a(pseudo_pos.en1)/3.141592, th2=180.0*e2a(pseudo_pos.en2)/3.141592) return self.RealPosition(
th1=-180.0 * e2a(pseudo_pos.en1 / 3.141592,
th2=180.0 * e2a(pseudo_pos.en2) / 3.141592
)
@real_position_argument @real_position_argument
def inverse(self, real_pos): def inverse(self, real_pos):
return self.PseudoPosition(en1=-a2e(3.141592*real_pos.th1/180.0), return self.PseudoPosition(
en2=a2e(3.141592*real_pos.th2/180.0), en1=-a2e(3.141592 * real_pos.th1 / 180.0),
energy=-a2e(3.141592*real_pos.th1/180.0)) en2=a2e(3.141592 * real_pos.th2 / 180.0),
energy=-a2e(3.141592 * real_pos.th1 / 180.0)
)

View File

@ -1,15 +1,15 @@
from ophyd import Device, Component, EpicsMotor, PseudoPositioner, PseudoSingle from ophyd import Device, Component, EpicsMotor, PseudoPositioner, PseudoSingle
from ophyd.pseudopos import pseudo_position_argument,real_position_argument from ophyd.pseudopos import pseudo_position_argument, real_position_argument
class SlitH(PseudoPositioner): class SlitH(PseudoPositioner):
"""Python wrapper for virtual slits """Python wrapper for virtual slits
These devices should be implemented as an EPICS SoftMotor IOC, These devices should be implemented as an EPICS SoftMotor IOC,
but thats not the case for all slits. So here is a pure ophyd but thats not the case for all slits. So here is a pure ophyd
implementation. Uses standard naming convention! implementation. Uses standard naming convention!
NOTE: The real and virtual axes are wrapped together. NOTE: The real and virtual axes are wrapped together.
""" """
# Motor interface # Motor interface
x1 = Component(EpicsMotor, "TRX1") x1 = Component(EpicsMotor, "TRX1")
@ -20,13 +20,13 @@ class SlitH(PseudoPositioner):
@pseudo_position_argument @pseudo_position_argument
def forward(self, pseudo_pos): def forward(self, pseudo_pos):
'''Run a forward (pseudo -> real) calculation''' """Run a forward (pseudo -> real) calculation"""
return self.RealPosition(x1=pseudo_pos.cenx-pseudo_pos.gapx/2, return self.RealPosition(x1=pseudo_pos.cenx-pseudo_pos.gapx/2,
x2=pseudo_pos.cenx+pseudo_pos.gapx/2) x2=pseudo_pos.cenx+pseudo_pos.gapx/2)
@real_position_argument @real_position_argument
def inverse(self, real_pos): def inverse(self, real_pos):
'''Run an inverse (real -> pseudo) calculation''' """Run an inverse (real -> pseudo) calculation"""
return self.PseudoPosition(cenx=(real_pos.x1+real_pos.x2)/2, return self.PseudoPosition(cenx=(real_pos.x1+real_pos.x2)/2,
gapx=real_pos.x2-real_pos.x1) gapx=real_pos.x2-real_pos.x1)
@ -34,11 +34,11 @@ class SlitH(PseudoPositioner):
class SlitV(PseudoPositioner): class SlitV(PseudoPositioner):
"""Python wrapper for virtual slits """Python wrapper for virtual slits
These devices should be implemented as an EPICS SoftMotor IOC, These devices should be implemented as an EPICS SoftMotor IOC,
but thats not the case for all slits. So here is a pure ophyd but thats not the case for all slits. So here is a pure ophyd
implementation. Uses standard naming convention! implementation. Uses standard naming convention!
NOTE: The real and virtual axes are wrapped together. NOTE: The real and virtual axes are wrapped together.
""" """
# Motor interface # Motor interface
y1 = Component(EpicsMotor, "TRY1") y1 = Component(EpicsMotor, "TRY1")