Преобразование массива в трехмерное растровое изображение

Введение

Скажем, у нас есть массив таких, что: massive = [[] for each in range(8)]

Каждый список в массиве описывает растровое изображение 8x8 таким образом, что:

Преобразование массива в трехмерное растровое изображение

И каждый элемент в списке описывает, является ли данный «квадрат» черным. Например, list = [0, 63] будет означать, что только первый и последний «квадраты» черные.

Массив, определенный в начале, должен описывать куб 8x8x8, где каждый список является «слоем», и, таким образом, 8 слоев образуют трехмерный куб. Поэтому индекс каждого элемента в массиве (который указывает на конкретный список в массиве) сообщает нам слой, на котором находится конкретное растровое изображение.

Теперь рассмотрим следующий массив:

massive = [[63, 62, 61, 60, 59, 58, 57, 56, 48, 40, 32, 24, 16, 8, 0, 1, 2, 3, 4, 5, 6, 7, 15, 23, 31, 39, 47, 55], 
           [63, 56, 0, 7], 
           [63, 56, 0, 7], 
           [35, 36, 27, 56, 28, 0, 7, 63], 
           [63, 56, 0, 7, 36, 35, 27, 28], 
           [63, 7, 56, 0], 
           [7, 0, 56, 63], 
           [7, 6, 5, 4, 3, 2, 1, 0, 8, 16, 32, 24, 40, 48, 56, 57, 58, 59, 60, 61, 62, 63, 55, 39, 47, 31, 23, 15]]

Вот визуализация всех уникальных растровых изображений в этом массиве:

Растр #1

Обратите внимание на массивные[0] и массивные[7] — первый и последний слои.

Преобразование массива в трехмерное растровое изображение

Растр #2

Обратите внимание на массивный[3] и массивный[4]

Преобразование массива в трехмерное растровое изображение

Растр #3

Примечание: массивный[1], массивный[2], массивный[5], массивный[6]

Преобразование массива в трехмерное растровое изображение

Упомяните, что элементы в этих списках могут быть не по порядку, тем не менее это незначительно, поскольку каждый элемент может только указать, активна ли эта конкретная позиция, и в списке не может быть дубликатов. Таким образом, отсутствие других номеров (от 0 до 63) в списке просто означало бы, что позиция «неактивна».

Массивное трехмерное представление

Вот как массив будет визуализирован в 3D

Преобразование массива в трехмерное растровое изображение

Проблема

Мне нужно, чтобы этот массив служил входом (для Arduino на базе C).

Это наш массив из примера выше в нужном формате:

const byte bitmap[8][8] = {
    {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF},
    {0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81},
    {0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81},
    {0x81, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x81},
    {0x81, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x81},
    {0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81},
    {0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81},
    {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF},
};

Как вы можете видеть, в приведенном выше массиве используется шестнадцатеричный код, например, 0xFF в двоичном формате равен 0b11111111.

Что имеет смысл: представьте, что биты выходят из вашего монитора по оси z, образуя полную линию из 8 квадратов.

Если разбить байт на биты и представить, что эти биты образуют слои (с параллельными битами), то можно увидеть, что этот массив представляет собой трехмерный куб (показан выше во введении). * В качестве альтернативы вы можете визуализировать его как целые байты по оси Z - в любом случае вы получите 3D-куб из введения.

Мне нужна функция, которая будет преобразовывать массив так, чтобы:

Вход:

       [[63, 62, 61, 60, 59, 58, 57, 56, 48, 40, 32, 24, 16, 8, 0, 1, 2, 3, 4, 5, 6, 7, 15, 23, 31, 39, 47, 55], 
       [63, 56, 0, 7], 
       [63, 56, 0, 7], 
       [35, 36, 27, 56, 28, 0, 7, 63], 
       [63, 56, 0, 7, 36, 35, 27, 28], 
       [63, 7, 56, 0], 
       [7, 0, 56, 63], 
       [7, 6, 5, 4, 3, 2, 1, 0, 8, 16, 32, 24, 40, 48, 56, 57, 58, 59, 60, 61, 62, 63, 55, 39, 47, 31, 23, 15]]

Выход:

{
    {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF},
    {0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81},
    {0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81},
    {0x81, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x81},
    {0x81, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x81},
    {0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81},
    {0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81},
    {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF},
};

Пытаться

Ниже моя попытка:

massive = [[63, 62, 61, 60, 59, 58, 57, 56, 48, 40, 32, 24, 16, 8, 0, 1, 2, 3, 4, 5, 6, 7, 15, 23, 31, 39, 47, 55], 
           [63, 56, 0, 7], 
           [63, 56, 0, 7], 
           [35, 36, 27, 56, 28, 0, 7, 63], 
           [63, 56, 0, 7, 36, 35, 27, 28], 
           [63, 7, 56, 0], 
           [7, 0, 56, 63], 
           [7, 6, 5, 4, 3, 2, 1, 0, 8, 16, 32, 24, 40, 48, 56, 57, 58, 59, 60, 61, 62, 63, 55, 39, 47, 31, 23, 15]]


rows, cols = (8, 8)
arr = [['' for i in range(cols)] for j in range(rows)]
arr[0][0] = ''

for row in arr:
  print(row)


def convert():
  for i in range(0, 64):
    for n in range(0,64):
      for each in massive:
        if i == massive[massive.index(each)][n]:
          arr[massive.index(each)][n] = '1'
        else:
          arr[massive.index(each)][n] = '0'

convert()

for row in arr:
  print(row)

Выход:

['', '', '', '', '', '', '', '']
['', '', '', '', '', '', '', '']
['', '', '', '', '', '', '', '']
['', '', '', '', '', '', '', '']
['', '', '', '', '', '', '', '']
['', '', '', '', '', '', '', '']
['', '', '', '', '', '', '', '']
['', '', '', '', '', '', '', '']
Traceback (most recent call last):
  File "main.py", line 28, in <module>
    convert()
  File "main.py", line 23, in convert
    if i == massive[massive.index(each)][n]:
IndexError: list index out of range

Я понимаю свою ошибку здесь, но я застрял и не могу придумать аккуратный способ получить желаемый результат.

Редактировать: * Учтите, что слои куба идут снизу вверх. Таким образом, массив Massive[0] будет первым слоем и, следовательно, самым нижним, тогда как массив[7] будет последним слоем и, следовательно, самым верхним (при визуализации в виде куба см. 3D-представление Massive во Введении).

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
0
144
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вот решение, использующее побитовые операции и затем преобразующее целые числа в шестнадцатеричное представление:

massive = [[63, 62, 61, 60, 59, 58, 57, 56, 48, 40, 32, 24, 16, 8, 0, 1, 2, 3, 4, 5, 6, 7, 15, 23, 31, 39, 47, 55],
           [63, 56, 0, 7],
           [63, 56, 0, 7],
           [35, 36, 27, 56, 28, 0, 7, 63],
           [63, 56, 0, 7, 36, 35, 27, 28],
           [63, 7, 56, 0],
           [7, 0, 56, 63],
           [7, 6, 5, 4, 3, 2, 1, 0, 8, 16, 32, 24, 40, 48, 56, 57, 58, 59, 60, 61, 62, 63, 55, 39, 47, 31, 23, 15]]


rows, cols = (8, 8)
arr = [[0 for i in range(cols)] for j in range(rows)]

# Since lower-bit layers go first in massive, we have to traverse massive in reverse order
for layer in reversed(massive):
    # Shift each bitmask by one bit
    for x in range(rows):
        for y in range(cols):
            arr[x][y] <<= 1
    # Write current layer to the last bit of arr[x][y]
    for val in layer:
        x, y = val % cols, val // cols
        arr[x][y] |= 1

for row in arr:
    print(', '.join('0x{0:0{1}X}'.format(x, 2) for x in row))

будет выводить

0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF
0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81
0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81
0x81, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x81
0x81, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x81
0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81
0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81
0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF

PS: я предлагаю вам использовать 4 пробела на уровень отступа вместо 2 (см. Руководство по отступам PEP8).

Да, нижние слои идут первыми, спасибо.

zаѓатhᵾѕтѓа 10.01.2023 07:27

Обновил мой ответ, чтобы отразить это. На самом деле, использование reversed здесь было бы лучше, чем [::-1], поскольку оно не создает новый список и (возможно) более читабельно.

Alex Bochkarev 12.01.2023 01:14

Чистое решение на питоне

massive = [[63, 62, 61, 60, 59, 58, 57, 56, 48, 40, 32, 24, 16, 8, 0, 1, 2, 3, 4, 5, 6, 7, 15, 23, 31, 39, 47, 55],
           [63, 56, 0, 7],
           [63, 56, 0, 7],
           [35, 36, 27, 56, 28, 0, 7, 63],
           [63, 56, 0, 7, 36, 35, 27, 28],
           [63, 7, 56, 0],
           [7, 0, 56, 63],
           [7, 6, 5, 4, 3, 2, 1, 0, 8, 16, 32, 24, 40, 48, 56, 57, 58, 59, 60, 61, 62, 63, 55, 39, 47, 31, 23, 15]]


cube = [[[0] * 8 for _ in range(8)] for _ in range(8)]

for i, row in enumerate(massive):
    for cell in row:
        cell_y = cell % 8
        cell_x = cell // 8
        cube[i][cell_x][cell_y] = 1

result = [[''] * 8 for _ in range(8)]
for x in range(8):
    for y in range(8):
        binary = ''
        for z in range(8):
            binary += str(cube[x][y][z])
        result[x][y] = hex(int(binary, 2))

for row in result:
    print(row)

выход

['0xff', '0x81', '0x81', '0x81', '0x81', '0x81', '0x81', '0xff']
['0x81', '0x0', '0x0', '0x0', '0x0', '0x0', '0x0', '0x81']
['0x81', '0x0', '0x0', '0x0', '0x0', '0x0', '0x0', '0x81']
['0x81', '0x0', '0x0', '0x18', '0x18', '0x0', '0x0', '0x81']
['0x81', '0x0', '0x0', '0x18', '0x18', '0x0', '0x0', '0x81']
['0x81', '0x0', '0x0', '0x0', '0x0', '0x0', '0x0', '0x81']
['0x81', '0x0', '0x0', '0x0', '0x0', '0x0', '0x0', '0x81']
['0xff', '0x81', '0x81', '0x81', '0x81', '0x81', '0x81', '0xff']
Ответ принят как подходящий

Определенно не самое эффективное, но, надеюсь, вполне читаемое и простое решение.

Начните с простой функции, которая преобразует индексы в требуемые растровые изображения слоя:

def bitmap(indices, side=8):
    """Transform a list of indices to an 8x8 bitmap with those indices turned on"""
    indices = set(indices)
    return [[int(side*i+j in indices) for j in range(side)] for i in range(side)]

Например, для первой строки в massive вы получите:

[[1, 1, 1, 1, 1, 1, 1, 1],
 [1, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 1],
 [1, 1, 1, 1, 1, 1, 1, 1]]

Это соответствует вашей иллюстрации слоев и может быть использовано для их визуального создания с помощью matplotlib --

plt.imshow(bitmap(massive[0]), cmap='gray_r')
plt.show()

Или даже как 3D-график с использованием вокселей:

cube = np.array([bitmap(layer) for layer in massive])
fig, ax = plt.subplots(subplot_kw = {"projection": "3d"})
# Use transpose of `cube` to get the direction right
# (bottom->up rather than left->right)
ax.voxels(cube.T, edgecolor='k')
ax.set(xticklabels=[], yticklabels=[], zticklabels=[])
plt.show()

Затем небольшая функция для добавления этих вертикальных слоев по мере необходимости:

def hexaize(massive, side=8):
    """Adds the values for each column across vertical layers"""
    final_map = [[0] * side for _ in range(side)]
    # Reverse-iterate over massive since it's given bottom-up and not top-down
    for i, layer in enumerate(reversed(massive)):
        for j, row in enumerate(bitmap(layer)):
            for k, val in enumerate(row):
                final_map[i][j] += val*2**k
    # Finally convert the added values to hexadecimal
    # Use the f-string formatting to ensure upper case and 2-digits
    return [[f"0x{val:02X}" for val in row] for row in final_map]

Затем вызов hexaize(massive) возвращает:

[['0xFF', '0x81', '0x81', '0x81', '0x81', '0x81', '0x81', '0xFF'],
 ['0x81', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x81'],
 ['0x81', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x81'],
 ['0x81', '0x00', '0x00', '0x18', '0x18', '0x00', '0x00', '0x81'],
 ['0x81', '0x00', '0x00', '0x18', '0x18', '0x00', '0x00', '0x81'],
 ['0x81', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x81'],
 ['0x81', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x81'],
 ['0xFF', '0x81', '0x81', '0x81', '0x81', '0x81', '0x81', '0xFF']]

Наконец, если вам нужен точный вывод, как описано выше (в C-подобной нотации?), то вы можете связать несколько вызовов replace следующим образом:

def massive_to_arduino(massive, side=8):
    """Converts a massive to Arduino style input"""
    # Get the hexa format of massive
    in_hex = hexaize(massive, side=side)
    # Replace square brackets with curly ones
    in_hex = str(in_hex).replace("[", "{").replace("]", "}")
    # Break rows to join them with new lines and indentation
    in_hex = "},\n   ".join(in_hex.split("},"))
    # Add new line, indentation, and semicolon to start and end
    return in_hex.replace("{{", "{\n    {").replace("}}", "},\n};")

А потом звоню

print(massive_to_arduino(massive))

производит

{
    {'0xFF', '0x81', '0x81', '0x81', '0x81', '0x81', '0x81', '0xFF'},
    {'0x81', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x81'},
    {'0x81', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x81'},
    {'0x81', '0x00', '0x00', '0x18', '0x18', '0x00', '0x00', '0x81'},
    {'0x81', '0x00', '0x00', '0x18', '0x18', '0x00', '0x00', '0x81'},
    {'0x81', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x81'},
    {'0x81', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x81'},
    {'0xFF', '0x81', '0x81', '0x81', '0x81', '0x81', '0x81', '0xFF'},
};

Спасибо за визуализацию. Можно ли изменить его так, чтобы при построении в режиме реального времени (например, при изменении массива) слои отображались снизу вверх, а не слева направо (см. Редактирование в этом сообщении).

zаѓатhᵾѕтѓа 12.01.2023 04:19

@zaѓathᵾѕтѓа Ах да, извините, я пропустил это. Это просто простая операция транспонирования (т.е. вы рисуете cube.T). Я отредактировал свой ответ для этого. Обновлено: я также добавил reversed, чтобы соответствовать этому требованию для вывода значений.

hyit 12.01.2023 09:19

Другие вопросы по теме