Skip to content
This repository has been archived by the owner on Dec 16, 2022. It is now read-only.

Commit

Permalink
MNT #148 plans and functions
Browse files Browse the repository at this point in the history
  • Loading branch information
prjemian committed Feb 11, 2020
1 parent 30cd03d commit b9ac478
Show file tree
Hide file tree
Showing 13 changed files with 1,040 additions and 10 deletions.
15 changes: 5 additions & 10 deletions profile_bluesky/startup/instrument/console_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@

from .mpl.console import *

# # are our soft IOCs running?
# logger.info("check if soft IOCs are running")
# from .iocs.check_iocs import *

logger.info("bluesky framework")

from .framework import *
from .devices import *
# TODO:
# from .plans import *
# from .utils import *
from .plans import *
from .utils import *

# from apstools.utils import device_read2table
# from apstools.utils import print_RE_md
# from apstools.utils import show_ophyd_symbols
from apstools.utils import device_read2table
from apstools.utils import print_RE_md
from apstools.utils import show_ophyd_symbols
3 changes: 3 additions & 0 deletions profile_bluesky/startup/instrument/devices/lambda_750k.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"""
X-Spectrum Lambda 750K area detector (EPICS)
* detector name: LAMBDA
* detector number: 25
Mimics an ophyd.areaDetector object without subclassing it.
"""

Expand Down
3 changes: 3 additions & 0 deletions profile_bluesky/startup/instrument/devices/rigaku_ufxc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"""
Rigaku Ultra-Fast X-ray Camera area detector (LabView, not EPICS)
* detector name: RIGAKU500K_NoGap
* detector number: 46
Mimics an ophyd.areaDetector object without subclassing it.
"""

Expand Down
13 changes: 13 additions & 0 deletions profile_bluesky/startup/instrument/plans/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

"""
local, custom Bluesky plans (scans) and other functions
"""

from .alignment import *
from .flux_calculations import *
from .lineup_tweak import *
from .move_diodes import *
from .move_sample import *
from .pv_registers import *
from .shutters import *
from .xpcs_acquire import *
36 changes: 36 additions & 0 deletions profile_bluesky/startup/instrument/plans/alignment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

"""
Command-line functions for alignment - NOT bluesky plans
"""

__all__ = """
pre_align
post_align
""".strip()

from instrument.session_logs import logger
logger.info(__file__)

from ..devices import actuator_flux, att, default_counter, pind4
from ..devices import shutter, shutter_mode

def pre_align():
"""
This is not a plan and so we should use it in command line, which means no use of RE
"""
global att, default_counter
shutter.close()
shutter_mode.put("1UFXC")
actuator_flux.put("IN")
att.put(0)
default_counter = pind4

def post_align():
"""
This is not a plan and so we should use it in command line, which means no use of RE
"""
global att
shutter.close()
#shutter_mode.put("1UFXC")
actuator_flux.put("OUT")
att.put(0) #att will be defined to att1 or att2
106 changes: 106 additions & 0 deletions profile_bluesky/startup/instrument/plans/flux_calculations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@

"""
Bluesky plans to calculate beam flux
"""

__all__ = """
calc_flux
flux
flux_params
print_flux_params
""".strip()

from instrument.session_logs import logger
logger.info(__file__)

from ..devices import monochromator, preamps
from .functions import taylor_series
import math
import pyRestTable


def calc_flux(cps, params, pin_diode):
"""
calculate the number of photons/s from diode count rate
"""
gain = preamps.gains[pin_diode.name]
amps = (cps/params["CtpV"])*gain
photons = amps/(1.60218e-19*params["N_elec"]*params["Abs_frac"])
return photons


def flux(pin_diode, count_rate):
"""
print the flux on the named photodiode
"""
gain = preamps.gains[pin_diode.name]
params = flux_params(pin_diode)
print_flux_params(params, pin_diode)

t = pyRestTable.Table()
t.addLabel("term")
t.addLabel("value")
t.addRow(("diode", pin_diode.name))
t.addRow(("count rate, cps", count_rate))
t.addRow(("photo current, A", count_rate/params["CtpV"]*gain))
v = calc_flux(count_rate, params, pin_diode)
t.addRow(("flux, ph/s", f"{v:8.3g}"))

logger.info(f"\n{t}")

return v


def flux_params(_counter):
"""
dictionary of computed conversion constants
"""
result = {}
result["Amps_per_Volt"] = preamps.gains[_counter.name] # amplifier gain
result["CtpV"] = 1e5 # voltage/frequency conversion
Length = result["fluxLength"] = 0.04 #
result["Element"] = "Si"
keV = monochromator.energy.position
result["Ephot"] = keV * 1000

if result["Element"] == "Ar":
N_elec = result["Ephot"]/26.4
v = taylor_series(keV, [-2.78262, .782515, -.0379763, 1.04293e-3, -1.14407e-5])
Elength = math.exp(v)
elif result["Element"] == "N2":
N_elec = result["Ephot"]/34.8
v = taylor_series(keV, [6.17639, -6.90647e-1, 2.44039e-2, -.453977e-3, 0.033084e-4])
Elength = 1.0/(math.exp(v)*1.165e-3)
elif result["Element"] == "He":
N_elec = result["Ephot"]/41.3
v = taylor_series(keV, [15.4707, 0.972059, -0.0487191, 0.00134312, -1.46011e-05])
Elength = math.exp(v) / 1e4
elif result["Element"] == "Si":
N_elec = result["Ephot"]/3.62 # 3.62: electron-hole pair production energy
Elength = 61e-4*pow((7.65/keV),-3)
Length = 400e-4
else:
raise KeyError(f"flux params not defined for '{result['Element']}'")

result["N_elec"] = N_elec
result["Elength"] = Elength
result["Abs_frac"] = (1-math.exp(-Length/Elength))

return result


def print_flux_params(params, counter):
gain = preamps.gains[counter.name]
element = params['Element']
t = pyRestTable.Table()
t.addLabel("term")
t.addLabel("value")
t.addLabel("units")
t.addRow(("detector", counter.name, ""))
t.addRow(("Amps/Volt", gain, "A/V"))
t.addRow(("counts/Volt", params["CtpV"], ""))
t.addRow(("Length", params["fluxLength"], "cm"))
t.addRow(("Element", element, ""))
t.addRow(("Ephot", params["Ephot"], "eV"))
t.addRow((f"{element} detector", params["Elength"], "cm"))
logger.info(f"\n{t}")
21 changes: 21 additions & 0 deletions profile_bluesky/startup/instrument/plans/functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

"""
various functions
"""

__all__ = ["taylor_series", ]

from instrument.session_logs import logger
logger.info(__file__)


def taylor_series(x, coefficients):
"""
compute a Taylor series expansion at x
a0 + x*(a1 + x*(a2+x*0)
"""
v = 0
for a in reversed(coefficients):
v = v*x + a
return v
145 changes: 145 additions & 0 deletions profile_bluesky/startup/instrument/plans/lineup_tweak.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@

"""
Bluesky plans to move the sample stage and actuators
"""

__all__ = """
lineup
tw
""".strip()

from instrument.session_logs import logger
logger.info(__file__)

from bluesky import plans as bp
from bluesky import plan_stubs as bps
from ..framework import bec
from ophyd.scaler import ScalerCH, ScalerChannel
import pyRestTable


def tw(counter, motor, delta):
"""
maximize a positioner using a motor and reading a scaler channel
In SPEC, the `tw` command initiates an interactive session.
Automate that here, if possible.
"""
# Usage: tw mot [mot2 ...] delta [delta2 ...] [count_time]
raise NotImplementedError("Need to write the Bluesky tw() plan")


def lineup(counter, axis, minus, plus, npts, time_s=0.1, peak_factor=4, width_factor=0.8,_md={}):
"""
lineup and center a given axis, relative to current position
PARAMETERS
counter : Signal or scaler channel object
detector or Signal to be maximized
axis : movable
Signal or EpicsMotor to use for alignment, the independent axis
minus : float
first point of scan at this offset from starting position
plus : float
last point of scan at this offset from starting position
npts : int
number of data points in the scan
time_s : float (default: 0.1)
count time per step
peak_factor : float (default: 4)
maximum must be greater than 'peak_factor'*minimum
width_factor : float (default: 0.8)
fwhm must be less than 'width_factor'*plot_range
EXAMPLE:
RE(lineup(diode, foemirror.theta, -30, 30, 30, 1.0))
"""
# first, determine if counter is part of a ScalerCH device
scaler = None
obj = counter.parent
if isinstance(counter.parent, ScalerChannel):
if hasattr(obj, "parent") and obj.parent is not None:
obj = obj.parent
if hasattr(obj, "parent") and isinstance(obj.parent, ScalerCH):
scaler = obj.parent

if scaler is not None:
old_sigs = scaler.stage_sigs
scaler.stage_sigs["preset_time"] = time_s
scaler.select_channels([counter.name])

if hasattr(axis, "position"):
old_position = axis.position
else:
old_position = axis.get()

def peak_analysis():
aligned = False
if counter.name in bec.peaks["cen"]:
table = pyRestTable.Table()
table.labels = ("key", "value")
table.addRow(("axis", axis.name))
table.addRow(("detector", counter.name))
table.addRow(("starting position", old_position))
for key in bec.peaks.ATTRS:
table.addRow((key, bec.peaks[key][counter.name]))
logger.info(f"alignment scan results:\n{table}")

lo = bec.peaks["min"][counter.name][-1] # [-1] means detector
hi = bec.peaks["max"][counter.name][-1] # [0] means axis
fwhm = bec.peaks["fwhm"][counter.name]
final = bec.peaks["cen"][counter.name]

ps = list(bec._peak_stats.values())[0][counter.name] # PeakStats object
# get the X data range as received by PeakStats
x_range = abs(max(ps.x_data) - min(ps.x_data))

if final is None:
logger.error(f"centroid is None")
final = old_position
elif fwhm is None:
logger.error(f"FWHM is None")
final = old_position
elif hi < peak_factor*lo:
logger.error(f"no clear peak: {hi} < {peak_factor}*{lo}")
final = old_position
elif fwhm > width_factor*x_range:
logger.error(f"FWHM too large: {fwhm} > {width_factor}*{x_range}")
final = old_position
else:
aligned = True

logger.info(f"moving {axis.name} to {final} (aligned: {aligned})")
yield from bps.mv(axis, final)
else:
logger.error("no statistical analysis of scan peak!")
yield from bps.null()

# too sneaky? We're modifying this structure locally
bec.peaks.aligned = aligned
bec.peaks.ATTRS = ('com', 'cen', 'max', 'min', 'fwhm')

md = dict(_md)
md["purpose"] = "alignment"
yield from bp.rel_scan([counter], axis, minus, plus, npts, md=md)
yield from peak_analysis()

if bec.peaks.aligned:
# again, tweak axis to maximize
md["purpose"] = "alignment - fine"
fwhm = bec.peaks["fwhm"][counter.name]
yield from bp.rel_scan([counter], axis, -fwhm, fwhm, npts, md=md)
yield from peak_analysis()

if scaler is not None:
scaler.select_channels()
scaler.stage_sigs = old_sigs
Loading

0 comments on commit b9ac478

Please sign in to comment.