Datarecorder - PIPython
Author: Physik Instrumente (PI) GmbH & Co. KG

# Data recorder

A PI device has one or more record tables that can be filled with float values (i.e. numbers). The typical workflow is as following.

- Set the record rate.
- Configure the data to be recorded.
- Configure the trigger event that starts the recorder.
- Perform the action that should be recorded.
- Wait until the action is finished.
- Wait until the data has been recorded.
- Start reading out the data from the controller.
- Wait until all data has been read out from the device.
- Process the data.

Please find an according sample in `samples/datarecorder.py`.

## Prepare the data recorder

### Set the record rate

With the GCS command `RTR` you can set the record rate in multiples of the device specific servo loop time. Hence the higher the RTR rate is the slower the data is recorded. For your convenience the Datarecorder() class takes a record rate in Hertz and seconds, too.

    from pipython import GCSDevice
    from pipython import datarectools, pitools
    pidevice = GCSDevice()
    pidevice.InterfaceSetupDlg()
    ...
    drec = datarectools.Datarecorder(pidevice)
    # drec.samplerate = 1  # servo cycles
    # drec.sampletime = 1E-5  # seconds
    drec.samplefrequ = 1000  # Hertz
    print('data recorder rate: {:d} servo cycles'.format(drec.samplerate))
    print('data recorder rate: {:.g} seconds'.format(drec.sampletime))
    print('data recorder rate: {:.2f} Hertz'.format(drec.samplefrequ))

### Set the record time

By default the entire data recorder memory is used to record the data. You can reduce the number of points with the `numvalues` property. Or you set the time to record in seconds with `rectime` which adjusts `numvalues` accordingly. The `rectimemax` property will use the entire data recorder memory and will adjust the `samplerate` accordingly.

Further on `pidevice` is referred as instance of pipython.GCSDevice and `drec` is referred as instance of pipython.datarectools.Datarecorder.

### Configure data recorder

With the GCS command `DRC` you can configure which measurement (the record option) of which record source (e.g. an axis or channel) is recorded in a specified record table.

There is an enumeration `pipython.datarectools.RecordOptions` available.

The function `Datarecorder.record()` takes one or more axes, one or more record options and one or more trigger options. If you call it with a single axis and several record options it will take the given axis for all recordings. And vice versa it will take a single record option for several given axes. When you omit the axes argument all connected axes are taken. If you omit the record option then `RecordOptions.ACTUAL_POSITION_2` is taken. It will return a list of record table IDs where the desired data will be stored. See the example code below.

### Configure the trigger event

With the GCS command `DRT` you can configure when the recording will start, e.g. immediately or with the next command that changes a position, i.e. that makes a motion.

There is an enumeration `pipython.datarectools.TriggerSources` available.

If you call the function Datarecorder.record() with a single value as trigger option it will use the `DRT` command with the record table "0". If it is called with a list as argument it will use `DRT` with the according record table IDs. See the controller user manual which is the appropriate way for your device. If you omit the trigger options argument then `TriggerSources.NEXT_COMMAND_WITH_RESET_2` is taken. If `TriggerSources.NEXT_COMMAND_WITH_RESET_2` is used then the error check will be disabled automatically.

The following sample will configure two recordings of one axis triggered by a position changing command and arm the data recorder.

    drec.options = (datarectools.RecordOptions.ACTUAL_POSITION_2,
                    datarectools.RecordOptions.COMMANDED_POSITION_1)
    drec.sources = pidevice.axes[0]
    drec.trigsources = datarectools.TriggerSources.POSITION_CHANGING_COMMAND_1
    drec.arm()

The following sample will configure two recordings of two axes.

    drec.options = datarectools.RecordOptions.ACTUAL_POSITION_2
    drec.sources = ['X', 'Y']
    drec.trigsources = datarectools.TriggerSources.POSITION_CHANGING_COMMAND_1
    drec.arm()

The following sample will configure four recordings of two analog inputs and two measurements regarding axis X.

    drec.sources = [1, 2, 'X', 'X']
    drec.options = [datarectools.RecordOptions.ANALOG_INPUT_81,
                    datarectools.RecordOptions.ANALOG_INPUT_81,
                    datarectools.RecordOptions.MOTOR_OUTPUT_73,
                    datarectools.RecordOptions.COMMANDED_POSITION_1]
    drec.trigsources = datarectools.TriggerSources.POSITION_CHANGING_COMMAND_1
    drec.arm()

### Get options from string

If you read the desired options for example from an INI file there are helper functions getrecopt() and gettrigsources() to translate a descriptive string into the according option value. The following example will set `trigsources` to `POSITION_CHANGING_COMMAND_1`.

    readout = 'pos_chg_cmd'  # e.g. from an INI file
    drec.trigsources = datarectools.gettrigsources(readout)

The first exact or abbreviated match of all parts of an option (i.e. in the example "POSITION", "CHANGING" and "COMMAND") is returned. The descriptive string is case insensitive. Use "_" as separator. The trailing number in the option name is not required for a match. Abbreviations must start with the first letter of the according part of an option.

## Get the data

### Wait for the motion to finish

After you started the triggering event (for example a motion) you can wait until the motion has finished with the "wait" helper functions in `pipython.pitools`.

    pidevice.MVR(axis, 1.0)
    pitools.waitontarget(pidevice, axis)

### Get the data recorder data

Finally you will read out the recorded data from the device with the GCS command `qDRR`. This command returns immediately with the GCS header containing information about the data recorder data. Then it starts a background task that keeps on storing the data still coming from the controller in an internal buffer. Check the current state of this buffer with the `bufstate` property. It will turn `True` when the task has finshed. Prior to that it is a float value in the range 0..1 indicating the progress of the data transfer. Hence end a loop with `while bufstate is not True` and not with `while not bufstate`.

    header = pidevice.qDRR(rectables, offset, numvalues)
    while pidevice.bufstate is not True:
        print('read data {:.1f}%...'.format(pidevice.bufstate * 100))
        sleep(0.1)

Remember that the task running in the background will lock the communication to the device. Hence your application indeed is able to continue after the `qDRR` command but when you try to communicate to the device during data readout this will result in a deadlock! To prevent this always check the `GCSDevice.locked` property.

For your convenience you can use Datarecorder.getdata() instead. It will wait until the desired data has been recorded and will then return the header and the data as two-dimensional list where the first index indicates the record table and the second index indicates the value in this record table.

    header, data = drec.getdata()

### Process data

The sample below shows how to use the header and the data from a recording of two tables to create a plot. (This requires matplotlib.)

        timescale = [header['SAMPLE_TIME'] * i for i in range(len(data[0]))]
        pyplot.plot(timescale, data[0], color='red')
        pyplot.plot(timescale, data[1], color='blue')
        pyplot.xlabel('time (s)')
        pyplot.ylabel(', '.join((header['NAME0'], header['NAME1'])))
        pyplot.title('Datarecorder data over time')
        pyplot.grid(True)
        pyplot.show()

If you are used to NumPy you can easily convert the datarecorder data into a NumPy array.

    import numpy as np
    ...
    header, data = drec.getdata()
    npdata = np.array(data)

## Appendix

### Wait for recording

To wait for the data recording to finish you can use `wait()` and `read()` instead of `getdata()`

    drec.arm()
    drec.wait()
    # recording is now finished
    header, data = drec.read()

« Previous Next »