PBC / pbc3_stream.py
EgeEken's picture
perf: batch PBC3 grid bit I/O
fc99650
Raw
History Blame Contribute Delete
4.74 kB
"""PBC3 bitstream framing, separate from the image algorithm."""
import numpy as np
import pbc3_ops as ops
from pbc3_types import BitReader, BitWriter
def write_grid(bw: BitWriter, values, bitcount: int) -> None:
"""Writes a flat grid of palette indices."""
acc = bw.acc
nbits = bw.nbits
data = bw.data
bitmask = (1 << bitcount) - 1
for value in values:
acc = (acc << bitcount) | (int(value) & bitmask)
nbits += bitcount
while nbits >= 8:
shift = nbits - 8
data.append((acc >> shift) & 255)
acc &= (1 << shift) - 1
nbits = shift
bw.acc = acc
bw.nbits = nbits
def read_grid(br: BitReader, count: int, bitcount: int) -> np.ndarray:
"""Reads a flat grid of palette indices."""
values = np.empty(count, dtype=np.uint16)
data = br.data
data_len = len(data)
data_index = br.i
acc = br.acc
nbits = br.nbits
bitmask = (1 << bitcount) - 1
for i in range(count):
while nbits < bitcount:
if data_index >= data_len:
raise EOFError("bitstream ended early")
acc = (acc << 8) | data[data_index]
data_index += 1
nbits += 8
shift = nbits - bitcount
values[i] = (acc >> shift) & bitmask
acc &= (1 << shift) - 1
nbits = shift
br.i = data_index
br.acc = acc
br.nbits = nbits
return values
def write_header(bw, w, h, original_w, original_h, downsampled, color_id, channels,
channel_bits, positive_bias, has_alpha, patch_count, base_values, warmup=None):
"""Writes the image-level stream header."""
bw.write(int(downsampled), 1)
if downsampled:
bw.write(original_w, 16)
bw.write(original_h, 16)
bw.write(w, 16)
bw.write(h, 16)
bw.write(color_id, 2)
bw.write(channels, 8)
bw.write(channel_bits, 4)
bw.write(int(positive_bias), 1)
bw.write(int(has_alpha), 1)
bw.write(patch_count, 32)
for base in base_values:
bw.write(base, 8)
bw.write(int(warmup is not None), 1)
if warmup is not None:
warm_w, warm_h, warm_split = warmup
bw.write(warm_w, 16)
bw.write(warm_h, 16)
bw.write(warm_split, 32)
def read_header(br, color_space_names):
"""Reads the image-level stream header."""
downsampled = bool(br.read(1))
original_w = br.read(16) if downsampled else None
original_h = br.read(16) if downsampled else None
w = br.read(16)
h = br.read(16)
color_id = br.read(2)
channels = br.read(8)
channel_bits = br.read(4)
positive_bias = bool(br.read(1))
has_alpha = bool(br.read(1))
patch_count = br.read(32)
base_values = [br.read(8) for _ in range(channels)]
warmup_on = bool(br.read(1))
warm_w = warm_h = warmup_split = None
if warmup_on:
warm_w = br.read(16)
warm_h = br.read(16)
warmup_split = br.read(32)
return (
downsampled, original_w, original_h, w, h, color_space_names[color_id], channels,
channel_bits, positive_bias, has_alpha, patch_count, base_values, warmup_on, warm_w,
warm_h, warmup_split,
)
def write_patch(bw, patch, channel_bits: int) -> None:
"""Writes one generated-palette patch."""
bw.write(patch["channel"], channel_bits)
bw.write(patch["x"], 16)
bw.write(patch["y"], 16)
bw.write(patch["w"], 16)
bw.write(patch["h"], 16)
bw.write(0, 1)
mask = patch["mask"]
bw.write(len(mask), 10)
for bit in mask:
bw.write(bit, 1)
bw.write(patch["neg"], 8)
bw.write(patch["pos"], 8)
bw.write(patch["max_bitcount"], 4)
bw.write(patch["cell_size"], 16)
write_grid(bw, patch["indices"].ravel().astype(np.int64), patch["bitcount"])
def read_patch(br, channel_bits: int, positive_bias: bool = True):
"""Reads one generated-palette patch and returns its decoded values."""
channel = br.read(channel_bits)
x, y = br.read(16), br.read(16)
w, h = br.read(16), br.read(16)
if br.read(1) != 0:
raise ValueError("explicit palette patches were removed in PBC3 3.0 release cleanup")
mask = [br.read(1) for _ in range(br.read(10))]
negative_max, positive_max = br.read(8), br.read(8)
max_bitcount = br.read(4)
bitcount = ops.resolve_palette_bitcount(mask, max_bitcount, negative_max, positive_max, positive_bias)
palette = ops.palette_generator(mask, max_bitcount, negative_max, positive_max, positive_bias)
cell_size = br.read(16)
gw, gh = ops.ceil_div(w, cell_size), ops.ceil_div(h, cell_size)
indices = read_grid(br, gh * gw, bitcount).reshape(gh, gw)
return channel, x, y, w, h, cell_size, palette[indices], bitcount