Tutorial
This is a quick tutorial on using the Python extensions in DevWare. If you are familiar with DevWare ini file commands see also the Ini File Equivalents chapter below.
Python Console Window
DevWare has a simple Python console window. Select the menu item View?Python Console to show the window.
All output directed to stdout or stderr (print() calls and error messages) goes to the top part of the window. In the bottom portion of the window you can type any command or expression for immediate execution.
The Python input() function (sys.stdin.readline()) causes a small pop-up dialog to appear where the user can enter the requested data.
Python in Ini Files
You can put Python code in an ini file, and use the Presets dialog or User Toolbar to execute it.
There are two ways embed Python in ini files. To execute single Python lines mixed with other ini file commands use a PYTHON= command.
[my preset]
REG= 0x3012, 0
PYTHON= print('set 0x3012 to 0')
Output from Python commands shows up in the Python Console window.
To execute a block of Python code of any size, create a preset with only Python in it. So the ini file interpreter knows to expect Python syntax instead of ini syntax you must have the string 'Python:' somewhere in the name of the preset.
[Python: clear some regs]
for addr in range(0x3600, 0x3800, 2):
reg.reg(addr).value = 0
Python code executes in the context of the _main_ module. DevWare automatically does an import sys, import midlib and import devware, and adds the directory of the ini file to sys.path. It creates a variable called reg that gives convenient access to sensor registers (more on this shortly), and a variable called demo2 (if you are running on a DEMO2 or DEMO2X) or mides (if you are using a MIDES board) for camera board FPGA registers. You can easily create these variables in your own modules too.
If there is a preset called only [Python:], DevWare will automatically execute it when it loads the ini file. This gives you a convenient place to put additional import statements, function definitions, or any other one-time-execute code. Example:
[Python:]
import os.path
import re
def image_size_a(w, h):
reg.PRI_A_IMAGE_WIDTH = w
reg.PRI_A_IMAGE_HEIGHT = h
reg.SEQ_CMD = 6
Aborting a running script
If a Python script is running in the main DevWare UI thread, then the DevWare user interface is not responsive while the script is running. This is the typical case when the user initiates a script in an ini file. In order to allow interrupting a script and aborting its execution, DevWare includes a mechanism which monitors the amount of time the script is running. If the script runs for more than the timeout time, DevWare will show a dialog window allowing the user to abort it. This is useful for cases where the script is running longer than intended, or is in an infinite loop.
In order for this option to work, the library signal has to be imported. This can be done either at the beginning of the individual script, or at the beginning of the default Python preset [Python:] (see section Python in Ini Files).
The following example shows a script with an infinite loop, which can be aborted by the user if the timeout has been set:
[Python: infinite loop]
import signal
i = 0
while i < 1:
i = 0
You set the timeout value by using the DevWare option variable Script Timeout: devware.setoption('Script Timeout', 10). Assigning this variable an integer number would set the timeout to the specified value in seconds. It is not possible to set a timeout in fractions of seconds.
Disable the timeout completely by setting the variable to the value 0.
Once the value has been set, it can always be modified to a new timeout value at any time during the execution of the Python script. DevWare checks whether a different timeout value has been set every 1 second.
Midlib Module Overview
Midlib is the library that all DevSuite applications use to access the camera devices. The midlib module provides classes that represent the physical devices on the camera or emulator platform, and classes to represent registers and register bitfields. I/O to the devices is performed by using methods and attributes on instances of these classes that correspond to specific chips and registers.
Accessing Registers
To read or write a register, create a midlib.Register object connected to the desired register and then get or set its value attribute. To illustrate, this example explicitly calls the midlib.Register constructor to create a Register object, and then writes to the register.
blacklevel = midlib.Register(symbol='DATA_PEDESTAL').value # read
midlib.Register(symbol='DATA_PEDESTAL').value = 42 # write
By default the midlib.Register() constructor looks for registers on the sensor. You can also specify another chip (if there's a chip data (.cdat) file loaded for it). You can alternately specify the register by address instead of name.
Explicitly calling the midlib.Register constructor is cumbersome, so for convenience there is the RegisterSet class. The RegisterSet class has helpful attributes and methods for constructing Register objects and writing to registers with short, simple syntax.
A RegisterSet object automatically has attributes corresponding to each of the registers. The predefined variable reg is the register set for the sensor, so reg.DATA_PEDESTAL implicitly calls the Register constructor to construct a Register object for the DATA_PEDESTAL register. The above example can be written as:
blacklevel = reg.DATA_PEDESTAL.value # read
reg.DATA_PEDESTAL.value = 42 # write
As further shorthand, assigning to the register symbol attribute is equivalent to assigning to the value (or float_value) attribute of the register object.
reg.DATA_PEDESTAL = 42
But understand that reg.DATA_PEDESTAL on the right hand side of an assignment or in an expression is the Register object. So
a = reg.DATA_PEDESTAL
sets a to the Register object, not the value of the register. To use the value of the register in an assignment or any expression, either use the value or float_value attribute, or explicitly cast the Register object to an int or float.
a = int(reg.DATA_PEDESTAL)
DevWare Caches Register Values
DevWare keeps the last value read or written to a register in a cache. As an optimization, the value attribute does a cached read. That is, it normally just returns the last read or written value without doing any I/O to the device. So doing a register read like this is very cheap and there is no need for you to cache register values in your Python code. But sometimes you do need to re-do I/O, for example to read a status register that changes automatically. In that case use the uncached_value attribute.
a = reg.JPEG_STATUS.uncached_value # force reading the hardware
Using Register Objects
Registers as Python objects is very useful. For example you could write generic functions that can operate on any kind of register. This function sets a register to its default value as defined in the sensor data file.
def restore_default(rr):
rr.value = rr.default
restore_default(reg.DATA_PEDESTAL)
Register Data Type and float_value
A Register object has both value and float_value attributes. The difference is the value attribute is the bits in the register as an unsigned integer, but the float_value translates from the register bits to a numerical value in floating point according to the data type of the register. If the register data type is unsigned integer, then value and float_value do the same thing, except float_value returns a Python float number. But if the data type is something else then float_value interprets the bits differently than value.
For example suppose firmware variable AWB_CCM_0 is 16-bit 2's complement signed fixed point with 8 fraction bits (the 'fixed8' data type). Then
reg.AWB_CCM_0.float_value = 1.75
assigns 0x01C0 to the variable. In an expression, casting a Register object to an int is the same as getting the value attribute, and casting a register to a float is the same as getting the float_value attribute.
x = reg.AWB_CCM_0.float_value
is equivalent to
x = float(reg.AWB_CCM_0)
There is also the uncached_float_value attribute that does an uncached read and a conversion to floating point.
Using the shorthand of setting a Register attribute of a RegisterSet object will use either the value or float_value depending on whether the right hand side is an int or float type.
reg.AWB_CCM_0 = 1.75
is equivalent to
reg.AWB_CCM_0.float_value = 1.75
and
reg.AWB_CCM_0 = 322
is equivalent to
reg.AWB_CCM_0.value = 322
Note that
reg.AWB_CCM_0 = x
may use either value or float_value depending on what data type x has when the statement is executed. That may be how you want it, but if you need it unambiguous either cast the right hand side to int or float, or use the value or float_value attribute.
reg.AWB_CCM_0 = float
or
reg.AWB_CCM_0.float_value = x
The data type is held in the datatype attribute of a register.
>>> reg.AWB_CCM_0.datatype
'fixed8'
You can also see the data type in DevWare at the bottom of the Register panel.
Register Bitfields
Similar to the Register class is the Bitfield class that represents a bitfield of a register. A Register object automatically has an attribute corresponding to each bitfield defined in the sensor data file.
reg.IMAGE_ORIENTATION.VERT_FLIP = 1 # write bitfield VERT_FLIP
vf = reg.IMAGE_ORIENTATION.VERT_FLIP.value # read bitfield
img_orien = reg.IMAGE_ORIENTATION # Register object
img_orien.VERT_FLIP = 0 # write bitfield
vf = img_oren.VERT_FLIP # Bitfield object
print(vf.value) # read bitfield (and print)
You can also construct a Bitfield object with an explicit mask if needed by calling the Bitfield constructor explicitly. The parameters to the Bitfield constructor are the parent register and the mask.
midlib.Bitfield(reg.AS_ALGO, mask=0xF0).value = 0 # write bitfield
For convience the Register class has a method called bitfield() that constructs a Bitfield object. You could write the above as
reg.AS_ALGO.bitfield(0xF0).value = 0
Bitfields can also have data types, and support both the value and float_value attributes.
Writing to a Bitfield always uses an uncached read in the read-modify-write cycle.
Iterating Over All Registers on a Device
A RegisterSet object is iterable (Python lingo). It iterates over the registers in the set. For example, this will loop over all sensor registers and print the symbolic name.
for rr in reg:
print(rr.symbol)
Register objects are also iterable and iterate over their bitfields. This prints out the names of all registers on the sensor with all bitfields of each register, as defined in the sensor data file.
for r in reg:
print(r.symbol)
for b in r:
print(' ', b.symbol)
Constructing a Register from an Address
You may prefer to specify a register by its address instead of its symbolic name, as a personal preference, or if the register is not in the sensor data file. The RegisterSet class has methods reg(), var() and sfr() to construct Register objects by address with simple syntax.
SFR in this context really just means physically, as opposed to logically, addressed memory on the SOC. Any physically addressed memory can be accessed with reg.sfr(). On SOCs that have multiple physical regions the PHY_REGION has to be specified before the address (similar to the driver number for a VAR.)
reg.reg(0x301A).value = 0x10C8
reg.var(3, 0x1E).value = 0x3F # driver 3, offset 0x1E
reg.sfr(0, 0x1400).value = 0x8044 # patch RAM, address 0x1400
Camera, Sensor and Chip Classes
The other classes are Camera, Sensor and Chip. The Camera represents the whole device and corresponds to the mi_camera_t structure of midlib. The Sensor is the sensor or SOC chip, or companion chip + sensor combination, as defined by the sensor data file. The Chip objects represent any additional chip data files that may be loaded corresponding to other chips on the I2C bus in the system, for example the DEMO2 FPGA.
The Camera constructor takes only an integer argument to indicate which camera device. Under DevWare camera 0 always corresponds to the currently selected camera. If there are other cameras attached to the system, they are accessible with indices from 1 to midlib.num_cameras – 1.
>>> midlib.Camera(0).name
'Aptina Imaging DEMO2'
The camera object has a sensor attribute that returns the Sensor object, and chip() method that returns a Chip object given a chip number.
>>> midlib.Camera(0).sensor.name
'A-3132SOC'
>>> midlib.Camera(0).chip(0).name
'DEMO2 B5'
Sensor and Chip objects have an attribute called reg that returns the RegisterSet for the device. The reg variable used in most of the examples above was created by
reg = midlib.Camera(0).sensor.reg
Capturing an Image from the Camera
To capture an image use the grab_frame() method of the Camera. Grab_frame() returns a tuple with the status code and a bytearray of the image data.
statuscode, img = midlib.Camera(0).grab_frame()
A status code of midlib.MI_CAMERA_SUCCESS (0) indicates success. Even if the status code is not 0, the bytearray will hold whatever data was received.
Devware Module Overview
The devware module consists mainly of methods based on the COM port methods. See also the DevWare COM Interface document, DevWare COM Development Guide.pdf.
The getstate() and setstate() methods get and set ColorPipe variables. This is the same as the STATE= ini file command.
devware.setstate('Contrast', 25)
The getoption(), setoption(), getoption_str(), and setoption_str() methods are the same as the COM port functions and set various DevWare modes and features.
devware.setoption('User Toolbar Show', 1)
The get_mouse_selection() and set_mouse_selection() methods get and set the Mouse Selection type and area. Get_mouse_selection() returns a tuple with 5 values.
sel, x1, y1, x2, y2 = get_mouse_selection()
sel can be 'off', 'row', 'column', 'rectangle', or 'point'. For 'row' only y1 is relevant, for 'column' only x1 is relevant, and for 'point' only x1 and y1 are relevant.
The image() method sets the image dimensions and type. Normally DevWare can determine these, this is an override useful especially during development or when a sensor is new and the DevWare support is not fully worked out yet.
The load_preset() method executes a preset from an ini file.
The upload_firmware() method loads firmware or firmware patches. Same as the COM function.
The upload_image_file() method loads a test image to a supported emulator platform. Same as the COM function.
The command() method sends a WM_COMMAND message to the DevWare main window. See the COM port document.
The stop() method stops the video streaming background threads if they are running.
The begin_access_regs() and end_access_regs() methods make accessing registers while images are streaming more efficient with drivers that don't support simultaneous register access and image capture.
The devware module also implements a Console object which is linked to sys.stdin, sys.stdout and sys.stderr. Calls to the write() method on stdout or stderr cause the data to appear in the Python Console window. Calls to stdin.readline() cause a small pop-up dialog to appear where the user can enter the data.
Threading
DevWare has three threads, the main thread which runs the user interface, and if video is streaming then two background threads, one that captures images from the camera (the camera thread) and one that does the image processing and displays the images (the display thread).
When the user runs an ini file preset, DevWare shuts down the camera and display threads. While the ini file is executing DevWare is a single-threaded application, and the script author need not worry about concurrency or other multi-threading issues.
It may be desirable to run Python code in a thread concurrently with the other DevWare threads. In that case the Python threading module can be used to create a new thread and run Python code in it. Put the code to execute as a thread in a function and call the function indirectly through threading.Thread().start(). Example:
[Python:]
import threading
def test_run(initial_it, n):
reg.COARSE_INTEGRATION_TIME = initial_it
for i in range(0, n):
midlib.delay(1000)
reg.COARSE_INTEGRATION_TIME.value += initial_it
[Python: Thread Test]
threading.Thread(target= test_run, args= (60,20)).start()
When the user invokes [Python: Thread Test] in the Presets dialog, the DevWare UI thread shuts down the camera and display threads, executes [Python: Thread Test] (which only creates a new thread and then finishes), and then resumes the camera and display threads. Simultaneously the new thread begins executing and remains alive after [Python: Thread Test] has finished. The 'test_run' thread increments the COARSE_INTEGRATION_TIME register 60, 120, 180, … 1200 every 1000ms until finished, which takes about 20 seconds. When the function exits the thread self-destructs.