//  [python]
def create_directory_table(
    num_frames: int = 0, pixel_dimensions: dict = {}, pixel_byte_size: int = 2
):
    """
    Creates directory tables for an ecat file when provided with the number of frames, the total number of pixels
    per frame, and the byte size of each of those pixels. Ecat's can have int16 (2 byte widths) or float 32 (4 byte
    widths) pixel values. This table will then be used to look up each subheader and it's corresponding pixel data
    after it is written.
    :param num_frames: number of frames taken during the ecat scan, a.k.a. time dimension
    :param pixel_dimensions: a dictionary such as {'x': 2, 'y': 2, 'z': 2}, the relevant information is contained within
    the values, not the keys. However, those keys may be important w/ cylindrical ecat files, at this point it is
    undetermined.
    :param pixel_byte_size: the width of each pixel's value in bytes.
    :return: a list containing 2 dimensional directory tables populated w/ elements corresponding to the byte position
    of the frame info of the ECAT.
    """
    # first determine the number of byte blocks the directory table(s) require based on the
    # number of frames
    required_directory_blocks = ceil(num_frames / 31)

    # determine the width of the frame byte blocks with the pixel dimensions and data sizes
    pixel_volume = numpy.prod([*pixel_dimensions.values()])

    # still not sure what some of these numbers mean, but we populate the first column of the byte array with them
    directory_tables = []

    # create entries for the directory table
    directory_order = [i + 1 for i in range(num_frames)]

    # why is the initial table byte position equal to 3? Because the first header and pixel data lies
    # after the main header byte block at position 0 to 1 and the directory table itself occupies byte blocks
    # 1 to 2, thus the first frame (header and pixel data) will land at byte block number 3! Whoopee!
    table_byte_position = 3  # 1 + required_directory_blocks
    for i in range(required_directory_blocks):
        # initialize empty 4 x 32 array
        table = numpy.ndarray((4, 32), dtype=">i4")

        # populate first column of table with codes and undetermined codes
        # note these table values are only extrapolated from a data set w/ 45 frames and datasets with less than
        # 31 frames. Behavior or values for frames numbering above 45 (or more specifically 62) is unknown.
        if i == (required_directory_blocks - 1):
            frames_to_iterate = num_frames % 31
            table[0, 0] = (
                31 - frames_to_iterate
            )  # number of un-used columns in the directory table
            table[1, 0] = 2  # stop value
            table[2, 0] = 2  # stop value
            table[3, 0] = frames_to_iterate  # number of frames referenced in this table
        else:
            frames_to_iterate = floor(num_frames / 31) * 31
            table[0, 0] = 0
            table[1, 0] = (
                0  # note this is just a placeholder the real value is entered after the final position of the
            )
            # last entry in the first directory is calculated
            table[2, 0] = 0
            table[3, 0] = frames_to_iterate

        for column in range(frames_to_iterate):
            if column != 0:
                table_byte_position += 1
            elif column == 0 and i == 1:
                table_byte_position += 2
            table[0, column + 1] = directory_order.pop(0)
            table[1, column + 1] = table_byte_position
            # frame byte position is shifted
            table_byte_position = int(
                (pixel_byte_size * pixel_volume) / 512 + table_byte_position
            )
            table[2, column + 1] = table_byte_position
            table[3, column + 1] = 1

        # VERY IMPORTANT
        if i != (required_directory_blocks - 1):
            table[1, 0] = table[2, column + 1] + 1

        directory_tables.append(table)

    return directory_tables