Sector 32-ID TXM¶
Note
This code is under active development and may change at any time. If you encounter issues, or documentation bugs, please submit an issue.
This page describes the features of the
aps_32id.txm.NanoTXM
class, and a few supporting
classes. The NanoTXM
class is the primary
interface for controlling the Transmission X-ray Microscope (TXM) at
beamline 32-ID-C. There is also a complimentary
aps_32id.txm.MicroTXM
.
A core design goal is to keep as much of the complexity in the
NanoTXM
class, which leaves the scripts to
handle high-level details. It also allows for better unit and
integration testing. When creating new scripts, it is recommended to
put all interactions to process variables (PVs) in methods of the
NanoTXM
class. This may seem silly for
single PV situations, but will make the script more readable. A
hypothetical example:
# Not readable at all: what does that address even mean??
PV('32idcTXM:SG_RdCntr:reset.PROC').put(1, wait=True)
# Better, but still not great: what does 1 mean?
txm.Reset_Theta = 1
# Best, even though this method definition would only have one line
txm.reset_theta()
Sector 32-ID Configuration¶
The following configuration options can be set in the
beamline_config.conf
file under the [32-ID-C]
heading:
- has_permit (yes|no)
- If
has_permit
is “no”, then the script will not attempt to change the X-ray source, monochromator, shutters, etc. This allows testing of scripts while the B-hutch is operating without risking interferance. - stage (NanoTXM|MicroCT)
- Controls which stage/optics/shutters to use for manipulating the
sample.
MicroCT
uses the front stage andNanoTXM
uses the rear stage. - zone_plate_drn (int)
- The width, in nm, of the outermost zone of the zone-plate of the zone-plate (\(\Delta r_n\)).
- zone_plate_diameter (int)
- The total diameter, in µm, of the zone-plate.
- zone_plate_drift_x (float)
- Adjusts the zoneplate x position by this amount for every unit change of zoneplate z. When properly set, this will keep the sample centered when changing energy.
- zone_plate_drift_y (float)
- Adjusts the zoneplate y position by this amount for every unit change of zoneplate z. When properly set, this will keep the sample centered when changing energy.
[32-ID-C]
has_permit = True
# Either NanoTXM or MicroCT
stage = NanoTXM
# Correct for zoneplate drift when changing energies
zone_plate_drn = 50
zone_plate_diameter = 180
zone_plate_drift_x = 0.
zone_plate_drift_y = 0.
Internally, these options are parsed in
aps_32id.txm.txm_config()
using the standard library’s
configparser package. To make scripts easier to read, it is best to
read the configuration only inside methods of
NanoTXM
(or subclasses). The configuration
values can be read in the following manner:
cfg = txm_config()['32-ID-C']
zp_drn = cfg.getfloat('zone_plate_drn')
has_permit = cfg.getboolean('has_permit')
Stopping Scans Gracefully¶
When a scan script ends, we want the instrument to return to a
usable configuration even if an exception occurred. Using the
run_scan()
context manager, this
becomes easy. At the start of the context, this manager saves certain
configuration details about instrument; when exiting the context for
any reason the configuration is restored, the CCD is set to
“continuous mode”, and any extra logging is stopped:
import logging
import aps_32id
txm = aps_32id.NanoTXM()
with txm.run_scan():
# Setup the microscope as desired
txm.setup_hdf_writer()
txm.start_logging(logging.INFO)
txm.setup_detector()
# Now do experiment stuff
Process Variables¶
Process variables (PVs), though the pyepics
package are the way
python controls the actuators and sensors of the instrument. There are
two ways to interact with process variables:
- The
pv_put()
method on aNanoTXM
object. - A
TxmPV
descriptor on theNanoTXM
class (or subclass).
The second option handles more of the underlying complexity, but
understanding it requires a good grasp of the first option. The
NanoTXM.pv_put()
method is a
wrapper around pyepics.PV.put()
, and accepts similar
arguments:
# These two sets of statements have the same effect
# Using the epics PV class
epics.PV('my_great_pv').put(1, wait=True)
# Using the TXM method
my_txm = TXM()
my_txm.pv_put('my_great_pv', 1, wait=True)
Behind the scenes, there is some extra magic so the txm can coordinate PVs that work together.
Manually supplying the PV name and options each time is cumbersome, so
the TxmPV
descriptor can be used to
define PVs at import time. Set instances of the
TxmPV
class as attributes on a
NanoTXM
subclass, then assign and retrieve
values directly from the attribute:
from aps_32id import NanoTXM
from scanlib import TxmPV
class ExampleTXM(NanoTXM):
# Define a PV during import time
my_awesome_pv = TxmPV('cryptic:pv:string', dtype=float, wait=True)
# More PV definitions go here
# Now we can use the PV attribute of the txm class
my_txm = ExampleTXM()
# Retrieve the current value
# Equivalent to ``float(epics.PV('cryptic:pv:string').get())``
curr_value = my_txm.my_awesome_pv
# Set the value
# Equivalent of epics.PV('cryptic:pv:string').put(2.718, wait=True)
my_txm.my_awesome_pv = 2.718
The advantage here is that boilerplate, such as type-casting and
blocking, can be defined once then forgotten. This approach also lets
you define PVs that should not be changed when the B-hutch is being
operated, by passing permit_required=True
to the TxmPV
constructor. More on this below.
Waiting on Process Variables¶
Sometimes it is necessary to set one PV then wait on a different PV to
confirm the new value. The tomo.32id.txm.TXM.wait_pv()
method
will poll a specified PV until it reaches its target value. It accepts
the attribute name of a PV, not the actual PV name itself. It may be
necessary to use the wait=False
argument on the first PV to avoid
blocking forever:
class MyTXM(TXM):
motor_pv = TxmPV('txm:motorA', wait=False
sensor_pv = TxmPV('txm:sensorA')
txm = MyTXM()
# First set the actuator to the desired value
new_position = 3.
txm.motor_pv = new_position
# This will block until the sensor reaches the target value
tmx.wait_pv('sensor_pv', new_position)
Waiting on Multiple Process Variables¶
Warning
This feature should be considered experimental. It has been know to break during some operations, most notably setting the undulator gap.
By default, calling the pv_put()
method
will block execution until the put
call has completed. This means
that setting several PVs becomes a serial operation. This is the
safest approach but is unnecessary in many situations. For example,
setting the x, y and z stage positions can be done simultaneously. You
can always use wait=False
and handle the blocking yourself,
however this is not always straight-forward and may involve messy
callbacks. Using the wait_pvs()
context
manager takes care of this. Any PVs that are set inside the context
will move immediately; if block=True
(default) the manager will
wait for them to finish before leaving the context.
txm = TXM()
# These move one at a time
txm.Motor_SampleY = 5
txm.Motor_SampleZ = 3
# This waits while both motors move simultaneously
with txm.wait_pvs():
txm.Motor_SampleY = 8
txm.Motor_SampleZ = 9
# These move in the background without blocking
with txm.wait_pvs(block=False):
txm.Motor_SampleY = 3
txm.Motor_SampleZ = 12
This table describes whether if and when a process variable blocks the execution of python code and waits for the PV to achieve its target value:
Context manager | pv_put(wait=True) |
pv_put(wait=False) |
---|---|---|
No context | Blocks now | No blocking |
TXM().wait_pvs |
Blocks later | No blocking |
TXM().wait_pvs(block=False) |
No blocking | No blocking |
Locking Shutter Permits¶
Sometimes it’s desireable to test portions of the codebase during
downtime while the B-hutch is operating. In order to do this, however,
it’s important to ensure that the shutters, undulator and
monochromator are not changed. Using the
TxmPV
descriptors makes this easy: any
PV’s that should not be changed can be given the
permit_required=True
argument to their constructor:
class MyTXM(TXM):
SHUTTER_OPEN = 1
my_shutter = TxmPV('32idc:shutter', permit_required=True)
def open_shutter(self):
"""Opens the shutter so we can science!"""
self.my_shutter = self.SHUTTER_OPEN
# This will not do anything
my_txm = MyTXM()
my_txm.open_shutter()
# This will control the PV as expected
my_txm = MyTXM(has_permit=True)
my_txm.open_shutter()
Note
There is no check that the C-hutch actually has permission to
open the shutter, etc. It’s controlled only by the has_permit
argument given to the TXM
constructor. Please be considerate.
Fast Shutter¶
The instrument is equipped with a “fast shutter” than protects the
specimen from excessive X-ray exposure. Calling
enable_fast_shutter()
turns this feature
on. If using the run_scan()
context manager
(recommended), the fast shutter is automatically disabled, otherwise
the disable_fast_shutter()
method should be
called to return to normal behavior. The fast shutter respects
exposure_time()
attribute.