9.4. Dtypes

The element type of an ndarray is its dtype. The dtype decides three things at once: how many bytes each element occupies, how the bytes are interpreted, and what range of values the array can store. Choosing the right dtype is the single biggest decision that affects RAM use on the camera.

9.4.1. The supported dtypes

numpy on the camera supports a small set of dtypes:

dtype

bytes

range

uint8

1

0 to 255

int8

1

-128 to 127

uint16

2

0 to 65 535

int16

2

-32 768 to 32 767

float

4 or 8

IEEE 754 (single or double)

bool

1

True / False

complex

8 or 16

optional, firmware-dependent

The float width depends on how the firmware was built; complex is only available on cams whose firmware reports a -c suffix in ulab.__version__. There is no int32 or int64.

Pick the type that matches the hardware that produced the data. An 8-bit ADC sample wants uint8; a 12-bit ADC sample fits in uint16; a luminance pixel from a grayscale camera fits in uint8 – saving four to eight times the RAM the default float would cost.

9.4.2. The default dtype

The default dtype of every constructor on Making arrays is float. That is rarely what the application wants when handling sensor data. Pass dtype= explicitly whenever the natural width is smaller:

sensor = np.array(samples, dtype=np.uint16)

Re-wrapping an integer array without a dtype= argument copies and converts to float, which both costs time and costs RAM. When performance matters, name the dtype.

9.4.3. The dtype of an existing array

dtype reads back the array’s dtype as a ulab.dtype instance. The single- character type code is what gets compared on firmware-conditional code paths:

a = np.array([1, 2, 3], dtype=np.uint8)
print(a.dtype)            # dtype('uint8')

9.4.4. Upcasting rules

Two arrays of different dtypes can be operands of the same operator. numpy picks the result type according to a short table:

left

right

result

uint8

int8

int16

uint8

int16

int16

uint8

uint16

uint16

int8

int16

int16

int8

uint16

uint16

uint16

int16

float

any

float

float

any

complex

complex

The last two rules promote straight to float because numpy on the camera has no 32-bit integer dtype.

When a binary operator has a Python scalar on one side, the scalar is converted to a single-element array of the smallest suitable dtype: 123 becomes a uint8 array, -1000 becomes int16, a Python float becomes float.

9.4.5. Integer overflow wraps

Operations on two arrays of the same integer dtype keep that dtype, even when the result overflows. The carry is silently dropped:

a = np.array([200, 200], dtype=np.uint8)
b = np.array([100, 100], dtype=np.uint8)
print(a + b)

Output:

array([44, 44], dtype=uint8)

The result is 300 mod 256 == 44. When an intermediate needs more range than the input dtype allows, cast first:

c = np.array(a, dtype=np.uint16) + b
# array([300, 300], dtype=uint16)

This rule applies to every integer operator – +, -, *, //, %, &, |, ^. Float arrays never overflow (they promote to infinity instead), so the cast trick is only needed in the integer case.