Userdata

Picopod has full support for Picotron userdata objects, which store one-dimensional or two-dimensional binary data.

Userdata Objects

You can work with userdata objects using the Userdata class:

from picopod import Userdata

u = Userdata("u8", 10)                  # 10 values (1D), zero-filled
u = Userdata("u8", 5, 10)               # 5x10 (2D), zero-filled
u = Userdata("u8", 2, 2, [1, 2, 3, 4])  # 2x2 (2D), initialized
u = Userdata("u8", data=[1, 2, 3, 4])   # Automatic size, initialized

The userdata type can be a string like in Picotron, or you can also use the DataType enum for something more strict:

from picopod import Userdata, DataType

u = Userdata("f64", data=[0.5, 1.0, 1.5, 2.0])
u = Userdata(DataType.F64, data=[0.5, 1.0, 1.5, 2.0])

Userdata exposes datatype, width, and height attributes:

>>> u = Userdata("u8", 5, 10)
>>> u.datatype
<DataType.U8: 1>
>>> u.width
5
>>> u.height
10

(Note that height for 1D userdata will be None.)

It also supports slicing/indexing for accessing or mutating the data:

>>> u = Userdata("u8", data=[1, 2, 3, 4])
>>> u[2]
3
>>> list(u[2:])
[3, 4]
>>> u[1] = 20
>>> u[2:] = [30, 40]
>>> list(u)
[1, 20, 30, 40]

Note that for efficiency reasons, userdata is implemented using an array object. This means that slicing userdata will alias it with a memoryview rather than copy it:

>>> u = Userdata("u8", data=[1, 2, 3, 4])
>>> s = u[2:]
>>> s
<memory at ...>
>>> u[2] = 30
>>> list(s)
[30, 4]

To copy a userdata object, use copy.deepcopy():

>>> import copy
>>> u1 = Userdata("u8", data=[1, 2, 3, 4])
>>> u2 = copy.deepcopy(u1)
>>> u1[0] = 0
>>> list(u1)
[0, 2, 3, 4]
>>> list(u2)
[1, 2, 3, 4]

Binary Format

Picopod supports all of Picotron’s userdata serialization formats, including binary (“PXU”) data with compression.

Like in Picotron, userdata serializes to a Lua function call by default:

>>> pod(Userdata("u8", data=[1, 2, 3, 4]))
b'userdata("u8",4,"01020304")'

To emit binary data with an automatically-chosen compression format, call pod() with binary=True:

>>> pod(Userdata("u8", data=[1] * 1000), binary=True)
b'pxu\x00\x03(\xe8\x03\x00\x00\x04\xf1\xff\xff\xff\xdb'

Binary format only supports 8-bit and 16-bit userdata. Attempting to use it with something else will still just use text:

>>> pod(Userdata("f64", data=[1, 2, 3, 4]), binary=True)
b'userdata("f64",4,"1.0,2.0,3.0,4.0")'

To select a particular compression algorithm (or none at all), call pod() with a compression_hint:

>>> from picopod import Compression
>>> pod(Userdata("u8", data=[1, 2, 3, 4]), binary=True, compression_hint=Compression.RAW)
b'pxu\x00\x03\x10\x04\x01\x02\x03\x04'
>>> pod(Userdata("u8", data=[1, 2, 3, 4]), binary=True, compression_hint=Compression.RLE)
b'pxu\x00\x03\x80\x04\x00\x00\x01\x00\x02\x00\x03\x00\x04'
>>> pod(Userdata("u8", data=[1, 2, 3, 4]), binary=True, compression_hint=Compression.MTF)
b'pxu\x00\x03 \x04\x04\x01\x02\x03\x04'

Image Data

Userdata objects also have to_rgb() and from_rgb() methods which convert palettized image data to/from RGB. You can use these along with a library like Pillow:

from PIL import Image

# Unpod the input file, expecting 2D userdata.
data = unpod(Path("image.pod").read_bytes(), type=Userdata)
assert data.height is not None

# Use to_rgb() to easily convert the userdata to RGB values.
image = Image.frombytes("RGB", (data.width, data.height), data.to_rgb())

# Convert and save to the output file.
image.save("image.png")

Refer to the encode_image and decode_image examples for more.