Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lens4escan #1202

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pcdsdevices/ccm.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ophyd.device import Component as Cpt
from ophyd.device import Device
from ophyd.device import FormattedComponent as FCpt
from ophyd.pseudopos import pseudo_position_argument, real_position_argument
from ophyd.signal import EpicsSignal, EpicsSignalRO, Signal
from ophyd.status import MoveStatus

Expand Down Expand Up @@ -589,6 +590,7 @@ def _update_intermediates(
ref2 = self.alio_to_energy(value + res_delta/2)
self.resolution.put(abs((ref1 - ref2) / res_delta), force=True)

@pseudo_position_argument
def forward(self, pseudo_pos: namedtuple) -> namedtuple:
"""
PseudoPositioner interface function for calculating the setpoint.
Expand All @@ -600,6 +602,7 @@ def forward(self, pseudo_pos: namedtuple) -> namedtuple:
alio = self.energy_to_alio(energy)
return self.RealPosition(alio=alio)

@real_position_argument
def inverse(self, real_pos: namedtuple) -> namedtuple:
"""
PseudoPositioner interface function for calculating the readback.
Expand Down Expand Up @@ -747,6 +750,7 @@ def __init__(
self.hutch = 'TST'
super().__init__(prefix, **kwargs)

@pseudo_position_argument
def forward(self, pseudo_pos: namedtuple) -> namedtuple:
"""
PseudoPositioner interface function for calculating the setpoint.
Expand All @@ -761,6 +765,7 @@ def forward(self, pseudo_pos: namedtuple) -> namedtuple:
vernier = energy * 1000
return self.RealPosition(alio=alio, acr_energy=vernier)

@real_position_argument
def inverse(self, real_pos: namedtuple) -> namedtuple:
"""
PseudoPositioner interface function for calculating the readback.
Expand Down
120 changes: 91 additions & 29 deletions pcdsdevices/lens.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from collections import defaultdict
from datetime import date

import numpy as np
from ophyd.device import Component as Cpt
from ophyd.device import FormattedComponent as FCpt
from pcdscalc import be_lens_calcs as calcs
Expand Down Expand Up @@ -67,6 +66,9 @@ class Prefocus(CombinedInOutRecordPositioner, LightpathInOutMixin):
# In should be everything except state 0 (Unknown) and state 1 (Out)
_in_if_not_out = True

tab_whitelist = ['x', 'y']
tab_component_names = True

def __init__(self, prefix, *, name, **kwargs):
# Set default transmission
# Done this way because states are still unknown at this point
Expand All @@ -76,6 +78,9 @@ def __init__(self, prefix, *, name, **kwargs):
else (1 if state in self.out_states
else 0))
super().__init__(prefix, name=name, **kwargs)
# motor aliases
self.x = self.x_motor
self.y = self.y_motor


class LensStackBase(BaseInterface, PseudoPositioner):
Expand Down Expand Up @@ -124,23 +129,25 @@ class LensStackBase(BaseInterface, PseudoPositioner):
y = FCpt(IMS, '{self.y_prefix}')
z = FCpt(IMS, '{self.z_prefix}')

calib_z = Cpt(PseudoSingleInterface)
beam_size = Cpt(PseudoSingleInterface)
calib_z = Cpt(PseudoSingleInterface, egu='m')
beam_size = Cpt(PseudoSingleInterface, egu='m')

tab_whitelist = ['tweak', 'align', 'calib_z', 'beam_size', 'create_lens',
'read_lens']
tab_component_names = True

def __init__(self, x_prefix, y_prefix, z_prefix, lens_set,
z_offset, z_dir, E, att_obj, lcls_obj=None,
mono_obj=None, *args, **kwargs):
z_offset, z_dir, E, att_obj, fwhm_unfocused=None,
lcls_obj=None, mono_obj=None, *args, **kwargs):
self.x_prefix = x_prefix
self.y_prefix = y_prefix
self.z_prefix = z_prefix
self.z_dir = z_dir
self.z_offset = z_offset
self.fwhm_unfocused = fwhm_unfocused or 500e-6

self._E = E
self._E = None
self._which_E = None
self._att_obj = att_obj
self._lcls_obj = lcls_obj
self._mono_obj = mono_obj
Expand All @@ -150,6 +157,45 @@ def __init__(self, x_prefix, y_prefix, z_prefix, lens_set,

super().__init__(x_prefix, *args, **kwargs)

@property
def energy(self):
"""
Get the energy from various sources based on current situation:
- If self.energy = <energy> has been used, it will return the
the user-defined energy
- If self._mono_obj is defined and inserted, it will return the
current mono energy
- Return the LCLS energy otherwise
"""
if self._E is not None:
self._which_E = "User"
return self._E
elif self._mono_obj is not None:
if self._mono_obj.inserted:
self._which_E = 'ccm'
return self._mono_obj.energy.energy.get().readback
else:
print('CCM not in, defaulting to LCLS energy')

if self._lcls_obj is not None:
self._which_E = 'lcls'
return self._lcls_obj.hard_e_energy.get()

@energy.setter
def energy(self, energy):
self._E = energy

def get_current_beam_info(self):
"""
Calculates the beam size (FWHM) at the IP for the current lens
positin (distance to the interaction point).
"""
dist_m = self.z.position / 1000 * self.z_dir + self.z_offset
beamsize = calcs.calc_beam_fwhm(self.energy, self.lens_set, distance=dist_m,
fwhm_unfocused=self.fwhm_unfocused)
print(f"Distance for beamsize {beamsize}: {dist_m}m")
return

def tweak(self):
"""
Call the tweak function from `pcdsdevice.interface`.
Expand Down Expand Up @@ -186,11 +232,13 @@ def forward(self, pseudo_pos):
AttributeError
If pseudo motor is not setup for use.
"""
if not np.isclose(pseudo_pos.beam_size, self.beam_size.position):
# if not np.isclose(pseudo_pos.beam_size, self.beam_size.position):
if True:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: to check again

beam_size = pseudo_pos.beam_size
dist = calcs.calc_distance_for_size(beam_size, self.lens_set,
self._E)[0]
self.energy)[0]
z_pos = (dist - self.z_offset) * self.z_dir * 1000
# z_pos = z_pos - 100 # dirty trick to get z in the range
else:
z_pos = pseudo_pos.calib_z
try:
Expand Down Expand Up @@ -228,9 +276,9 @@ def inverse(self, real_pos):
PseudoPosition
"""
dist_m = real_pos.z / 1000 * self.z_dir + self.z_offset
logger.info('dist_m %s', dist_m)
beamsize = calcs.calc_beam_fwhm(self._E, self.lens_set,
distance=dist_m)
beamsize = calcs.calc_beam_fwhm(self.energy, self.lens_set,
distance=dist_m,
printsummary=False)
return self.PseudoPosition(calib_z=real_pos.z, beam_size=beamsize)

def align(self, z_position=None, edge_offset=20):
Expand Down Expand Up @@ -302,8 +350,9 @@ def move(self, position, wait=True, timeout=None, moved_cb=None):
positioner instance.
"""
if self._make_safe() is True:
return super().move(position, wait=wait, timeout=timeout,
moved_cb=moved_cb)
st = super().move(position, wait=wait, timeout=timeout,
moved_cb=moved_cb)
return st
else:
logger.warning('Aborting moving for safety.')
return
Expand All @@ -322,20 +371,30 @@ def _make_safe(self):
logger.warning('Cannot do safe moveZ, no attenuator'
' object provided.')
return False
filt, thk = self._att_obj.filters[0], 0
for f in self._att_obj.filters:
t = f.thickness.get()
if t > thk:
filt, thk = f, t
if not filt.inserted:
filt.insert()
if 'Attenuator' in self._att_obj.__class__.__name__:
filt, thk = self._att_obj.filters[0], 0
for f in self._att_obj.filters:
t = f.thickness.get()
if t > thk:
filt, thk = f, t
if not filt.inserted:
filt.insert(wait=True)
time.sleep(0.01)
if filt.inserted:
logger.info('Beam stop attenuator moved in!')
safe = True
else:
logger.warning('Beam stop attenuator did not move in!')
safe = False
elif 'PulsePicker' in self._att_obj.__class__.__name__:
self._att_obj.close(wait=True)
time.sleep(0.01)
if filt.inserted:
logger.info('Beam stop attenuator moved in!')
safe = True
else:
logger.warning('Beam stop attenuator did not move in!')
safe = False
if self._att_obj.inserted:
logger.info('Pulse picker closed!')
safe = True
else:
logger.warning('Pulse picker did not close!')
safe = False
return safe


Expand All @@ -359,7 +418,8 @@ class LensStack(LensStackBase):
1 or -1, represents beam direction wrt z direction.
E: number, optional
Beam energy
att_obj : attenuator object, optional
att_obj : attenuator object, optional. Can be the beamline attenuator or
the pulse-picker. Used to block the beam while lens stack is moving.
lcls_obj
Object that gets PVs from lcls (for energy)
mono_obj
Expand Down Expand Up @@ -530,11 +590,13 @@ def __init__(self, x_prefix, y_prefix, z_prefix, z_offset, z_dir, E,

if lens_set is None:
# Defaulting this a the first set in the file for now
lens_set = calcs.get_lens_set(1, self.path)
self.lens_set = calcs.get_lens_set(1, self.path)
else:
self.lens_set = lens_set

super().__init__(
x_prefix, y_prefix, z_prefix, lens_set, z_offset, z_dir, E,
att_obj, *args, **kwargs
att_obj, lcls_obj, mono_obj, *args, **kwargs
)

def read_lens(self, print_only=False):
Expand Down
90 changes: 90 additions & 0 deletions pcdsdevices/lens_for_escan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from ophyd.device import Component as Cpt
from ophyd.pseudopos import PseudoSingle

from pcdsdevices.device import ObjectComponent as OCpt
from pcdsdevices.interface import FltMvInterface
from pcdsdevices.pseudopos import (PseudoPositioner, PseudoSingleInterface,
pseudo_position_argument,
real_position_argument)


class Lens4EScan(FltMvInterface, PseudoPositioner):
_real = ("mono_energy", "beam_size")
_pseudo = ("energy", )

mono_energy = OCpt('{mono_energy}')
beam_size = OCpt('{lens_stack.beam_size}')

energy = Cpt(PseudoSingleInterface)

tab_whitelist = ['calc_lens_pos', 'mono_energy', 'stack']
tab_component_names = True

def __init__(self, mono_energy, lens_stack,
desired_beamsize=5e-6,
*args, **kwargs):
self.mono = mono_energy
self.stack = lens_stack
self.desired_beamsize = desired_beamsize
super().__init__(*args, **kwargs)

@pseudo_position_argument
def forward(self, pseudo_pos):
return self.RealPosition(mono_energy=pseudo_pos.energy,
beam_size=self.desired_beamsize)

@real_position_argument
def inverse(self, real_pos):
return self.PseudoPosition(energy=real_pos.mono_energy)

@pseudo_position_argument
def move(self, position, *args, **kwargs):
self.stack.energy = position.energy
st = super().move(position, *args,
moved_cb=self.cb_open_beamstop,
**kwargs)
return st

def calc_lens_pos(self, energy):
"""
Return the expected lens position (x,y,z) for a given energy.
"""
stack_current_energy = self.stack.energy
if self.stack._which_E != 'User':
stack_current_energy = None
self.stack.energy = energy
lens_pos = self.stack.forward(beam_size=self.desired_beamsize)
self.stack.energy = stack_current_energy
return lens_pos

def cb_open_beamstop(self, obj):
"""
Callback function to open the attenuator or pulse picker at the end of a
move. If the atttenuator is used, this function assumes that the blade #9
is used to block the beam.
"""
if 'Attenuator' in self.stack._att_obj.__class__.__name__:
self.stack._att_obj.filters[9].remove()
elif 'PulsePicker' in self.stack._att_obj.__class__.__name__:
self.stack._att_obj.open()
return

def _concurrent_move(self, real_pos, **kwargs):
"""
Try done fix: override the waiting list with the pseudopos parents,
not pseudosingle, since cb is called on the parent in this case.

This is needed because PseudoSingle does not report the right status,
as they defer the motion to to their parent class (PseudoPositioner).
When building a pseudo-positioner made of PseudoSingles, one needs to
point the _real_waiting to the PseudoSingles' parent class.
"""
for real_axis in self._real:
if isinstance(real_axis, PseudoSingle):
self._real_waiting.append(real_axis.parent)
else:
self._real_waiting_append(real_axis)

for real, value in zip(self._real, real_pos):
self.log.debug("[concurrent] Moving %s to %s", real.name, value)
real.move(value, wait=False, moved_cb=self._real_finished, **kwargs)
2 changes: 1 addition & 1 deletion pcdsdevices/tests/test_lens_sets/test
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[2, 0.0002, 4, 0.0005]
null
Loading