Skip to content

Instantly share code, notes, and snippets.

@promto-c
Created February 11, 2024 20:32
Show Gist options
  • Select an option

  • Save promto-c/a268eebbb989d1b7aed01f17292c5a2c to your computer and use it in GitHub Desktop.

Select an option

Save promto-c/a268eebbb989d1b7aed01f17292c5a2c to your computer and use it in GitHub Desktop.
Compare the execution times of reading and processing the image data from DPX file 12bit packed in 16-bit vs. 32-bit modes.
import struct, math
import time
import numpy as np
def read_dpx_header(file):
headers = {}
# Generic File Header
generic_file_header_format = ">I I 8s I I I I I 100s 24s 100s 200s 200s I 104s"
headers['GenericFileHeader'] = struct.unpack(
generic_file_header_format, file.read(768)
)
# Determine endianness based on magic number
magic_number = headers['GenericFileHeader'][0]
if magic_number == 0x53445058:
headers['endianness'] = 'be' # big-endian
elif magic_number == 0x58504453:
headers['endianness'] = 'le' # little-endian
# Generic Image Header
generic_image_header_format = ">H H I I"
headers['GenericImageHeader'] = struct.unpack(
generic_image_header_format, file.read(12)
)
return headers
def read_dpx_12bit_packed(image_path: str, read_as_32bit: bool) -> np.ndarray:
with open(image_path, "rb") as file:
meta = read_dpx_header(file)
width = meta['GenericImageHeader'][2]
height = meta['GenericImageHeader'][3]
offset = meta['GenericFileHeader'][1]
file.seek(offset)
if read_as_32bit:
words_per_line = math.ceil(width * 9/8)
raw = np.fromfile(file, dtype=np.uint32, count=words_per_line*height)
else:
words_per_line = math.ceil(width * 9/4)
raw = np.fromfile(file, dtype=np.uint16, count=words_per_line*height)
word_lines = raw.reshape(height, words_per_line)
if meta['endianness'] == 'be':
raw.byteswap(True)
# Constants for the process
components_per_pixel = 3 # RGB components
# Extract 8 components from 6 halfwords (read as 16bit)
# Extract 8 components from 3 words (read as 32bit)
# word1: B5 B6 B7 B8 B9 B10 B11 B12 G1 G2 G3 G4 G5 G6 G7 G8 G9 G10 G11 G12 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12
# word2: BBBBGGGGGGGGGGGG RRRRRRRRRRRR B1 B2 B3 B4
# word3: GGGGGGGGGGGGRRRR RRRRRRRRBBBBBBBB
if read_as_32bit:
image_data = np.array([
word_lines[:, 0::3] & 0xFFF,
(word_lines[:, 0::3] >> 12) & 0xFFF,
((word_lines[:, 0::3] >> 24) & 0xFF) | ((word_lines[:, 1::3] & 0xF) << 8),
(word_lines[:, 1::3] >> 4) & 0xFFF,
(word_lines[:, 1::3] >> 16) & 0xFFF,
((word_lines[:, 1::3] >> 28) & 0xF) | ((word_lines[:, 2::3] & 0xFF) << 4),
(word_lines[:, 2::3] >> 8)& 0xFFF,
(word_lines[:, 2::3] >> 20 & 0xFFF)
], dtype=np.uint16).transpose(1, 2, 0).reshape(height, width, components_per_pixel) # Reshape to (height, width, components_per_pixel)
else:
image_data = np.array([
(word_lines[:, 1::6] & 0xFFF),
((word_lines[:, 0::6] & 0xFF) << 4) | (word_lines[:, 1::6] >> 12),
((word_lines[:, 3::6] & 0xF) << 8) | (word_lines[:, 0::6] >> 8),
(word_lines[:, 3::6] >> 4),
(word_lines[:, 2::6] & 0xFFF),
((word_lines[:, 5::6] & 0xFF) << 4) | (word_lines[:, 2::6] >> 12),
((word_lines[:, 4::6] & 0xF) << 8) | (word_lines[:, 5::6] >> 8),
(word_lines[:, 4::6] >> 4 & 0xFFF)
], dtype=np.uint16).transpose(1, 2, 0).reshape(height, width, components_per_pixel) # Reshape to (height, width, components_per_pixel)
# Convert to float32 and normalize
image_data = image_data.astype(np.float32)
image_data /= 0x0FFF
return image_data
# Test Execution Time
# -------------------
def measure_execution_time(func, *args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
return time.time() - start_time
def test_multiple_times(image_path, num_tests=10):
times_16bit = []
times_32bit = []
for _ in range(num_tests):
times_16bit.append(measure_execution_time(read_dpx_12bit_packed, image_path, read_as_32bit=False))
times_32bit.append(measure_execution_time(read_dpx_12bit_packed, image_path, read_as_32bit=True))
avg_time_16bit = sum(times_16bit) / num_tests
avg_time_32bit = sum(times_32bit) / num_tests
print(f"Average 16-bit reading time over {num_tests} tests: {avg_time_16bit} seconds")
print(f"Average 32-bit reading time over {num_tests} tests: {avg_time_32bit} seconds")
if __name__ == "__main__":
image_path = 'dpx12.0000.dpx'
test_multiple_times(image_path, num_tests=100)
# Average 16-bit reading time over 100 tests: 0.047649037837982175 seconds
# Average 32-bit reading time over 100 tests: 0.05539111137390137 seconds
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment