DevWare Python
- 1 Overview
- 2 Tutorial
- 3 Ini File Equivalents
- 4 ApBase Module Reference
- 4.1 Camera Class
- 4.2 Sensor Class
- 4.3 Chip Class
- 4.4 RegisterSet Class
- 4.5 Register Class
- 4.6 Bitfield Class
- 4.7 Console Class
- 4.8 Apbase Methods
- 4.9 Apbase Constants
- 5 DevWare Module Reference
- 5.1 DevWare Methods
- 6 Custom Register Dialog Definition via "Register Tabs" preset
- 6.1 Command Syntax
Overview
DevWare embeds Python as its built-in scripting language.
Within a Python script, access to the sensor and DevWare features is through built-in extension modules called Devware and ApBase (Note that this used to be Midlib which has been replaced by ApBase). Functions that correspond to ApBase API calls are in the ApBase module. (ApBase is the library that does all I/O to the sensor including register access and acquiring images. See the ApBase API document.) All other functions are in the DevWare module. The reason for the split is so scripts can work in other ApBase-based applications besides DevWare as long as they don't depend on DevWare-specific features.
To enable Python in DevWareX, you will be prompted with the URL for the 64-bit version.
Here isthe direct link:
https://www.python.org/ftp/python/3.8.2/python-3.8.2-amd64.exe
You can manage the environment as well; see page Python Environment Selection for details.
There is a simple Python console window in DevWare. On the main window select the menu View -> Python Console. The output from executing Python code appears in the upper text box. Commands for immediate execution may be entered in the lower edit box. The command entry window keeps a command history; use the up and down arrows to select a previous command.
The usual way to run Python code within DevWare is to put the code in an ini file and execute it like any other ini file preset. It's also possible to send code to the Python interpreter through the COM interface and from a plug-in.
To run embedded Python from the COM interface, use the RunPython() method. See DevWare COM Interface.
To run embedded Python from a plug-in, use the RunPython() callback. See the Plugin Dialog Development Guide.
Both INI file and Python samples can be found in the installation folder under "samples\Python_Samples" with a description found here.
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 to 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 apbase 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 demo3 (if you are using a DEMO3 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. You can use "Hidden:" as with other presets if desired. 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
If there is a preset called [Python: Unload], DevWare will automatically execute it when the corresponding ini file window is closed, or when DevWare exits. This can be used to clean up background threads.
DevWare only executes [Python:] when the ini file is opened in a Presets window. In scripts you have to execute [Python:] explicitly if the file isn't in a window, or in applications outside of DevWare.
You can load an ini file into a DevWare Presets window with:
devware.open_ini_file(inifilepath)
This will run [Python:].
DevWare automatically creates a variable called __IniFileName. When the execution of an ini file preset begins, DevWare sets the contents of this variable to the full path to the ini file. This is useful for accessing other files in the same directory as the current ini file. Note that when execution of the preset ends, the variable reverts back to its previous value, so the variable is not meaningful if accessed from a background thread that remains running after the preset that launched it is finished. If the ini file path is needed in a thread function, then it must be passed to the thread function as a parameter.
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.
ApBase Module Overview
ApBase is the library that all DevSuite applications use to access the camera devices. The apbase 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 an apbase.Register object connected to the desired register and then get or set its value attribute. To illustrate, this example explicitly calls the apbase.Register constructor to create a Register object, and then writes to the register.
blacklevel = apbase.Register(symbol='DATA_PEDESTAL').value # read
apbase.Register(symbol='DATA_PEDESTAL').value = 42 # write
By default the apbase.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 apbase.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)
Note: ApBase Caches Register Values
ApBase 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(X)
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_orien.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.
apbase .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. 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 apbase.num_cameras – 1. The parameter is optional, and defaults to the currently selected camera.
>>> apbase.Camera().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.
>>> apbase.Camera().sensor.name
'A-3132SOC'
>>> apbase.Camera().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 = apbase.Camera().sensor.reg
Capturing an Image from the Camera
To capture an image use the grab_frame() method of the Camera. Grab_frame() returns a 2-tuple with the status code and a bytearray of the image data.
statuscode, img = apbase.Camera().grab_frame()
A status code of apbase.MI_CAMERA_SUCCESS (0) indicates success. Even if the status code is not 0, the bytearray will hold whatever data was received.
The colorpipe() method can convert the raw image to RGB. Colorpipe() returns a 4-tuple with the RGB image as a bytearray, and the width, height and bit-depth of the RGB data. This is the same conversion used in the DevWare display window.
rgb,w,h,d = apbase.colorpipe(img)
Other Apbase Methods
The getstate() and setstate() methods get and set ColorPipe variables. This is the same as the STATE= ini file command.
apbase.setstate(‘Contrast’, 25)
The image() method sets the image dimensions and type. Normally ApBase can determine these automatically, 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 apbase module also implements a Console object which is linked to sys.stdin, sys.stdout and sys.stderr. In DevWare, 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.
Devware Module Overview
The devware module consists mainly of methods based on the COM port methods. See also DevWare COM Interface.
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):
apbase.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.
If there is a preset called [Python: Unload], DevWare will automatically execute it when the corresponding ini file window is closed, or when DevWare exits. This can be used to clean up background threads.
Subprocesses
If the use of a Subprocess is necessary instead of a thread, "sys.prefix" can be used to ensure that the same version of Python is being used.
An example;
import os
import sys
import subprocess
command = [os.path.join(sys.prefix, 'python.exe'), '–version']
output = subprocess.check_output(command, shell=True)
print(output)
Results in:
>>> b'Python 3.9.4\r\n'
Ini File Equivalents
This section lists ini file commands with the Python code that does the same or similar thing. It will illustrate how to do common operations.
On the left are example ini file commands, and the equivalent Python is on the right. Keep in mind that Python is much more flexible than the ini commands. Where an ini command required a numerical constant, Python will allow any expression. Python has much more flow control, functions with parameters, user-defined classes and so on.
This is the recommended way to write a register, variable or bitfield.
INI File Commands | Python Script |
|---|---|
FIELD_WR= OUTPUT_FORMAT_TEST, 256 | reg.OUTPUT_FORMAT_TEST = 256 |
FIELD_WR= SEQ_CAP_MODE, VIDEO, 1 | reg.SEQ_CAP_MODE.VIDEO = 1 |
If the data type is defined, you can use floating point values and Python will translate to the right value accordingly.This example has fixed8 (signed with 8 fraction bits) CCM settings.
INI File Commands | Python Script |
|---|---|
FIELD_WR=CAM1_AWB_CCM_L_0, 0x0180 | reg. CAM1_AWB_CCM_L_0 = 1.5 |
FIELD_WR=CAM1_AWB_CCM_L_1, 0xFF7A | reg. CAM1_AWB_CCM_L_1 = -0.523 |
FIELD_WR=CAM1_AWB_CCM_L_2, 0x0018 | reg. CAM1_AWB_CCM_L_2 = 0.094 |
On older sensors that have multiple pages of 8-bit addressed registers and an ADDR_SPACE_SEL register, the reg.reg() method takes two parameters. On newer sensors with a 16-bit register address space reg.reg() takes one parameter. It is an error to use the wrong number of parameters.
INI File Commands | Python Script |
|---|---|
REG= 0x3084, 0x2409 (no register page) | reg.reg(0x3084).value = 0x2409 |
REG= 1, 0x08, 0x0158 (with register page) | reg.reg(1, 0x08).value = 0x0158 |
Same as REG= but use the bitfield() method to construct a Bitfield object.
INI File Commands | Python Script |
|---|---|
BITFIELD= 0x001A, 0x0200, 1 | reg.reg(0x001A).bitfield(0x0200).value = 1 |
BITFIELD= 0, 0x23, 0x100, 1 | reg.reg(0, 0x23).bitfield(0x100).value = 1 |
Note that the size of the variable is determined by the sensor data file. If the variable is not defined then an optional size in bits can be passed as the third argument. It defaults to 16 bits wide.
INI File Commands | Python Script |
|---|---|
VAR= 18, 0x126, 1 | reg.var(18, 0x126).value = 1 |
VAR8 forces an 8-bit data transaction. The Python is not quite equivalent because if the variable is defined in the sensor data file, Python will use the data size in the sensor data file and ignore the data size parameter.
INI File Commands | Python Script |
|---|---|
VAR8=15, 0x0C, 0 | reg.var(15, 0x0C, 8).value = 0 |
This writes to MCU memory using the physical, rather than logical, addressing. SFR8 is analogous to VAR8. If the SOC supports multiple physical regions, then the physical region must be the first parameter. After the address you can specify an optional size in bits. The default is 16 bits.
INI File Commands | Python Script |
|---|---|
SFR= 0x1078, 0xFFFF | reg.sfr(0x1078).value = 0xFFFF |
If you set a register to a list of values Python does a burst write. This only works for simple registers and SFR (physically addressed) memory.
INI File Commands | Python Script |
|---|---|
REG_BURST= 0x990, 0x3C3C, 0x3C3C, 0x3C5F, 0x4F30, 0xED08, 0xBD61, 0xD5CE, 0x4CD | reg.reg(0x990).value = [0x3C3C, 0x3C3C, 0x3C5F, 0x4F30, 0xED08, 0xBD61, 0xD5CE, 0x4CD] |
The SERIAL_REG command is for I2C write to any arbitrary device address with specified register address size and data size. There is equivalent Python syntax by explicitly calling the apbase.Register constructor, but there may be a better way.
If there is a chip data file loaded for the chip, you can access the register by its name through the RegisterSet object for the chip
chipreg = apbase.Camera().chip(1).reg
chipreg.CONTROL_REG = 0x123
If the register is on the DEMO2 or MIDES FPGA you can use the predefined demo2 or mides RegisterSet variables. If there is no chip data file (or even if there is), you can create a Python variable for the Register object and use that.
ctrl_reg = apbase.Register(ship_addr=0x38, addr=0x04, addr_size=8, data_size=16)
ctrl_reg.value = 0x18
You can also do bitfield writes in Python using the bitfield() method of the Register class, or by named bitfield.
INI File Commands | Python Script |
|---|---|
SERIAL_REG= 0x64, 0x25, 0x18, 8:16 | apbase.Register(ship_addr=0x64, addr=0x25, addr_size=8, data_size=16).value = 0x18 |
Delay in milliseconds.
INI File Commands | Python Script |
|---|---|
DELAY= 50 | apbase.delay(50) |
Set a DevWare internal variable. You can also set so-called DevWare Option variables with devware.setoption().
INI File Commands | Python Script |
|---|---|
STATE= Display Zoom Percent, 50 | apbase.setstate('Display Zoom Percent', 50) |
Execute a file preset from an ini file. If no filename is given, the devware.load_preset() method will load from the currently executing ini file, or if that can't be determined, the main ini file. Also consider defining a function entirely in Python instead of using an ini preset.
INI File Commands | Python Script |
|---|---|
LOAD= AWB Settings | apbase.load_preset('AWB Settings') |
LOAD= mysettings.ini, New CCM | devware.load_preset('mysettings.ini', 'New CCM') |
Present a multiple choice to the user. Python is more flexible in that it can do anything following the user's selection, it's not limited to executing a preset.
INI File Commands | Python Script |
|---|---|
PROMPT= "Choose Mode", "Parallel", LOAD=Init Parallel, "MIPI", LOAD=Init MIPI |