Blikpils
Blikpils

Reputation: 37

Numpy unpack uint16 to 1-5-5-5 bit chunks

I am trying to convert a binary string to an image in Python using numpy but i am having a hard time finding a good way of approaching it with a an unconventional bit distribution (as far as my knowledge goes).

these are the specifics of how and what to convert. 16-bit texture tile (256*256). Each bitu16 represents a pixel whose colour is of the form ARGB, MSB-to-LSB: 1-bit transparency
5-bit red channel 5-bit green channel 5-bit blue channel

Numpy does not really have support for anything 1 bit or 5 bit. I tried setting up a np.dtype with different argb channels but without any luck. unpackbits seems to not work on uint16 so in that case i might have to split it in 2 uint8's


dt = np.dtype([('a', np.bool_), ('r', np.half), ('g', np.half), ('b', np.half)])

data = data.read(131072)

dataFromBuffer = np.frombuffer(data, dtype=dt)
img  = dataFromBuffer.reshape(256, 256)


Upvotes: 1

Views: 464

Answers (2)

Paul Panzer
Paul Panzer

Reputation: 53079

Here is how to make your approach work:

# make small example
x = np.random.randint(0,1<<16,size=(5,5),dtype=np.uint16)

# set up dtype
dt = np.dtype([*zip('argb',(bool,*3*(np.uint8,)))])

# bit of bit twiddling

def unpack_argb(x):
    out = np.empty(x.shape,dt)
    for i,ch in enumerate(reversed('argb')):
        out[ch] = (x>>(5*i))&31
    return out

def pack_argb(x):
    out = x['a'].astype(np.uint16)
    for ch in 'rgb':
        out <<= 5
        out += x[ch]&31
    return out

# check round trip
np.all(x == pack_argb(unpack_argb(x)))
# True

Update:

def argb16_to_rgba32(x):
    out = np.empty(x.shape+(4,),np.uint8)
    out[...,3] = (x>>8)&0x80
    out[...,0] = (x>>7)&0xf8
    out[...,1] = (x>>2)&0xf8
    out[...,2] = (x<<3)&0xf8
    return out

def rgba32_to_argb16(x):
    x16 = x.astype(np.uint16)&0xf8
    out = (x16[...,3]&0x80)<<8
    out += x16[...,0]<<7
    out += x16[...,1]<<2
    out += x16[...,2]>>3
    return out

Upvotes: 1

DaveIdito
DaveIdito

Reputation: 1606

You are right regarding the lack of bit numpy bit level support in this case. A high-level (yet functional) approach for handling the bits can be done as follows:

image_16_bit = 123 # A 16bit integer.

bits = '{:016b}'.format(image_16_bit) 

transparency = int(bits[0], 2)
red_channel = int(bits[1:6], 2)
green_channel = int(bits[6:11], 2)
blue_channel = int(bits[11:], 2)

print(transparency, red_channel, green_channel, blue_channel) # 0 0 3 27

You may run this over all the ints, and then collect the individual channel values. Finally you may cast it to a numpy array to get your image as a numpy array.

Upvotes: 2

Related Questions