Python: leyendo archivos binarios de 12 bits

Estoy tratando de leer archivos binarios de 12 bits que contienen imágenes (un video) utilizando Python 3.

Para leer un archivo similar pero codificado en 16 bits, lo siguiente funciona muy bien:

import numpy as np images = np.memmap(filename_video, dtype=np.uint16, mode='r', shape=(nb_frames, height, width)) 

donde filename_video es el archivo y nb_frames, altura y ancho de las características del video que se pueden leer desde otro archivo. Por “trabajar muy bien” me refiero a rápido: leer un video de 640×256 que tiene 140 cuadros toma aproximadamente 1 ms en mi computadora.

Por lo que sé, no puedo usar esto cuando el archivo está codificado en 12 bits porque no hay un tipo uint12. Entonces, lo que estoy tratando de hacer es leer un archivo de 12 bits y almacenarlo en una matriz uint de 16 bits. Lo siguiente, tomado de ( Python: leer imágenes binarias empaquetadas de 12 bits ), funciona:

 with open(filename_video, 'rb') as f: data=f.read() images=np.zeros(int(2*len(data)/3),dtype=np.uint16) ii=0 for jj in range(0,int(len(data))-2,3): a=bitstring.Bits(bytes=data[jj:jj+3],length=24) images[ii],images[ii+1] = a.unpack('uint:12,uint:12') ii=ii+2 images = np.reshape(images,(nb_frames,height,width)) 

Sin embargo, esto es muy lento: la lectura de un video de 640×256 que tiene solo 5 cuadros toma aproximadamente 11.5 s con mi máquina. Idealmente, me gustaría poder leer archivos de 12 bits tan eficientemente como puedo leer archivos de 8 o 16 bits usando memmap. O al menos no 10 ^ 5 veces más lento. ¿Cómo podría acelerar las cosas?

Aquí hay un ejemplo de archivo: http://s000.tinyupload.com/index.php?file_id=26973488795334213426 (nb_frames = 5, height = 256, width = 640).

Tengo una implementación ligeramente diferente de la propuesta por @ max9111 que no requiere una llamada para unpackbits .

Crea dos valores uint12 partir de tres uint8 consecutivos directamente al cortar el byte medio a la mitad y usar las operaciones binarias de numpy. En lo siguiente, se supone que data_chunks es una cadena binaria que contiene la información para un número de número arbitrario de enteros de 12 bits (por lo tanto, su longitud debe ser un múltiplo de 3).

 def read_uint12(data_chunk): data = np.frombuffer(data_chunk, dtype=np.uint8) fst_uint8, mid_uint8, lst_uint8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.uint16).T fst_uint12 = (fst_uint8 << 4) + (mid_uint8 >> 4) snd_uint12 = ((mid_uint8 % 16) << 8) + lst_uint8 return np.reshape(np.concatenate((fst_uint12[:, None], snd_uint12[:, None]), axis=1), 2 * fst_uint12.shape[0]) 

Comparé con la otra implementación y este enfoque demostró ser ~ 4 veces más rápido en una entrada de ~ 5 Mb:
read_uint12_unpackbits 65.5 ms ± 1.11 ms por bucle (media ± read_uint12 de 7 carreras, 10 bucles cada una) read_uint12 14 ms ± 513 µs por bucle (media ± read_uint12 de 7 carreras, 100 bucles cada una)

Editar

Con la ayuda de la respuesta de @Cyril Gaudefroy, creé una solución comstackda utilizando Numba .

 import numba as nb import numpy as np @nb.njit(nb.uint16[::1](nb.uint8[::1]),fastmath=True,parallel=True) def nb_read_uint12(data_chunk): """data_chunk is a contigous 1D array of uint8 data) eg.data_chunk = np.frombuffer(data_chunk, dtype=np.uint8)""" #ensure that the data_chunk has the right length assert np.mod(data_chunk.shape[0],3)==0 out=np.empty(data_chunk.shape[0]//3*2,dtype=np.uint16) for i in nb.prange(data_chunk.shape[0]//3): fst_uint8=np.uint16(data_chunk[i*3]) mid_uint8=np.uint16(data_chunk[i*3+1]) lst_uint8=np.uint16(data_chunk[i*3+2]) out[i*2] = (fst_uint8 << 4) + (mid_uint8 >> 4) out[i*2+1] = ((mid_uint8 % 16) << 8) + lst_uint8 return out 

Tiempos

 num_Frames=10 data_chunk=np.random.randint(low=0,high=255,size=np.int(640*256*1.5*num_Frames),dtype=np.uint8) Cyril Gaudefroy(numpy only): 11ms ->225MB/s Numba version: 1.1ms ->2.25GB/s 

Versión anterior (no recomendada)

Si Numba no es una opción, vea la respuesta de @Cyril Gaudefroys.

Cuando leí la pregunta, pensé que debía haber una respuesta simple, pero fallé. Sin embargo, escribí un código simple (pero feo) que es unas 300 veces más rápido que su ejemplo y alcanza unos 25 MB / s en mi computadora portátil (Core i5 3210M).

 def read_uint12(filename_video,nb_frames,height,width): data=np.fromfile(filename_video, dtype=np.uint8) data=np.unpackbits(data) data=data.reshape((data.shape[0]/12,12)) images=np.zeros(data_2.shape[0],dtype=np.uint16) for i in xrange(0,12): images+=2**i*data[:,11-i] images = np.reshape(images,(nb_frames,height,width)) return images 

Encontrado @cyrilgaudefroy respuesta útil. Sin embargo, inicialmente, no funcionó en mis datos de imagen binaria empaquetados de 12 bits. Descubrí que el empaque es un poco diferente en este caso particular. El byte “medio” contenía los nibbles menos significativos. Los bytes 1 y 3 del triplete son los 8 bits más significativos de los doce. De ahí la respuesta modificada de @cyrilgaudefroy a:

 def read_uint12(data_chunk): data = np.frombuffer(data_chunk, dtype=np.uint8) fst_uint8, mid_uint8, lst_uint8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.uint16).T fst_uint12 = (fst_uint8 << 4) + (mid_uint8 >> 4) snd_uint12 = (lst_uint8 << 4) + (np.bitwise_and(15, mid_uint8)) return np.reshape(np.concatenate((fst_uint12[:, None], snd_uint12[:, None]), axis=1), 2 * fst_uint12.shape[0]) 

Aquí hay otra variación. Mi formato de datos es:

primer uint12: 4 bits más significativos de 4 bits menos significativos de segundo uint8 + 8 bits menos significativos desde primer uint8

segundo uint12: 8 bits más significativos desde el tercer uint8 + 4 bits menos significativos desde los 4 bits más significativos desde el segundo uint8

El código correspondiente es:

 def read_uint12(data_chunk): data = np.frombuffer(data_chunk, dtype=np.uint8) fst_uint8, mid_uint8, lst_uint8 = numpy.reshape(data, (data.shape[0] // 3, 3)).astype(numpy.uint16).T fst_uint12 = ((mid_uint8 & 0x0F) << 8) | fst_uint8 snd_uint12 = (lst_uint8 << 4) | ((mid_uint8 & 0xF0) >> 4) return numpy.reshape(numpy.concatenate((fst_uint12[:, None], snd_uint12[:, None]), axis=1), 2 * fst_uint12.shape[0])