6. 正在写入新的可扫描文件

什么是可扫描的定义被设计成尽可能灵活。这是为了使Scanables能够尽可能广泛地表示各种概念-从硬件部件到复杂的计算。

简单地说,Scanable代表一个数字或一组数字,其行为方式类似于马达。换句话说,您可以使用getPosition()方法检索Scanable保存的数字,并且可以让Scanable执行一些操作来更改这些数字,方法是使用其中一个Move方法。您还可以使用isBusy()方法询问Scanables状态,以查看它是否准备好接受对其中一个移动方法的调用。

与可扫描对象交互的方式包括:

myScannable()

返回漂亮的打印版本的Scanable

pos myScannable

返回漂亮的打印版本的Scanable

myScannable()

返回可扫描对象的当前位置

pos myScannable 10

移动可扫描的

myScannable(10)

移动可扫描对象的替代语法

pos myScannable 10 myOtherScannable 20

同时移动两个可扫描对象

inc myScannable 1

扫描仪的相对运动

myScannable.isBusy()

如果扫描仪正在移动,则返回True(1

myScannable.waitWhileBusy()

当扫描仪完成移动时返回

myScannable.a(10)

异步移动:指示Scanable移动,然后立即返回

myScannable.ar(1)

异步相对移动

6.1. 如何创建可扫描图像

创建脚本文件。打开Scripts透视图。在Project Explorer视图中,展开“Scripts:User”文件夹以显示此文件夹的“src”子文件夹。选择“src”项,按鼠标右键并选择“新建文件”。

_images/CreateScriptFile1.png

在“New File”对话框中,输入文件名(扩展名为.py),然后按Finish。新文件将被创建并显示在编辑器视图中,以供您开始添加内容。

_images/CreateScriptFile2.png

要运行脚本,请输入使用运行命令,例如运行脚本文件MyScript.py::

>run "MyScript.py"
_images/RunScript.png

如果脚本文件包含可扫描对象(即类)的定义,则不运行脚本,而是“导入”脚本,然后发出命令创建对象的实例::

>import MyScannable.py

如果更改MyScanable的内容,则需要重新加载它::

>reload(MyScannable)

导入文件后,您可以使用下面显示的代码,通过执行文件中定义的类的构造函数来实例化文件中定义的类的实例

_images/FirstScannable.png
> simpleScannable = MyScannable.SimpleScannable("simpleScannable")

6.2. 基本模板

要编写您自己的Scanable,您需要扩展一个基类,该基类提供所需的大部分功能。然后,您需要实现与返回位置、执行操作和返回状态相关的三个方法。此最简单案例的模板如下所示:

from gda.device.scannable import ScannableMotionBase
from gda.device.scannable import ScannableUtils

class MyScannable(ScannableMotionBase):
    def __init__(self, name):
        self.setName(name)
        self.setInputNames([name])
        self.setExtraNames([])
        self.setOutputFormat(["%5.5g"])
        self.currentposition = 10 # this template scannable represents a single number
        self.iambusy = 0 # flag to hold the status of the scannable

    def getPosition(self):
        """returns the value this scannable represents."""
        return self.currentposition

    def asynchronousMoveTo(self, new_position):
        """Performs the operation this Scannable represents."""
        self.iambusy = 1
        self.currentposition = new_position
        self.iambusy = 0

    def isBusy(self):
        """Returns the status of this Scannable."""
        return self.iambusy

一旦实现了这三个方法,该类就可以像任何其他Scanable一样运行,并可在扫描和本指南中列出的其他命令中使用。构造函数稍微复杂一些,因为它需要定义Scanable包含哪些数字。下面将更详细地说明这一点。

6.3. 可扫描构造函数

基本模板中显示的Scanable构造函数设置Scanable以供使用:

# constructor
def __init__(self, name):
    self.setName(name)
    self.setInputNames([name])
    self.setExtraNames([])
    self.setOutputFormat(["%5.5g"])
    self.currentposition = 10 # this template scannable represents a single number
    self.iambusy = 0 # flag to hold the status of the scannable

在本例中,此构造函数的最后两行设置Scanable的起始值,并创建一个标志来确定其忙碌状态。这两个属性特定于该类,并在该类实现的三个方法中使用。

构造函数中的其他行是Scanable正常工作所必需的,旨在使该类能够与该类扩展的基类(即ScanableMotionBase)正确交互。这些行中的第一行设置Scanable的名称。在将类漂亮地打印到Jython终端时使用此名称。其他三行设置了三个数组,它们定义了此Scanable代表什么数字,它们被称为什么,以及在将Scanable漂亮地打印到终端时应该以什么格式打印出来:

self.setInputNames([name])
self.setExtraNames([])
self.setOutputFormat(["%5.5g"])

InputNames数组提供此Scanable的rawAchronousMoveTo预期的数组大小。InputNames数组的每个元素都是该元素的标签,用于文件头等。请注意,如果需要,此数组的大小可以为零。

ExtraNames数组的使用方式与InputNames数组类似,但列出了Scanable的rawGetPosition方法返回的数组中的其他元素,即rawGetPosition返回的数组可能大于rawAchronousMoveTo所需的数组。这允许Scanable保存和返回比移动或执行它在rawAchronousMoveTo方法中所做的任何操作所需的信息更多的信息。如果需要,此数组通常为零大小。

OutputFormat数组列出InputNames和ExtraNames数组元素的格式字符串。这在漂亮打印来自rawGetPosition的输出时使用。OutputFormat数组的大小必须与InputNames和ExtraNames数组的大小之和相同,这样Scanable才能正常工作。

6.4. 可在可扫描接口中实现的方法

有许多方法可以选择性地在Scanable类中实现,以扩展它们的功能。它们中的大多数是在分步扫描期间使用的,以防您需要在重复移动扫描对象和收集数据的正常操作之外进行额外操作。

scannable.atPointStart()

在步进扫描中的每个点移动任何扫描对象之前立即调用

scannable.atPointEnd()

在步骤扫描中的每个点收集数据后立即调用

scannable.atScanLineStart()

在多维扫描中的每个子扫描开始时调用

scannable.atScanLineEnd()

在多维扫描中的每个子扫描结束时调用

scannable.atScanStart()

在每次扫描开始时调用

scannable.atScanEnd()

在每次扫描结束时调用

scannable.stop()

在中止扫描或按下JythonTerminal上的“紧急停止”按钮时调用。这应该用于中止在Scanable的rawAchronousMoveTo方法中执行的操作。

scannable.toString()

字符串实现此项以覆盖此Scanable的漂亮打印输出

scannable.isPositionValid()

给定一个表示位置的对象,如果该位置对此可扫描对象可接受,则应返回1(true

6.5. 更多模板

以下是一些更详细的模板和注释,它们可能会有所帮助:

6.5.1. 全扫描模板

前面显示的基本模板的更完整版本:

from gda.device.scannable import ScannableMotionBase

#
# A template for all Scannable classes.
#
# The rawIsBusy, rawGetPosition, and rawAsynchronousMoveTo methods must be
# implemented.
#
# The others (commented out here) are optional depending on how your scannable
# works.
#
#******************************************************************************
# Note: the inputNames, extraNames and outputFormat arrays defined in __init__:
# Your Scannable could represent no numbers, a single number or an array of
# numbers. These arrays define what the Scannable represents.
#
# The inputNames array is a list of labels of the elements accepted by the
# new_position argument of the rawAsynchronousMoveTo method.
#
# The extraNames array is a list of labels of extra elements in case the array
# returned by rawGetPosition is larger than the array accepted by
# rawAsynchronousMoveTo.
#
# The outputFormat array is used when pretty-printing the scannable and lists
# the format to use for each element in the array returned by rawGetPosition.
# It is very important that the size of this array matches the sum of the sizes
# of inputNames and extraNames.
#
#
#******************************************************************************
#
class scannableTemplate(ScannableMotionBase):

    #
    # The constructor.
    #
    def __init__(self, name):
       self.name = name
       self.currentposition = 10 # this scannable represents a single number
       self.setInputNames([name])
       self.setExtraNames([])
       self.setOutputFormat(["%5.5g"])
       self.iambusy = 0 # flag to hold the status of the scannable

    #
    # Returns the value represented by this Scannable. This should be a number
    # or an array of numbers
    #
    def rawGetPosition(self):
        return self.currentposition

    #
    # Does the work represented by this Scannable. If this takes a long time,
    # then you should run a separate thread from within this method. See the
    # threaded_scannable_template.py script for details on how to do this.
    #
    def rawAsynchronousMoveTo(self, new_position):
        self.iambusy = 1
        self.currentposition = new_position
        self.iambusy = 0

    #
    # Returns false (0) if the action started by rawAsynchronousMoveTo has been
    # completed
    #
    def rawIsBusy(self):
        return self.iambusy

    #
    # Called when panic stop called on the system.
    #
    # def stop(self):
    #     print str(self.name), "stop called!"

    #
    # Implement this to override the pretty-print version of this Scannable
    #
    # def toString(self):
    #     return self.name

    #
    # Given an object, this returns true (1) if that object is a valid position
    # for this scannable to use in its rawAsynchronousMoveTo method
    #
    # def isPositionValid(self):
    #     return 1

    #
    # Called just before every node in a scan
    #
    # def atPointStart(self):
    #     print str(self.name),"doing atPointStart()!"

    #
    # Called after every node in a scan
    #
    # def atPointEnd(self):
    #     print str(self.name), "doing atPointEnd()!"

    #
    # In multi-dimensional scans, called before each line in the scan
    # This is still called once in single dimensional scans.
    #
    # def atScanLineStart(self):
    #     print str(self.name), "doing atScanStart()!"

    #
    # In multi-dimensional scans, called after each line in the scan
    # This is still called once in single dimensional scans.
    #
    # def atScanLineEnd(self):
    #     print str(self.name), "doing atScanEnd()!"

    #
    # Called at the start of the scan (called once in multi-dimensional scans)
    #
    # def atScanGroupStart(self):
    #     print str(self.name), "doing atGroupStart()!"

    #
    # Called at the end of the scan (called once in multi-dimensional scans)
    #
    # def atScanGroupEnd(self):
    #     print str(self.name), "doing atGroupEnd()!"

6.5.2. 螺纹扫描模板

一个Scanable,其rawAchronousMoveTo内的操作在单独的线程中运行:

from gda.device.scannable import ScannableMotionBase
from java.lang import Thread, Runnable

#
# Threaded version of the class in scannable_template in case the work performed
# within the rawAsynchronousMoveTo takes a long time.
#
class threadedScannableTemplate(ScannableMotionBase):

    #
    # The constructor.
    #
    def __init__(self, name):
        self.name = name
        self.currentposition = 10 # this scannable represents a single number
        self.setInputNames([name])
        self.setExtraNames([])
        self.setOutputFormat(["%5.5g"])
        self.iambusy = 0 # flag to hold the status of the scannable

    #
    # Returns the value represented by this Scannable. This should be a number
    # or an array of numbers
    #
    def rawGetPosition(self):
        return self.currentposition

    #
    # Creates a new moveScannableThread object to do the work and then starts it
    # in a new thread.
    #
    def rawAsynchronousMoveTo(self, new_position):
        self.iambusy = 1
        newThread = moveScannableThread(self, new_position)
        t = Thread(newThread)
        t.start()

    #
    # Returns false (0) if the action started by rawAsynchronousMoveTo has been
    # completed
    #
    def rawIsBusy(self):
        return self.iambusy


#
# An object called internally by the threadedScannableTemplate.
#
# It is very important that the busy flag is set to 0 at the end of
# the run method.
#
class moveScannableThread(Runnable):

    #
    # Constructor for this class
    #
    def __init__(self, theScannable, new_position):
        self.myScannable = theScannable
        self.target = new_position

    #
    # Does the work to move what the Scannable represents. This is run in a new
    # thread started by the line in rawAsynchronousMoveTo: t.start()
    #
    def run(self):
        print "you have asked me to move to", str(self.target)
        self.myScannable.currentposition = self.target
        self.myScannable.iambusy = 0

6.5.3. 检测器模板

对于希望充当检测器的对象:

from gda.device.detector import ScannableMotionBase

#
# A template class to use as a basis to create your own Detector objects.
#
# Detectors must work in the following manner:
#     - a call to collectData to collect some new data. Ideally this should be
#       asynchronous (i.e. the function returns immediately and the work is done
#       in a new Thread). See threaded_detector_template.py for this.
#     - repeated calls to getStatus may be made by external classes to see if
#       data is still being collected.
#     - once getStatus returns false (0) then a call to readout maybe made by
#       external classes to collect the data.
#
#
class templateDetectorClass(ScannableMotionBase):

    #
    # The constructor.
    #
    def __init__(self, name):
        self.setName(name)
        self.isCollecting = 0
        self.myData = 0

    #
    # Performs the work to collect some data. This method should not return the
    # data, but instead keep the status field up to date.
    #
    def collectData(self):
        self.isCollecting = 1
        print "you have asked me to collect data!"
        self.myData += 1
        self.isCollecting = 0

    #
    # Returns true (1) if this object is busy collecting data
    #
    def getStatus(self):
        return self.isCollecting

    #
    # Returns the last data which was collected. This should only be called when
    # getStatus returns false
    #
    def readout(self):
        return self.myData

6.5.4. 螺纹检测器模板

检测器模板的螺纹版本:

from java.lang import Thread, Runnable
from gda.device.detector import ScannableMotionBase

#
# A more complex template for detectors in which the work to perform the data
# collection is performed in its own thread.
#
#
#
class threadedTemplateDetectorClass(ScannableMotionBase):

    #
    # The constructor.
    #
    def __init__(self, name):
        self.setName(name)
        self.isCollecting = 0
        self.myData = 0

    #
    # Performs the work to collect some data. This method should not return the
    # data, but instead keep the status field up to date.
    #
    def collectData(self):
        self.isCollecting = 1
        newThread = collectDataThread(self)
        t = Thread(newThread)
        t.start()

    #
    # Returns true (1) if this object is busy collecting data
    #
    def getStatus(self):
        return self.isCollecting

    #
    # Returns the last data which was collected. This should only be called when
    # getStatus returns false
    #
    def readout(self):
        return self.myData;

#
# A method called internally by the threadedTemplateDetectorClass to collect
# the data in a separate thread.
#
# It is very important that the isCollecting flag is set to 0 at the end of
# this method.
#
class collectDataThread(Runnable):

    def __init__(self, theDetector):
        self.myDetector = theDetector

    def run(self):
        print "you have asked me to collect data!"
        self.myDetector.myData +=1
        self.myDetector.isCollecting = 0

6.5.5. 多维扫描模板

以下示例说明如何编写表示数字数组的Scanable,其中Position命令的输出比Move方法接收的输出更多:

from gda.device.scannable import ScannableMotionBase

class MultiElementTestClass(ScannableMotionBase):

    def __init__(self):
        self.setName("y")
        self.setInputNames(["first","second"])
        self.setExtraNames(["third"])
        self._position = [20,30]
        self.setOutputFormat(["%4.10f","%4.10f","%4.10f"])

    def rawGetPosition(self):
        return self._position + [10]

    def rawAsynchronousMoveTo(self,new_position):
        if len(new_position) == 2:
            self._position=new_position

    def rawIsBusy(self):
        return 0