// WIKI

arducam-evk-gui

Tkinter GUI for the Arducam EVK SDK, sensor data-driven.

Published on Updated on Python 3.8+ · TkinterOpenCV · NumPyArducam EVK SDK 1.0.7MIT
Code on GitHub ↗

Operational guide to arducam-evk-gui: what it is, how to install it (Windows and Linux), how the interface is used, how to work on the sensor registers and how to build the .cfg configuration file and the JSON profile. The project is a fork of ArduCam_EVK_Demo that unifies the individual command-line examples into a single Tkinter GUI, sensor-agnostic and data-driven. Content derived from the README, GUIcamera/README.md, python/README.md, the doc/ guides and the real sensors/_template.json and sensors/mira220.json files; the authoritative reference remains the GitHub repository.

What it is and what it is for

The upstream ArduCAM/ArduCam_EVK_Demo project ships separate standalone examples (open/init/start/stop, controls, mode switching, register dump, single-register read/write, snapshots, sequence saving, SDK logging). This fork merges them into one Tkinter window and adds a sensor-profile-driven register editor.

It started as a bench tool for bringing up the ams OSRAM Mira220 image sensor: validating sensor and optics, tuning exposure/gain, inspecting and writing registers, capturing frames and sequences for analysis — all from a GUI, without writing host code. MIT-licensed for the fork additions.

Data-driven architecture: configs/ + sensors/

The GUI is decoupled from the sensor. A configs/ folder holds the .cfg files (one sensor mode per file: resolution, bit depth, format, I²C); a sensors/ folder holds the .json profiles (the human-readable register map). At start-up the GUI scans configs/, groups the .cfg files by sensor and binds the JSON profile by comparing (case-insensitive) the JSON match field with the .cfg TYPE (or the file name).

Practical consequence: adding or documenting a sensor requires no Python code changes — you only work on a .cfg and a .json. config_manager.py acts as the .cfg parser and .json profile loader.

Installation — Windows

To use the GUI you only need the USB driver and the Python dependencies (the C/C++ environment — Visual Studio, CMake, OpenCV, EVK SDK variables — is only needed to compile the bundled C/C++ demos). Download the Arducam driver package (install_USB_Camera_Drivers.zip), unzip it and run install_driver.bat.

Once the driver is installed, continue with the Python dependencies and launching the GUI (next section).

Installation — Linux

Install OpenCV and the Arducam SDK from the official PPA: sudo apt install libopencv-dev cmake; then add the Arducam APT key and list and install arducam-config-parser-dev and arducam-evk-sdk-dev.

Set up the udev rules: download configure_udev_rules.sh, make it executable (sudo chmod +x) and run it, so the EVK device is accessible without root privileges. Then continue with the Python dependencies.

Python dependencies and launch

Requirements: Python 3.8 or newer. Runtime dependencies are in requirements.txt: ArducamEvkSDK (the SDK Python bindings), Pillow, opencv-python, numpy, arducam-rgbir-remosaic.

Launch: cd GUIcamera; pip install -r requirements.txt; python launch_gui.py. launch_gui.py adds the GUIcamera/ folder to sys.path and calls gui.main(); running python gui.py from inside the folder also works.

The interface screens

Preview — live canvas with adaptive 16→8 bit shifting (no min-max stretch), live stats, snapshot and sequence saving, log-scale histogram (X = pixel value, Y = count) and a software auto-exposure loop with optional auto-gain.

Controls — auto-generated sliders for every control declared in the .cfg (e.g. Framerate, Exp(us)); the exposure cap follows the framerate. Sensor regs — per-register editor driven by the active JSON profile: a toolbar to switch profile, filter, group and toggle combined multi-byte / per-byte view; each row has an “i” button that opens a datasheet-style help dialog.

Registers (raw) — raw address/value picker with 8 bit checkboxes and a live description from the active profile. Advanced — USB transfer size, sensor mode switch, SDK log level and log file. Info — dump_info() output and an in-app SDK log console.

Typical workflow

  1. Connect the EVK over USB and start the GUI.
  2. Pick the sensor (from the discovered .cfg files), then the specific configuration, then the USB device.
  3. Open then Start: capture runs on a background thread and the Tk main loop updates the preview without blocking on USB I/O.
  4. Adjust exposure/gain from Controls, inspect/write registers from the Sensor regs tab (with datasheet help), save snapshots or sequences from Preview. For high-bandwidth modes (12-bit at full resolution) increase the USB transfer size in the Advanced tab between open() and start().

The .cfg configuration file

The .cfg describes one sensor mode in the Arducam SDK format. Main fields: CFG_MODE (0 = parameters from the UI / 1 = use this file as a script); TYPE (sensor identifier — the key that ties it to the JSON profile); VID (Vendor ID); SIZE (width, height); BIT_WIDTH (bit depth).

FORMAT (image format): 0 = RAW (sub-codes 0=RG, 1=GR, 2=GB, 3=BG), 1 = RGB565 (0=RGB, 1=BGR), 2 = YUV422 (0=YUYV, 1=YVYU, 2=UYVY, 3=VYUY), 3 = JPG. I2C_MODE (I²C address/data width): 0 = 8-bit addr/8-bit data, 1 = 8/16, 2 = 16/8, 3 = 16/16. I2C_ADDR (sensor I²C address, 8-bit incl. R/W).

Example (Mira220, 16-bit addr / 8-bit data, RAW): CFG_MODE = 0 · TYPE = Mira220 · SIZE = 640, 480 · BIT_WIDTH = 12 · FORMAT = 4, 0 · I2C_MODE = 2 · I2C_ADDR = 0xA8. The .cfg file name is what the GUI shows in the configuration selector.

Building the sensor JSON profile

A profile (sensors/<name>.json) has the top-level keys: name, description, match (a list of strings compared against the .cfg TYPE/file name, case-insensitive), guide (long-form quick-reference text shown in the UI), tabs (hints for the individual tabs) and registers (the register array). Always start from sensors/_template.json.

Each register has: group (UI grouping), name, addr (e.g. "0x0100"), kind, desc (short), help (long datasheet-style text) and, where it makes sense, lo/hi (range). Supported kinds: u8, bool, u16be, u16le, u24be, ro (read-only), enum, multi (a bit-field spread across several registers, written read-modify-write) and computed (a value computed by a formula over multiple reads, e.g. die temperature).

enum adds choices: { "Label": value }. multi adds parts: a list of { addr, reg_lsb, width, value_lsb } mapping the logical value bits onto the physical registers (LSB before MSB). A register may also declare bits: [ { lsb, width, name, desc } ] to document individual bit-fields. Example (from _template.json) — 16-bit exposure across 0x0202/0x0203 as kind "multi", and sensor state as "enum" with Standby/Streaming choices.

The real sensors/mira220.json file is the complete reference example: a register map verified against the datasheet, populated guide and tabs, rich help for each register. Copy it as a template when the sensor is similar.

Adding a new sensor

  1. Drop the .cfg file produced by the Arducam tooling into configs/ (the TYPE field is the key).
  2. Create the matching JSON profile in sensors/ starting from _template.json, making sure match contains the same TYPE value (or a case-insensitive alias).
  3. Restart the GUI: the new sensor and its configurations appear automatically in the selector. No Python code changes.

The arithmetic of exposure

The advice “start from low exposure and gain” works, but during bring-up you need to know how much time a number written into a register corresponds to, and why past a certain point that number cannot be raised. These are three calculations, all coming out of the same two sensor timing registers.

Exposure is not programmed in microseconds but in lines, and a line lasts as long as the sensor takes to read it:

Pixel clock line_length_pck frame_length_lines Line time Maximum fps
74.25 MHz 2960 2200 39.87 µs 11.4
74.25 MHz 2960 1200 39.87 µs 20.9
160.00 MHz 2960 2200 18.50 µs 24.6
74.25 MHz 1600 800 21.55 µs 58.0
48.00 MHz 1600 520 33.33 µs 57.7

With the table’s first row — typical values for a 5 MP sensor — a line lasts 39.87 µs, and from there exposure reads off directly:

Exposure (lines) Time Fraction of one frame at 30 fps
1 0.040 ms 0.1 %
10 0.399 ms 1.2 %
100 3.987 ms 12.0 %
500 19.933 ms 59.8 %
1000 39.865 ms 119.6 %
2190 87.305 ms 261.9 %

The ceiling is frame_length_lines: exposure cannot exceed the frame duration, because the first line read must have finished integrating before the next frame starts. With frame_length_lines = 2200 the maximum is therefore around 87.6 ms. And this is where bring-up’s most common surprise hides: to expose for longer you have to lengthen the frame, that is, give up frame rate.

frame_length_lines Maximum fps Maximum exposure
1200 20.9 47.76 ms
2200 11.4 87.62 ms
4400 5.7 175.33 ms

So “the image is too dark, I’ll raise the exposure” stops being an adjustment at some point and becomes a design choice: more light or more fps, not both. These are the same two registers that set the blanking and therefore the bandwidth on the CSI-2 link — the connection is explained in the MIPI CSI-2 bandwidth wiki: lengthening the frame to expose longer also lowers the link utilization.

Exposure or gain: they are not equivalent

Faced with a dark image the GUI offers two knobs, and the temptation is to treat them as interchangeable. They are not, and the difference is measured in SNR. In the regime where the dominant noise is photonic (shot noise), signal grows as the number of photons collected N and noise as √N:

Action Signal SNR (shot-noise regime)
exposure × 2 × 2 × 1.41 (+3.0 dB)
exposure × 4 × 4 × 2.00 (+6.0 dB)
exposure × 8 × 8 × 2.83 (+9.0 dB)
analogue gain × 2 × 2 × 1.00 (0 dB)
digital gain × 2 × 2 × 1.00 (0 dB)

Doubling the exposure collects twice the photons and improves SNR by a real 3 dB. Doubling the gain multiplies both the signal and the noise already there by two: the image is brighter and not one decibel better. Hence the right order during bring-up: spend all the available exposure first, then consider gain.

With one caveat that avoids the opposite mistake: analogue gain — applied before the converter — is not useless in low light. In that regime the dominant noise is no longer photonic but the read noise of the downstream chain, and amplifying before the ADC makes it less significant relative to the signal. That is why many sensors have a switchable conversion gain. Digital gain, on the other hand, never helps: it multiplies an already quantized number, and the only thing it adds is the feeling of having done something.

The 8-bit shift, in numbers

The troubleshooting section says the 16→08 bit conversion applies no min-max stretch, “so a very dark frame stays dark”. By how much:

12-bit code % of scale After the shift, 8-bit % of scale
50 1.2 % 3 1.2 %
200 4.9 % 12 4.7 %
512 12.5 % 32 12.5 %
1024 25.0 % 64 25.1 %
2048 50.0 % 128 50.2 %
4095 100.0 % 255 100.0 %

A code of 200 out of 4095 — 4.9 % of scale, a distinctly underexposed frame — becomes 12 out of 255, that is exactly the same 4.7 %: the shift does not change the percentage, so it cannot make visible what is not there. What it loses is resolution: 16 levels at 12 bits collapse into one level at 8, and in a shadow region where the useful signal occupies a hundred 12-bit codes, six remain. That is why exposure is judged on the 12-bit histogram, on a logarithmic scale, and not on the 8-bit preview.

Troubleshooting

  • The EVK does not appear in the device selector. On Windows, check that install_driver.bat was run; on Linux, that the udev rules are installed and the device is accessible without root. Without the driver or correct permissions the SDK does not enumerate the camera.
  • The sensor does not show up among the configurations. The JSON profile’s match field must match (case-insensitive) the .cfg TYPE. Run python -m config_manager to see what the GUI bound, without opening the interface.
  • Black or saturated preview. Start from low exposure and gain and raise gradually; the 16→8 bit shifting applies no min-max stretch, so a very dark frame stays dark. The log-scale histogram helps you see where the energy sits.
  • Dropped frames or low throughput in high-bandwidth modes. Increase the USB transfer size in the Advanced tab between open() and start(): at 12-bit full resolution the default may not be enough.

Diagnostics and licence

Diagnostics: python -m config_manager prints the discovered configurations and profiles without opening the GUI (useful to check matches and paths); application and SDK messages are visible in the Info/Log tab, with log level and log file configurable in the Advanced tab.

Licence: the fork additions (the GUI) are released under MIT. The upstream ArduCam_EVK_Demo code (c/, c++/, python/ folders) and the bundled Arducam SDK (evk_sdk/, version 1.0.7, redistributed as-is) retain their original Arducam licence terms.

Vendor

Arducam — Simplifying embedded vision for all.

Last updated: · Spotted an error or stale figure? Let us know

← Back to the Wiki index

A similar project?

Acoustics, embedded, calculation tools: if you have a related use case, let’s talk.