1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
|
# -*- coding: utf-8 -*-
#cython: embedsignature=True, language_level=3
## This is for optimisation
#cython: boundscheck=False, wraparound=False, cdivision=True, initializedcheck=False,
## This is for developping:
##cython: profile=True, warn.undeclared=True, warn.unused=True, warn.unused_result=False, warn.unused_arg=True
#
# Project: X-ray image reader
# https://github.com/silx-kit/fabio
#
# Copyright (C) 2015 European Synchrotron Radiation Facility, Grenoble, France
#
# Principal author: Jérôme Kieffer (Jerome.Kieffer@ESRF.eu)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
New Cython version of mar345_IO for preparing the migration to Python3
Compressor & decompressor for "pack" algorithm by JPA, binding to CCP4 libraries
those libraries are re-implemented in Cython.
Known bugs:
-----------
The precomp/postdec part need to be performed operation in int16 and exports
uint16. Some calculation are overflowing, this is needed to reproduce the
original implementation which is buggy
"""
__authors__ = ["Jerome Kieffer", "Gael Goret", "Thomas Vincent"]
__contact__ = "jerome.kieffer@esrf.eu"
__license__ = "MIT"
__copyright__ = "2012-2020, European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "11/12/2020"
from libc.stdint cimport int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t
import cython
import numpy
import os
import tempfile
import logging
logger = logging.getLogger(__name__)
ctypedef fused any_int_t:
int8_t
int16_t
int32_t
int64_t
# Few constants:
cdef:
uint8_t *CCP4_PCK_BIT_COUNT = [0, 4, 5, 6, 7, 8, 16, 32]
uint8_t *CCP4_BITSIZE = [0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0,
6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7]
unsigned int CCP4_PCK_BLOCK_HEADER_LENGTH = 6
cdef extern from "ccp4_pack.h":
void* mar345_read_data_string(char *instream, int ocount, int dim1, int dim2) nogil
void pack_wordimage_c(short int*, int , int , char*) nogil
void* ccp4_unpack_string (void *, void *, size_t, size_t, size_t) nogil
void* ccp4_unpack_v2_string(void *, void *, size_t, size_t, size_t) nogil
cdef int PACK_SIZE_HIGH = 8
@cython.boundscheck(False)
def compress_pck(image not None, bint use_CCP4=False):
"""
:param image: numpy array as input
:param use_CCP4: use the former LGPL implementation provided by CCP4
:return: binary stream
"""
cdef:
uint32_t size, dim0, dim1, i, j
int fd, ret
char* name
int16_t[::1] data
int32_t[::1] raw
bytes output
assert image.ndim == 2, "Input image shape is 2D"
size = image.size
dim0 = image.shape[0]
dim1 = image.shape[1]
data = numpy.ascontiguousarray(image.ravel(), dtype=numpy.int16)
if use_CCP4:
(fd, fname) = tempfile.mkstemp()
fname = fname.encode("ASCII")
name = <char*> fname
with nogil:
pack_wordimage_c(<short int *> &data[0], dim1, dim0, name)
with open(name, "rb") as f:
f.seek(0)
output = f.read()
os.close(fd)
os.unlink(fname)
else:
output = ("\nCCP4 packed image, X: %04d, Y: %04d\n" % (dim1, dim0)).encode("ASCII")
raw = precomp(data, dim1)
cont = pack_image(raw, False)
output += cont.get().tobytes()
return output
@cython.boundscheck(False)
@cython.cdivision(True)
def uncompress_pck(bytes raw not None, dim1=None, dim2=None, overflowPix=None, version=None, normal_start=None, swap_needed=None, bint use_CCP4=False):
"""
Unpack a mar345 compressed image
:param raw: input string (bytes in python3)
:param dim1,dim2: optional parameters size
:param overflowPix: optional parameters: number of overflowed pixels
:param version: PCK version 1 or 2
:param normal_start: position of the normal value section (can be auto-guessed)
:param swap_needed: set to True when reading data from a foreign endianness (little on big or big on little)
:return: ndarray of 2D with the right size
"""
cdef:
int cdimx, cdimy, chigh, cversion, records, normal_offset, lenkey, i, stop, idx, value
uint32_t[:, ::1] data
uint8_t[::1] instream
int32_t[::1] unpacked
int32_t[:, ::1] overflow_data # handles overflows
void* out
end = None
key1 = b"CCP4 packed image, X: "
key2 = b"CCP4 packed image V2, X: "
if (dim1 is None) or (dim2 is None) or \
(version not in [1, 2]) or \
(version is None) or \
(normal_start is None):
start = raw.find(key2)
key = key2
cversion = 2
if start == -1:
start = raw.find(key1)
key = key1
cversion = 1
lenkey = len(key)
start = raw.index(key) + lenkey
sizes = raw[start:start + 13]
cdimx = < int > int(sizes[:4])
cdimy = < int > int(sizes[13-4:13])
normal_offset = start + 13
else:
cdimx = < int > dim1
cdimy = < int > dim2
cversion = <int> version
normal_offset = <int> normal_start
if cversion == 1:
lenkey = len(key1)
else:
lenkey = len(key2)
if cversion not in [1, 2]:
raise RuntimeError("Cannot determine the compression scheme for PCK compression (either version 1 or 2)")
if (overflowPix is None) and (overflowPix is not False):
end = raw.find("END OF HEADER")
start = raw[:end].find("HIGH")
hiLine = raw[start:end]
hiLine = hiLine.split("\n")[0]
word = hiLine.split()
if len(word) > 1:
chigh = int(word[1])
else:
logger.warning("Error while looking for overflowed pixels in line %s", hiLine.strip())
chigh = 0
else:
chigh = < int > overflowPix
instream = numpy.frombuffer(raw[normal_offset:].lstrip(), dtype=numpy.uint8).copy()
if use_CCP4:
data = numpy.empty((cdimy, cdimx), dtype=numpy.uint32)
with nogil:
################################################################################
# rely to whichever version of ccp4_unpack is appropriate
################################################################################
if cversion == 1:
ccp4_unpack_string(&data[0, 0], &instream[0], cdimx, cdimy, 0)
else:
# cversion == 2:
ccp4_unpack_v2_string(&data[0, 0], &instream[0], cdimx, cdimy, 0)
else:
# There is a bug in the mar345 implementation which performs arithmetics
# of post-decompression in 16bits integers and overflows with large values
unpacked = unpack_pck(instream, cdimx, cdimy).get1d()
data = numpy.ascontiguousarray(postdec(unpacked, cdimx), numpy.uint32).reshape((cdimy, cdimx))
if chigh > 0:
################################################################################
# handle overflows: Each record is 8 overflow of 2x32bits integers
################################################################################
records = (chigh + PACK_SIZE_HIGH - 1) // PACK_SIZE_HIGH
stop = normal_offset - lenkey - 14
odata = numpy.frombuffer(raw[stop - 64 * records: stop], dtype=numpy.int32).copy()
if swap_needed:
odata.byteswap(True)
overflow_data = odata.reshape((-1, 2))
for i in range(overflow_data.shape[0]):
idx = overflow_data[i, 0] - 1 # indexes are even values (-1 because 1 based counting)
value = overflow_data[i, 1] # values are odd values
if (idx >= 0) and (idx < cdimx * cdimy):
data[idx // cdimx, idx % cdimx] = <uint32_t> value
return numpy.asarray(data)
################################################################################
# Re-Implementation of the pck compression/decompression
################################################################################
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
@cython.initializedcheck(False)
cpdef inline int32_t[::1] precomp(int16_t[::1] img, uint32_t width):
"""Pre-compression by subtracting the average value of the four neighbours
Actually it looks a bit more complicated:
* there comes the +2 from ?
* the first element remains untouched
* elements of the first line (+ first of second) use only former element
JPA, the original author wrote:
Compression is achieved by first calculating the differences between every
pixel and the truncated value of four of its neighbours. For example:
the difference for a pixel at img[x, y] is:
comp[y, x] = img[y, x] - (img[y-1, x-1] + img[y-1, x] + img[y-1, x+1] + img[y, x-1]) / 4
This part implements overlows of int16 as the reference implementation is buggy
"""
cdef:
int size, i
int32_t[::1] comp
int16_t last, cur, im0, im1, im2
size = img.size
comp = numpy.zeros(size, dtype=numpy.int32)
with nogil:
# First pixel
comp[0] = last = im0 = img[0]
im1 = img[1]
im2 = img[2]
# First line (+ 1 pixel)
for i in range(1, width + 1):
cur = img[i]
comp[i] = cur - last
last = cur
# Rest of the image
for i in range(width + 1, size):
cur = img[i]
comp[i] = <int16_t> (cur - (last + im0 + im1 + im2 + 2) // 4)
last = cur
im0 = im1
im1 = im2
im2 = img[i - width + 2]
return comp
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
@cython.initializedcheck(False)
cpdef inline uint32_t[::1] postdec(int32_t[::1] comp, int width):
"""Post decompression by adding the average value of the four neighbours
Actually it looks a bit more complicated:
* there comes the +2 from ?
* the first element remains untouched
* elements of the first line (+ fist of second) use only former element
JPA, the original author wrote:
Compression is achieved by first calculating the differences between every
pixel and the truncated value of four of its neighbours. For example:
the difference for a pixel at img[x, y] is:
comp[y, x] = img[y, x] - (img[y-1, x-1] + img[y-1, x] + img[y-1, x+1] + img[y, x-1]) / 4
This part implementes overlows of int16 as the reference implementation is bugged
"""
cdef:
int size, i
uint32_t[::1] img
int16_t last, cur, fl0, fl1, fl2
size = comp.size
img = numpy.zeros(size, dtype=numpy.uint32)
with nogil:
# First pixel
last = comp[0]
img[0] = last
# First line (+ 1 pixel)
for i in range(1, width + 1):
img[i] = cur = comp[i] + last
last = cur
# Rest of the image: not parallel in this case
fl0 = img[0]
fl1 = img[1]
fl2 = img[2]
for i in range(width + 1, size):
# overflow expected here.
cur = comp[i] + (last + fl0 + fl1 + fl2 + 2) // 4
# ensures the data is cropped at 16 bits!
img[i] = <uint16_t> cur
last = cur
fl0 = fl1
fl1 = fl2
fl2 = img[i - width + 2]
return img
################################################################################
# Re-Implementation of the pck compression stuff
################################################################################
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
@cython.initializedcheck(False)
cpdef inline int calc_nb_bits(any_int_t[::1] data, uint32_t start, uint32_t stop) nogil:
"""Calculate the number of bits needed to encode the data
:param data: input data, probably slices of a larger array
:param start: start position
:param stop: stop position
:return: the needed number of bits to store the values
Comment from JPA:
.................
Returns the number of bits necessary to encode the longword-array 'chunk'
of size 'n' The size in bits of one encoded element can be 0, 4, 5, 6, 7,
8, 16 or 32.
"""
cdef:
uint32_t size, maxsize, i, abs_data
any_int_t read_data
size = stop - start
maxsize = 0
for i in range(start, stop):
read_data = data[i]
abs_data = - read_data if read_data < 0 else read_data
if abs_data > maxsize:
maxsize = abs_data
if maxsize == 0:
return 0
elif maxsize < 8:
return size * 4
elif maxsize < 16:
return size * 5
elif maxsize < 32:
return size * 6
elif maxsize < 64:
return size * 7
elif maxsize < 128:
return size * 8
elif maxsize < 32768:
return size * 16
else:
return size * 32
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
@cython.initializedcheck(False)
def pack_image(img, bint do_precomp=True):
"""Pack an image into a binary compressed block
:param img: input image as numpy.int16
:param do_precomp: perform the subtraction to the 4 neighbours's average. False is for testing the packing only
:return: 1D array of numpy.int8
JPA wrote:
..........
Pack image 'img', containing 'x * y' WORD-sized pixels into byte-stream
"""
cdef:
uint32_t nrow, ncol, size, stream_size
int16_t[::1] input_image
int32_t[::1] raw
PackContainer container
uint32_t i, position
uint32_t nb_val_packed
uint32_t current_block_size, next_bock_size
if do_precomp:
assert len(img.shape) == 2
nrow = img.shape[0]
ncol = img.shape[1]
input_image = numpy.ascontiguousarray(img, dtype=numpy.int16).ravel()
# pre compression: subtract the average of the 4 neighbours
raw = precomp(input_image, ncol)
size = nrow * ncol
else:
raw = numpy.ascontiguousarray(img, dtype=numpy.int32).ravel()
size = raw.size
# allocation of the output buffer
container = PackContainer(size)
position = 0
while position < size:
nb_val_packed = 1
current_block_size = calc_nb_bits(raw, position, position + nb_val_packed)
while ((position + nb_val_packed) < size) and (nb_val_packed < 128):
if (position + 2 * nb_val_packed) < size:
next_bock_size = calc_nb_bits(raw, position + nb_val_packed, position + 2 * nb_val_packed)
else:
break
if 2 * max(current_block_size, next_bock_size) < (current_block_size + next_bock_size + CCP4_PCK_BLOCK_HEADER_LENGTH):
nb_val_packed *= 2
current_block_size = 2 * max(current_block_size, next_bock_size)
else:
break
container.append(raw, position, nb_val_packed, current_block_size)
position += nb_val_packed
return container
cdef class PackContainer:
cdef:
readonly uint32_t position, offset, allocated
uint8_t[::1] data
def __cinit__(self, uint32_t size=4096):
"""Constructor of the class
:param size: start size of the array
"""
self.position = 0
self.offset = 0
self.allocated = size
self.data = numpy.zeros(self.allocated, dtype=numpy.uint8)
def __dealloc__(self):
self.data = None
def get(self):
"""retrieve the populated array"""
if self.offset:
end = self.position + 1
else:
end = self.position
return numpy.asarray(self.data[:end])
cpdef append(self, int32_t[::1] data, uint32_t position, uint32_t nb_val, uint32_t block_size):
"""Append a block of data[position: position+nb_val] to the compressed
stream. Only the most significant bits are takes.
:param data: input uncompressed image as 1D array
:param position: start position of reading of the image
:param nb_val: number of value from data to pack in the block
:param block_size: number of bits for the whole block
The 6 bits header is managed here as well as the stream resizing.
"""
cdef:
uint32_t offset, index, i, bit_per_val, nb_bytes
uint64_t tmp, tostore, mask
int64_t topack
bit_per_val = block_size // nb_val
# realloc memory if needed
nb_bytes = (CCP4_PCK_BLOCK_HEADER_LENGTH + block_size + 7) // 8
if self.position + nb_bytes >= self.allocated:
self.allocated *= 2
new_stream = numpy.zeros(self.allocated, dtype=numpy.uint8)
if self.offset:
new_stream[:self.position + 1] = self.data[:self.position + 1]
else:
new_stream[:self.position] = self.data[:self.position]
self.data = new_stream
with nogil:
if self.offset == 0:
tmp = 0
else:
tmp = self.data[self.position]
# append 6 bits of header
tmp |= pack_nb_val(nb_val, bit_per_val) << self.offset
self.offset += CCP4_PCK_BLOCK_HEADER_LENGTH
self.data[self.position] = tmp & (255)
if self.offset >= 8:
self.position += 1
self.offset -= 8
self.data[self.position] = (tmp >> 8) & (255)
# now pack every value in input stream"
for i in range(nb_val):
topack = data[position + i]
mask = ((1 << (bit_per_val - 1)) - 1)
tmp = (topack & mask)
if topack < 0:
# handle the sign
tmp |= 1 << (bit_per_val - 1)
# read last position
if self.offset == 0:
tostore = 0
else:
tostore = self.data[self.position]
tostore |= tmp << self.offset
self.offset += bit_per_val
# Update the array
self.data[self.position] = tostore & (255)
while self.offset >= 8:
tostore = tostore >> 8
self.offset -= 8
self.position += 1
self.data[self.position] = tostore & (255)
cpdef inline uint8_t pack_nb_val(uint8_t nb_val, uint8_t value_size) nogil:
"""Calculate the header to be stored in 6 bits
:param nb_val: number of values to be stored: must be a power of 2 <=128
:param value_size: can be 0, 4, 5, 6, 7, 8, 16 or 32, the number of bits per value
:return: the header as an unsigned char
"""
cdef:
uint32_t value, i
value = 0
for i in range(8):
if (nb_val >> i) == 1:
value |= i
break
value |= (CCP4_BITSIZE[value_size]) << (CCP4_PCK_BLOCK_HEADER_LENGTH >> 1)
# should be 6/2 = 3
return value
################################################################################
# Re-Implementation of the pck uncompression stuff
################################################################################
cpdef UnpackContainer unpack_pck(uint8_t[::1] stream, int ncol, int nrow):
"""Unpack the raw stream and return the image
V1 only for now, V2 may be added later
:param stream: raw input stream
:param ncol: number of columns in the image (i.e width)
:param nrow: number if rows in the image (i.e. height)
:return: Container with decompressed image
"""
cdef:
uint32_t offset # Number of bit to offset in the current byte
uint32_t pos, end_pos # current position and last position of block in byte stream
uint32_t size # size of the input stream
int32_t value, next # integer values
uint32_t nb_val_packed, nb_bit_per_val, nb_bit_in_block
UnpackContainer cont # Container with unpacked data
cont = UnpackContainer(ncol, nrow)
size = stream.size
# Luckily we start at byte boundary
offset = 0
pos = 0
while pos < (size) and cont.position < (cont.size):
value = stream[pos]
if offset > (8 - CCP4_PCK_BLOCK_HEADER_LENGTH):
# wrap around
pos += 1
next = stream[pos]
value |= next << 8
value = value >> offset
offset += CCP4_PCK_BLOCK_HEADER_LENGTH - 8
elif offset == (8 - CCP4_PCK_BLOCK_HEADER_LENGTH):
# Exactly on the boundary
value = value >> offset
pos += 1
offset = 0
else:
# stay in same byte
value = value >> offset
offset += CCP4_PCK_BLOCK_HEADER_LENGTH
# we use 7 as mask: decimal value of 111
# move from offset, read 3 lsb, take the power of 2
nb_val_packed = 1 << (value & 7)
# read 3 next bits, search in LUT for the size of each element in block
nb_bit_per_val = CCP4_PCK_BIT_COUNT[(value >> 3) & 7]
if nb_bit_per_val == 0:
cont.set_zero(nb_val_packed)
else:
nb_bit_in_block = nb_bit_per_val * nb_val_packed
cont.unpack(stream, pos, offset, nb_val_packed, nb_bit_per_val)
offset += nb_bit_in_block
pos += offset // 8
offset %= 8
return cont
cdef class UnpackContainer:
cdef:
readonly uint32_t nrow, ncol, position, size
readonly int32_t[::1] data
# readonly list debug
def __cinit__(self, int ncol, int nrow):
self.nrow = nrow
self.ncol = ncol
self.size = nrow * ncol
self.data = numpy.zeros(self.size, dtype=numpy.int32)
self.position = 0
def __dealloc__(self):
self.data = None
def get(self):
"""retrieve the populated array"""
return numpy.asarray(self.data).reshape((self.nrow, self.ncol))
cpdef int32_t[::1] get1d(self):
"""retrieve the populated array"""
return self.data
cpdef set_zero(self, int number):
"set so many zeros"
self.position += number
cpdef unpack(self, uint8_t[::1] stream, uint32_t pos, uint32_t offset, uint32_t nb_value, uint32_t value_size):
"""unpack a block on data, all the same size
:param stream: input stream, already sliced
:param offset: number of bits of offset, at the begining of the stream
:param nb_value: number of values to unpack
:param value_size: number of bits of each value
"""
cdef:
uint32_t i # simple counters
int j
uint32_t new_offset # position after read
int64_t cur # value to be stored
uint64_t tmp # under contruction: needs to be unsigned
int to_read # number of bytes to read
with nogil:
cur = 0
for i in range(nb_value):
# read as many bytes as needed and unpack them to tmp variable
tmp = stream[pos] >> offset
new_offset = value_size + offset
to_read = (new_offset + 7) // 8
for j in range(1, to_read):
tmp |= (stream[pos + j]) << (8 * j - offset)
# Remove the msb of tmp to keep only the interesting values
cur = tmp & ((1 << (value_size)) - 1)
# change sign if most significant bit is 1
if cur >> (value_size - 1):
cur |= (-1) << (value_size - 1)
# Update the storage
self.data[self.position] = cur
self.position += 1
# Update the position in the array
pos = pos + new_offset // 8
offset = new_offset % 8
|