Flaking
This commit is contained in:
parent
19f5f728cc
commit
c7867a910f
@ -25,47 +25,36 @@ fp = open(f"{path}/db/test_database.yml", "r")
|
|||||||
lut_db = yaml.load(fp, Loader=yaml.Loader)
|
lut_db = yaml.load(fp, Loader=yaml.Loader)
|
||||||
|
|
||||||
# Load SLS common database (already in DB)
|
# Load SLS common database (already in DB)
|
||||||
#fp = open(f"{path}/db/machine_database.yml", "r")
|
# fp = open(f"{path}/db/machine_database.yml", "r")
|
||||||
#lut_db = yaml.load(fp, Loader=yaml.Loader)
|
# lut_db = yaml.load(fp, Loader=yaml.Loader)
|
||||||
|
|
||||||
# Load beamline specific database
|
# Load beamline specific database
|
||||||
bl = os.getenv('BEAMLINE_XNAME', "X12SA")
|
bl = os.getenv("BEAMLINE_XNAME", "X12SA")
|
||||||
fp = open(f"{path}/db/{bl.lower()}_database.yml", "r")
|
fp = open(f"{path}/db/{bl.lower()}_database.yml", "r")
|
||||||
lut_db.update(yaml.load(fp, Loader=yaml.Loader))
|
lut_db.update(yaml.load(fp, Loader=yaml.Loader))
|
||||||
|
|
||||||
|
|
||||||
def createProxy(name: str, connect=True) -> OphydObject:
|
def createProxy(name: str, connect=True) -> OphydObject:
|
||||||
""" Factory routine to create an ophyd device with a pre-defined schema.
|
"""Factory routine to create an ophyd device with a pre-defined schema.
|
||||||
Does nothing if the device is already an OphydObject!
|
Does nothing if the device is already an OphydObject!
|
||||||
"""
|
"""
|
||||||
if issubclass(type(name), OphydObject):
|
if issubclass(type(name), OphydObject):
|
||||||
return name
|
return name
|
||||||
|
|
||||||
entry = lut_db[name]
|
entry = lut_db[name]
|
||||||
cls_candidate = globals()[entry["type"]]
|
cls_candidate = globals()[entry["type"]]
|
||||||
print(f"Device candidate: {cls_candidate}")
|
print(f"Device candidate: {cls_candidate}")
|
||||||
|
|
||||||
try:
|
if issubclass(cls_candidate, OphydObject):
|
||||||
if issubclass(cls_candidate, OphydObject):
|
ret = cls_candidate(**entry["config"])
|
||||||
ret = cls_candidate(**entry["config"])
|
if connect:
|
||||||
if connect:
|
ret.wait_for_connection(timeout=5)
|
||||||
ret.wait_for_connection(timeout=5)
|
return ret
|
||||||
return ret
|
else:
|
||||||
else:
|
raise RuntimeError(f"Unsupported return class: {entry["type"]}")
|
||||||
raise RuntimeError(f"Unsupported return class: {schema}")
|
|
||||||
except TypeError:
|
|
||||||
# Simulated devices
|
|
||||||
if issubclass(type(cls_candidate), OphydObject):
|
|
||||||
return cls_candidate
|
|
||||||
else:
|
|
||||||
raise RuntimeError(f"Unsupported return class: {schema}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
for key in lut_db:
|
for key in lut_db:
|
||||||
print(key)
|
print(key)
|
||||||
dut = createProxy(str(key))
|
dut = createProxy(str(key))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,79 +11,78 @@ from ophyd.pseudopos import pseudo_position_argument, real_position_argument, Ps
|
|||||||
|
|
||||||
|
|
||||||
class DelayStatic(Device):
|
class DelayStatic(Device):
|
||||||
""" Static axis for the T0 output channel
|
"""Static axis for the T0 output channel
|
||||||
It allows setting the logic levels, but the timing is fixed.
|
It allows setting the logic levels, but the timing is fixed.
|
||||||
The signal is high after receiving the trigger until the end
|
The signal is high after receiving the trigger until the end
|
||||||
of the holdoff period.
|
of the holdoff period.
|
||||||
"""
|
"""
|
||||||
# Other channel stuff
|
# Other channel stuff
|
||||||
ttl_mode = Component(EpicsSignal, "OutputModeTtlSS.PROC", kind=Kind.config)
|
ttl_mode = Component(EpicsSignal, "OutputModeTtlSS.PROC", kind=Kind.config)
|
||||||
nim_mode = Component(EpicsSignal, "OutputModeNimSS.PROC", kind=Kind.config)
|
nim_mode = Component(EpicsSignal, "OutputModeNimSS.PROC", kind=Kind.config)
|
||||||
polarity = Component(EpicsSignal, "OutputPolarityBI", write_pv="OutputPolarityBO", name='polarity', kind=Kind.config)
|
polarity = Component(EpicsSignal, "OutputPolarityBI", write_pv="OutputPolarityBO", name='polarity', kind=Kind.config)
|
||||||
amplitude = Component(EpicsSignal, "OutputAmpAI", write_pv="OutputAmpAO", name='amplitude', kind=Kind.config)
|
amplitude = Component(EpicsSignal, "OutputAmpAI", write_pv="OutputAmpAO", name='amplitude', kind=Kind.config)
|
||||||
polarity = Component(EpicsSignal, "OutputOffsetAI", write_pv="OutputOffsetAO", name='offset', kind=Kind.config)
|
polarity = Component(EpicsSignal, "OutputOffsetAI", write_pv="OutputOffsetAO", name='offset', kind=Kind.config)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DummyPositioner(Device, PositionerBase):
|
class DummyPositioner(Device, PositionerBase):
|
||||||
setpoint = Component(EpicsSignal, "DelayAO", kind=Kind.config)
|
setpoint = Component(EpicsSignal, "DelayAO", kind=Kind.config)
|
||||||
readback = Component(EpicsSignalRO, "DelayAI", kind=Kind.config)
|
readback = Component(EpicsSignalRO, "DelayAI", kind=Kind.config)
|
||||||
|
|
||||||
|
|
||||||
class DelayPair(PseudoPositioner):
|
class DelayPair(PseudoPositioner):
|
||||||
""" Delay pair interface for DG645
|
"""Delay pair interface for DG645
|
||||||
|
|
||||||
Virtual motor interface to a pair of signals (on the frontpanel).
|
Virtual motor interface to a pair of signals (on the frontpanel).
|
||||||
It offers a simple delay and pulse width interface for scanning.
|
It offers a simple delay and pulse width interface for scanning.
|
||||||
"""
|
"""
|
||||||
# The pseudo positioner axes
|
# The pseudo positioner axes
|
||||||
delay = Component(PseudoSingle, limits=(0, 2000.0), name='delay')
|
delay = Component(PseudoSingle, limits=(0, 2000.0), name='delay')
|
||||||
width = Component(PseudoSingle, limits=(0, 2000.0), name='pulsewidth')
|
width = Component(PseudoSingle, limits=(0, 2000.0), name='pulsewidth')
|
||||||
# The real delay axes
|
# The real delay axes
|
||||||
ch1 = Component(DummyPositioner, name='ch1')
|
ch1 = Component(DummyPositioner, name='ch1')
|
||||||
ch2 = Component(DummyPositioner, name='ch2')
|
ch2 = Component(DummyPositioner, name='ch2')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Change suffix names before connecting (a bit of dynamic connections)
|
# Change suffix names before connecting (a bit of dynamic connections)
|
||||||
self.__class__.__dict__['ch1'].suffix = kwargs['channel'][0]
|
self.__class__.__dict__['ch1'].suffix = kwargs['channel'][0]
|
||||||
self.__class__.__dict__['ch2'].suffix = kwargs['channel'][1]
|
self.__class__.__dict__['ch2'].suffix = kwargs['channel'][1]
|
||||||
del kwargs['channel']
|
del kwargs['channel']
|
||||||
# Call parent to start the connections
|
# Call parent to start the connections
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@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(ch1=pseudo_pos.delay, ch2=pseudo_pos.delay+pseudo_pos.width)
|
return self.RealPosition(ch1=pseudo_pos.delay, ch2=pseudo_pos.delay+pseudo_pos.width)
|
||||||
|
|
||||||
@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(delay=real_pos.ch1, width=real_pos.ch2 - real_pos.ch1)
|
return self.PseudoPosition(delay=real_pos.ch1, width=real_pos.ch2 - real_pos.ch1)
|
||||||
|
|
||||||
|
|
||||||
class DelayGeneratorDG645(Device):
|
class DelayGeneratorDG645(Device):
|
||||||
""" DG645 delay generator
|
"""DG645 delay generator
|
||||||
|
|
||||||
This class implements a thin Ophyd wrapper around the Stanford Research DG645
|
This class implements a thin Ophyd wrapper around the Stanford Research DG645
|
||||||
digital delay generator.
|
digital delay generator.
|
||||||
|
|
||||||
Internally, the DG645 generates 8+1 signals: A, B, C, D, E, F, G, H and T0
|
Internally, the DG645 generates 8+1 signals: A, B, C, D, E, F, G, H and T0
|
||||||
Front panel outputs T0, AB, CD, EF and GH are a combination of these signals.
|
Front panel outputs T0, AB, CD, EF and GH are a combination of these signals.
|
||||||
Back panel outputs are directly routed signals. So signals are NOT INDEPENDENT.
|
Back panel outputs are directly routed signals. So signals are NOT INDEPENDENT.
|
||||||
|
|
||||||
|
Front panel signals:
|
||||||
Front panel signals:
|
All signals go high after their defined delays and go low after the trigger
|
||||||
All signals go high after their defined delays and go low after the trigger
|
holdoff period, i.e. this is the trigger window. Front panel outputs provide
|
||||||
holdoff period, i.e. this is the trigger window. Front panel outputs provide
|
a combination of these events.
|
||||||
a combination of these events.
|
Option 1 back panel 5V signals:
|
||||||
Option 1 back panel 5V signals:
|
All signals go high after their defined delays and go low after the trigger
|
||||||
All signals go high after their defined delays and go low after the trigger
|
holdoff period, i.e. this is the trigger window. The signals will stay high
|
||||||
holdoff period, i.e. this is the trigger window. The signals will stay high
|
until the end of the window.
|
||||||
until the end of the window.
|
Option 2 back panel 30V signals:
|
||||||
Option 2 back panel 30V signals:
|
All signals go high after their defined delays for ~100ns. This is fixed by
|
||||||
All signals go high after their defined delays for ~100ns. This is fixed by
|
electronics (30V needs quite some power). This is not implemented in the
|
||||||
electronics (30V needs quite some power). This is not implemented in the
|
current device
|
||||||
current device
|
|
||||||
"""
|
"""
|
||||||
state = Component(EpicsSignalRO, "EventStatusLI", name='status_register')
|
state = Component(EpicsSignalRO, "EventStatusLI", name='status_register')
|
||||||
status = Component(EpicsSignalRO, "StatusSI", name='status')
|
status = Component(EpicsSignalRO, "StatusSI", name='status')
|
||||||
@ -94,7 +93,7 @@ class DelayGeneratorDG645(Device):
|
|||||||
channelCD = Component(DelayPair, "", name='CD', channel="CD")
|
channelCD = Component(DelayPair, "", name='CD', channel="CD")
|
||||||
channelEF = Component(DelayPair, "", name='EF', channel="EF")
|
channelEF = Component(DelayPair, "", name='EF', channel="EF")
|
||||||
channelGH = Component(DelayPair, "", name='GH', channel="GH")
|
channelGH = Component(DelayPair, "", name='GH', channel="GH")
|
||||||
|
|
||||||
# Minimum time between triggers
|
# Minimum time between triggers
|
||||||
holdoff = Component(EpicsSignal, "TriggerHoldoffAI", write_pv="TriggerHoldoffAO", name='trigger_holdoff', kind=Kind.config)
|
holdoff = Component(EpicsSignal, "TriggerHoldoffAI", write_pv="TriggerHoldoffAO", name='trigger_holdoff', kind=Kind.config)
|
||||||
inhibit = Component(EpicsSignal, "TriggerInhibitMI", write_pv="TriggerInhibitMO", name='trigger_inhibit', kind=Kind.config)
|
inhibit = Component(EpicsSignal, "TriggerInhibitMI", write_pv="TriggerInhibitMO", name='trigger_inhibit', kind=Kind.config)
|
||||||
@ -112,15 +111,14 @@ class DelayGeneratorDG645(Device):
|
|||||||
burstDelay = Component(EpicsSignal, "BurstDelayAI", write_pv="BurstDelayAO", name='burstdelay', kind=Kind.config)
|
burstDelay = Component(EpicsSignal, "BurstDelayAI", write_pv="BurstDelayAO", name='burstdelay', kind=Kind.config)
|
||||||
burstPeriod = Component(EpicsSignal, "BurstPeriodAI", write_pv="BurstPeriodAO", name='burstperiod', kind=Kind.config)
|
burstPeriod = Component(EpicsSignal, "BurstPeriodAI", write_pv="BurstPeriodAO", name='burstperiod', kind=Kind.config)
|
||||||
|
|
||||||
|
|
||||||
def stage(self):
|
def stage(self):
|
||||||
"""Trigger the generator by arming to accept triggers"""
|
"""Trigger the generator by arming to accept triggers"""
|
||||||
self.arm.write(1).wait()
|
self.arm.write(1).wait()
|
||||||
|
|
||||||
def unstage(self):
|
def unstage(self):
|
||||||
"""Stop the trigger generator from accepting triggers"""
|
"""Stop the trigger generator from accepting triggers"""
|
||||||
self.arm.write(0).wait()
|
self.arm.write(0).wait()
|
||||||
|
|
||||||
def burstEnable(self, count, delay, period, config="all"):
|
def burstEnable(self, count, delay, period, config="all"):
|
||||||
"""Enable the burst mode"""
|
"""Enable the burst mode"""
|
||||||
# Validate inputs
|
# Validate inputs
|
||||||
@ -139,16 +137,12 @@ class DelayGeneratorDG645(Device):
|
|||||||
self.burstConfig.set(0).wait()
|
self.burstConfig.set(0).wait()
|
||||||
elif config=="first":
|
elif config=="first":
|
||||||
self.burstConfig.set(1).wait()
|
self.burstConfig.set(1).wait()
|
||||||
|
|
||||||
def busrtDisable(self):
|
def busrtDisable(self):
|
||||||
"""Disable the burst mode"""
|
"""Disable the burst mode"""
|
||||||
self.burstMode.set(0).wait()
|
self.burstMode.set(0).wait()
|
||||||
|
|
||||||
|
|
||||||
|
# Automatically connect to test environmenr if directly invoked
|
||||||
# pair = DelayPair("DGEN01:", name="delayer", channel="CD")
|
if __name__ == "__main__":
|
||||||
dgen = DelayGeneratorDG645("X01DA-PC-DGEN:", name="delayer")
|
dgen = DelayGeneratorDG645("X01DA-PC-DGEN:", name="delayer")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@ 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.
|
|
||||||
is completely custom XBPM with templates directly in the
|
|
||||||
VME repo. Thus it needs a custom ophyd template as well...
|
|
||||||
|
|
||||||
WARN: The x and y are not updated by the IOC
|
This wrapper provides a positioner interface for the ID control.
|
||||||
|
is completely custom XBPM with templates directly in the
|
||||||
|
VME repo. Thus it needs a custom ophyd template as well...
|
||||||
|
|
||||||
|
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)
|
||||||
@ -24,20 +24,3 @@ class InsertionDevice(PVPositioner):
|
|||||||
# (NA for important devices)
|
# (NA for important devices)
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,15 +2,16 @@ import numpy as np
|
|||||||
from ophyd import Device, Component, EpicsSignal, EpicsSignalRO
|
from ophyd import Device, Component, EpicsSignal, EpicsSignalRO
|
||||||
import matplotlib.pyplot as plt
|
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)
|
||||||
@ -21,17 +22,17 @@ class SpmBase(Device):
|
|||||||
y = Component(EpicsSignalRO, "Y", auto_monitor=True)
|
y = Component(EpicsSignalRO, "Y", auto_monitor=True)
|
||||||
scale = Component(EpicsSignal, "PositionScaleY", auto_monitor=True)
|
scale = Component(EpicsSignal, "PositionScaleY", auto_monitor=True)
|
||||||
offset = Component(EpicsSignal, "PositionOffsetY", auto_monitor=True)
|
offset = Component(EpicsSignal, "PositionOffsetY", auto_monitor=True)
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
used to monitor the beam height (only Y) for the bending magnet
|
|
||||||
beamlines of SLS.
|
|
||||||
|
|
||||||
This simulation device extends the basic proxy with a script that
|
SPM's consist of a set of four horizontal tungsten blades and are
|
||||||
fills signals with quasi-randomized values.
|
used to monitor the beam height (only Y) for the bending magnet
|
||||||
|
beamlines of SLS.
|
||||||
|
|
||||||
|
This simulation device extends the basic proxy with a script that
|
||||||
|
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)
|
||||||
@ -42,25 +43,25 @@ class SpmSim(SpmBase):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self._MX = 0
|
self._MX = 0
|
||||||
self._MY = 0
|
self._MY = 0
|
||||||
self._I0 = 255.0
|
self._I0 = 255.0
|
||||||
self._x = np.linspace(-5, 5, 64)
|
self._x = np.linspace(-5, 5, 64)
|
||||||
self._y = np.linspace(-5, 5, 64)
|
self._y = np.linspace(-5, 5, 64)
|
||||||
self._x, self._y = np.meshgrid(self._x, self._y)
|
self._x, self._y = np.meshgrid(self._x, self._y)
|
||||||
|
|
||||||
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)
|
||||||
return arr
|
return arr
|
||||||
|
|
||||||
@ -84,7 +85,7 @@ class SpmSim(SpmBase):
|
|||||||
#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__":
|
||||||
@ -101,4 +102,3 @@ if __name__ == "__main__":
|
|||||||
print("---")
|
print("---")
|
||||||
spm1.sim()
|
spm1.sim()
|
||||||
spm2.sim()
|
spm2.sim()
|
||||||
|
|
||||||
|
@ -4,12 +4,12 @@ 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
|
|
||||||
VME repo. Thus it needs a custom ophyd template as well...
|
|
||||||
|
|
||||||
WARN: The x and y are not updated by the IOC
|
This is completely custom XBPM with templates directly in the
|
||||||
|
VME repo. Thus it needs a custom ophyd template as well...
|
||||||
|
|
||||||
|
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)
|
||||||
@ -21,16 +21,16 @@ 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)
|
||||||
@ -48,23 +48,20 @@ class XbpmBase(Device):
|
|||||||
offsetV = Component(EpicsSignal, "PositionOffsetY", auto_monitor=False)
|
offsetV = Component(EpicsSignal, "PositionOffsetY", auto_monitor=False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
photoelectrons from the incoming X-ray beam. These electons
|
|
||||||
are collected and their current is measured. Effectively
|
|
||||||
they act as four quadrant photodiodes and are used as BPMs
|
|
||||||
at the undulator beamlines of SLS.
|
|
||||||
|
|
||||||
Note: EPICS provided signals are read only, but the user can
|
|
||||||
change the beam position offset.
|
|
||||||
|
|
||||||
This simulation device extends the basic proxy with a script that
|
XBPM's consist of a metal-coated diamond window that ejects
|
||||||
fills signals with quasi-randomized values.
|
photoelectrons from the incoming X-ray beam. These electons
|
||||||
|
are collected and their current is measured. Effectively
|
||||||
|
they act as four quadrant photodiodes and are used as BPMs
|
||||||
|
at the undulator beamlines of SLS.
|
||||||
|
|
||||||
|
Note: EPICS provided signals are read only, but the user can
|
||||||
|
change the beam position offset.
|
||||||
|
|
||||||
|
This simulation device extends the basic proxy with a script that
|
||||||
|
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)
|
||||||
@ -75,25 +72,25 @@ class XbpmSim(XbpmBase):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self._MX = 0
|
self._MX = 0
|
||||||
self._MY = 0
|
self._MY = 0
|
||||||
self._I0 = 255.0
|
self._I0 = 255.0
|
||||||
self._x = np.linspace(-5, 5, 64)
|
self._x = np.linspace(-5, 5, 64)
|
||||||
self._y = np.linspace(-5, 5, 64)
|
self._y = np.linspace(-5, 5, 64)
|
||||||
self._x, self._y = np.meshgrid(self._x, self._y)
|
self._x, self._y = np.meshgrid(self._x, self._y)
|
||||||
|
|
||||||
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)
|
||||||
return arr
|
return arr
|
||||||
|
|
||||||
@ -117,7 +114,7 @@ class XbpmSim(XbpmBase):
|
|||||||
#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__":
|
||||||
@ -134,20 +131,3 @@ if __name__ == "__main__":
|
|||||||
print("---")
|
print("---")
|
||||||
xbpm1.sim()
|
xbpm1.sim()
|
||||||
xbpm2.sim()
|
xbpm2.sim()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,11 +3,10 @@
|
|||||||
Created on Wed Oct 13 18:06:15 2021
|
Created on Wed Oct 13 18:06:15 2021
|
||||||
|
|
||||||
@author: mohacsi_i
|
@author: mohacsi_i
|
||||||
|
|
||||||
|
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
|
||||||
@ -16,9 +15,10 @@ 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!
|
||||||
"""
|
"""
|
||||||
lncorr = LN_CORR if lnc else 0.0
|
lncorr = LN_CORR if lnc else 0.0
|
||||||
angle = angle*np.pi/180 if deg else angle
|
angle = angle*np.pi/180 if deg else angle
|
||||||
@ -30,28 +30,28 @@ def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False):
|
|||||||
|
|
||||||
|
|
||||||
def e2w(energy):
|
def e2w(energy):
|
||||||
""" Convert between energy and wavelength
|
"""Convert between energy and wavelength
|
||||||
"""
|
"""
|
||||||
return 0.1 * 12398.42 / energy
|
return 0.1 * 12398.42 / energy
|
||||||
|
|
||||||
|
|
||||||
def w2e(wwl):
|
def w2e(wwl):
|
||||||
""" Convert between wavelength and energy
|
"""Convert between wavelength and energy
|
||||||
"""
|
"""
|
||||||
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
|
||||||
@ -61,37 +61,35 @@ def e2a(energy, hkl=[1,1,1], lnc=False, bent=False):
|
|||||||
return angle
|
return angle
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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')
|
||||||
# Virtual axis
|
# Virtual axis
|
||||||
energy = Component(PseudoSingle, name='energy')
|
energy = Component(PseudoSingle, name='energy')
|
||||||
|
|
||||||
_real = ['angle']
|
_real = ['angle']
|
||||||
|
|
||||||
@pseudo_position_argument
|
@pseudo_position_argument
|
||||||
def forward(self, pseudo_pos):
|
def forward(self, pseudo_pos):
|
||||||
return self.RealPosition(angle=180.0*e2a(pseudo_pos.energy)/3.141592)
|
return self.RealPosition(angle=180.0*e2a(pseudo_pos.energy)/3.141592)
|
||||||
|
|
||||||
@real_position_argument
|
@real_position_argument
|
||||||
def inverse(self, real_pos):
|
def inverse(self, real_pos):
|
||||||
return self.PseudoPosition(energy=a2e(3.141592*real_pos.angle/180.0))
|
return self.PseudoPosition(energy=a2e(3.141592*real_pos.angle/180.0))
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
@ -110,21 +108,16 @@ class MonoDccm(PseudoPositioner):
|
|||||||
|
|
||||||
@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(en1=-a2e(3.141592*real_pos.th1/180.0),
|
||||||
en2=a2e(3.141592*real_pos.th2/180.0),
|
en2=a2e(3.141592*real_pos.th2/180.0),
|
||||||
energy=-a2e(3.141592*real_pos.th1/180.0))
|
energy=-a2e(3.141592*real_pos.th1/180.0))
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,130 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Created on Wed Oct 13 18:06:15 2021
|
|
||||||
|
|
||||||
@author: mohacsi_i
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from math import isclose
|
|
||||||
from ophyd import EpicsSignal, EpicsSignalRO, EpicsMotor, PseudoPositioner, PseudoSingle, Device, Component, Kind
|
|
||||||
from ophyd.pseudopos import pseudo_position_argument, real_position_argument
|
|
||||||
from ophyd.sim import SynAxis, Syn2DGauss
|
|
||||||
|
|
||||||
LN_CORR = 2e-4
|
|
||||||
|
|
||||||
def a2e(angle, hkl=[1,1,1], lnc=False, bent=False, deg=False):
|
|
||||||
""" Convert between angle and energy for Si monchromators
|
|
||||||
ATTENTION: 'angle' must be in radians, not degrees!
|
|
||||||
"""
|
|
||||||
lncorr = LN_CORR if lnc else 0.0
|
|
||||||
angle = angle*np.pi/180 if deg else angle
|
|
||||||
|
|
||||||
# Lattice constant along direction
|
|
||||||
d0 = 5.43102 * (1.0-lncorr) / np.linalg.norm(hkl)
|
|
||||||
energy = 12.39842 / (2.0 * d0 * np.sin(angle))
|
|
||||||
return energy
|
|
||||||
|
|
||||||
|
|
||||||
def e2w(energy):
|
|
||||||
""" Convert between energy and wavelength
|
|
||||||
"""
|
|
||||||
return 0.1 * 12398.42 / energy
|
|
||||||
|
|
||||||
|
|
||||||
def w2e(wwl):
|
|
||||||
""" Convert between wavelength and energy
|
|
||||||
"""
|
|
||||||
return 12398.42 * 0.1 / wwl
|
|
||||||
|
|
||||||
|
|
||||||
def e2a(energy, hkl=[1,1,1], lnc=False, bent=False):
|
|
||||||
""" Convert between energy and angle for Si monchromators
|
|
||||||
ATTENTION: 'angle' must be in radians, not degrees!
|
|
||||||
"""
|
|
||||||
lncorr = LN_CORR if lnc else 0.0
|
|
||||||
|
|
||||||
# Lattice constant along direction
|
|
||||||
d0 = 2*5.43102 * (1.0-lncorr) / np.linalg.norm(hkl)
|
|
||||||
angle = np.arcsin(12.39842/d0/energy)
|
|
||||||
|
|
||||||
# Rfine for bent mirror
|
|
||||||
if bent:
|
|
||||||
rho = 2 * 19.65 * 8.35 / 28 * np.sin(angle)
|
|
||||||
dt = 0.2e-3 / rho * 0.279
|
|
||||||
d0 = 2 * 5.43102 * (1.0+dt) / np.linalg.norm(hkl)
|
|
||||||
angle = np.arcsin(12.39842/d0/energy)
|
|
||||||
|
|
||||||
return angle
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MonoMotor(PseudoPositioner):
|
|
||||||
""" Monochromator axis
|
|
||||||
|
|
||||||
Small wrapper to combine a real angular axis with the corresponding energy.
|
|
||||||
ATTENTION: 'angle' is in degrees, at least for PXIII
|
|
||||||
"""
|
|
||||||
# Real axis (in degrees)
|
|
||||||
angle = Component(EpicsMotor, "", name='angle')
|
|
||||||
# Virtual axis
|
|
||||||
energy = Component(PseudoSingle, name='energy')
|
|
||||||
|
|
||||||
_real = ['angle']
|
|
||||||
|
|
||||||
@pseudo_position_argument
|
|
||||||
def forward(self, pseudo_pos):
|
|
||||||
return self.RealPosition(angle=180.0*e2a(pseudo_pos.energy)/3.141592)
|
|
||||||
|
|
||||||
@real_position_argument
|
|
||||||
def inverse(self, real_pos):
|
|
||||||
return self.PseudoPosition(energy=a2e(3.141592*real_pos.angle/180.0))
|
|
||||||
|
|
||||||
|
|
||||||
class MonoDccm(PseudoPositioner):
|
|
||||||
""" Combined DCCM monochromator
|
|
||||||
|
|
||||||
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
|
|
||||||
have a beam offset.
|
|
||||||
ATTENTION: 'angle' is in degrees, at least for PXIII
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Real axis (in degrees)
|
|
||||||
th1 = Component(EpicsMotor, "ROX1", name='theta1')
|
|
||||||
th2 = Component(EpicsMotor, "ROX2", name='theta2')
|
|
||||||
|
|
||||||
# Virtual axes
|
|
||||||
en1 = Component(PseudoSingle, name='en1')
|
|
||||||
en2 = Component(PseudoSingle, name='en2')
|
|
||||||
energy = Component(PseudoSingle, name='energy', kind=Kind.hinted)
|
|
||||||
|
|
||||||
# Other parameters
|
|
||||||
#feedback = Component(EpicsSignal, "MONOBEAM", name="feedback")
|
|
||||||
#enc1 = Component(EpicsSignalRO, "1:EXC1", name="enc1")
|
|
||||||
#enc2 = Component(EpicsSignalRO, "1:EXC2", name="enc2")
|
|
||||||
|
|
||||||
@pseudo_position_argument
|
|
||||||
def forward(self, pseudo_pos):
|
|
||||||
"""
|
|
||||||
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:
|
|
||||||
# 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)
|
|
||||||
else:
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
@real_position_argument
|
|
||||||
def inverse(self, real_pos):
|
|
||||||
return self.PseudoPosition(en1=-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))
|
|
||||||
|
|
||||||
|
|
@ -2,20 +2,14 @@ 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")
|
||||||
@ -37,16 +31,15 @@ class SlitH(PseudoPositioner):
|
|||||||
gapx=real_pos.x2-real_pos.x1)
|
gapx=real_pos.x2-real_pos.x1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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")
|
||||||
y2 = Component(EpicsMotor, "TRY2")
|
y2 = Component(EpicsMotor, "TRY2")
|
||||||
@ -56,13 +49,12 @@ class SlitV(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(y1=pseudo_pos.ceny-pseudo_pos.gapy/2,
|
return self.RealPosition(y1=pseudo_pos.ceny-pseudo_pos.gapy/2,
|
||||||
y2=pseudo_pos.ceny+pseudo_pos.gapy/2)
|
y2=pseudo_pos.ceny+pseudo_pos.gapy/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(ceny=(real_pos.y1+real_pos.y2)/2,
|
return self.PseudoPosition(ceny=(real_pos.y1+real_pos.y2)/2,
|
||||||
gapy=real_pos.y2-real_pos.y1)
|
gapy=real_pos.y2-real_pos.y1)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user