Unpack 12 bit image (Mono12p)
https://www.emva.org/wp-content/uploads/GenICamPixelFormatValues.pdf
The USB3 Vision method is designated with a p. It is a 12-bit format with its bit-stream following the bit packing method illustrated in Figure 3. The first byte of the packed stream contains the eight least significant bits (lsb) of the first pixel. The third byte contains the eight most significant bits (msb) of the second pixel. The four lsb of the second byte contains four msb of the first pixel, and the rest of the second byte is packed with the four lsb of the second pixel.
@njit(fastmath=False)
def unpack_mono12p_vectorized(
packed: np.ndarray, height: int, width: int
) -> np.ndarray:
"""
Vectorized unpacking of Mono12p (12-bit packed) image from uint8 array.
Parameters
----------
packed : np.ndarray
1D uint8 array containing the packed Mono12p data.
Returns
-------
np.ndarray
1D uint16 array of unpacked 12-bit pixel values.
"""
packed = packed.ravel()
# Reshape into (n_groups, 3) where each group is 3 bytes for 2 pixels
groups = packed.reshape(-1, 3)
b0 = groups[:, 0].astype(np.uint16)
b1 = groups[:, 1].astype(np.uint16)
b2 = groups[:, 2].astype(np.uint16)
# Pixel 0: LSB from b0, MSB from lower 4 bits of b1
p0 = b0 | ((b1 & 0x0F) << 8)
# Pixel 1: LSB from b2, MSB from upper 4 bits of b1
p1 = b2 | ((b1 >> 4) << 8)
# Interleave p0 and p1 to reconstruct full pixel array
unpacked = np.empty(p0.size + p1.size, dtype=np.uint16)
unpacked[0::2] = p0
unpacked[1::2] = p1
return unpacked.reshape((height, width)).astype(np.float32) / 4095.0