Как упаковать произвольную битовую последовательность в Python?

Я хочу закодировать / сжать некоторые двоичные данные изображения в виде последовательности битов. (Эта последовательность, как правило, имеет длину, которая не вписывается аккуратно в целый ряд стандартных целочисленных типов.)

Как я могу сделать это, не теряя места? (Я понимаю, что, если последовательность битов не имеет "хорошей" длины, всегда будет небольшое количество [< 1 байт] оставшегося пространства в самом конце.)

Я полагаю, что для каждого символа, который я хочу кодировать, потребуется не более 3 битов. Есть ли в Python какие-либо встроенные инструменты для такой работы?

4 ответа

Решение

Там нет ничего очень удобного, но есть сторонние модули, такие как bitstring и bitarray, которые предназначены для этого.

from bitstring import BitArray
s = BitArray('0b11011')
s += '0b100'
s += 'uint:5=9'
s += [0, 1, 1, 0, 1]
...
s.tobytes()

Чтобы объединить последовательность 3-битных чисел (то есть диапазон 0->7), вы можете использовать

>>> symbols = [0, 4, 5, 3, 1, 1, 7, 6, 5, 2, 6, 2]
>>> BitArray().join(BitArray(uint=x, length=3) for x in symbols)
BitArray('0x12b27eab2')
>>> _.tobytes()
'\x12\xb2~\xab '

Некоторые связанные вопросы:

Вы пробовали просто сжать всю последовательность с помощью bz2? Если последовательность длинная, вы должны использовать bz2.BZ2Compressor, чтобы разрешить частичную обработку, в противном случае используйте bz2.compress для всего этого. Сжатие, вероятно, не будет идеальным, но обычно будет очень близко при работе с разреженными данными.

надеюсь, это поможет.

Поскольку у вас есть отображение от символов к 3-битной строке, bitarray делает хорошую работу по кодированию и декодированию списков символов в и из массивов битов:

from bitarray import bitarray
from random import choice

symbols = {
    '0' : bitarray('000'),
    'a' : bitarray('001'),
    'b' : bitarray('010'),
    'c' : bitarray('011'),
    'd' : bitarray('100'),
    'e' : bitarray('101'),
    'f' : bitarray('110'),
    'g' : bitarray('111'),
}

seedstring = ''.join(choice(symbols.keys()) for _ in range(40))

# construct bitarray using symbol->bitarray mapping
ba = bitarray()
ba.encode(symbols, seedstring)

print seedstring
print ba

# what does bitarray look like internally?
ba_string = ba.tostring()
print repr(ba_string)
print len(ba_string)

Печать:

egb0dbebccde0gfdfbc0d0ccfcg0acgg0ccfga00
bitarray('10111101000010001010101001101110010100... etc.
'\xbd\x08\xaanQ\xf4\xc9\x88\x1b\xcf\x82\xff\r\xee@'
15

Вы можете видеть, что этот список из 40 символов (120 бит) кодируется в 15-байтовый битовый массив.

Обратите внимание, что это точно решит вашу проблему, но это приведет к преобразованию списка целых чисел в упакованную байтовую строку. Каждое целое число в списке должно занимать только указанное количество бит:

      from typing import List

def pack_bytes(data:List[int], bit_width:int) -> bytes:
    """Convert list if integers to packed byte string.
    Each integer should only consume the specified number of bits
    """

    packed_bytes = bytearray()
    buffer = 0
    bits_buffered = 0
    for sample in data:
        bit_mask = 0x01
        for i in range(bit_width):
            bit = (sample & bit_mask) >> i
            bit_mask <<= 1
            buffer |= (bit << bits_buffered)
            bits_buffered += 1

            if bits_buffered == 8:
                packed_bytes.append(buffer)
                buffer = 0
                bits_buffered = 0
    if bits_buffered != 0:
        packed_bytes.append(buffer)

    return packed_bytes

Пример:

      data = [0, 1, 2, 3, 4, 5, 6]

packed_data = pack_bytes(data, bit_width=4)
print(' '.join(f'0x{x:02X}' for x in packed_data))
# 0x10 0x32 0x54 0x06
Другие вопросы по тегам