Эффективное сохранение плиток в изображение бигтифа - PullRequest
0 голосов
/ 10 апреля 2020

У меня есть тысячи плиток в оттенках серого размером 256 x 256 пикселей с dtype np.uint8, и я хочу как можно быстрее объединить их в одно пирамидальное изображение BigTiff.

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

1) Tifffile , используя функцию imsave, которая оказалась очень медленной, я бы оценил, по крайней мере, в течение 10 минут, для файла, который в конечном итоге будет около 700 МБ

2) pyvips , преобразовав массивное изображение numpy в изображение pyvips с помощью pyvips.Image.new_from_memory, а затем сохранив его, используя следующее:

vips_img.tiffsave(filename, tile=True, compression='lzw', bigtiff=True, pyramid=True, Q=80)

Создание vips_img занимает ~ 42 секунды, а сохранение его на диск - еще ~ 30, но все это делается с помощью одного потока. Мне интересно, есть ли способ сделать это более эффективно, используя другой метод или используя многопоточность. Доступно высокоскоростное хранение, поэтому потенциально можно сначала сохранить данные в другом формате или перенести их на другой язык программирования, если это необходимо.

Просто мозговой штурм: все плитки взяты из уже существующего изображения BigTiff и были помещены через конвейер предварительной обработки и теперь необходимо сохранить снова. Мне интересно, может ли потенциально быть способ скопировать оригинальный файл и заменить данные там эффективно.

редактировать с дополнительной информацией:

Размеры изображения примерно 55k на 45k, но я хотел бы использовать этот код и для больших изображений, вплоть до 150k на 150к например.

Для изображений размером 55k на 45k и плиток 256 на 256 мы говорим о ~ 53k плиток. Эти плитки не содержат всю информацию, которая мне интересна, поэтому в итоге я могу получить 50% плиток, которые я хочу сохранить снова, оставшаяся часть изображения может быть черной. Сохранение обработанного в том же формате кажется мне наиболее удобным подходом, поскольку я хотел бы отобразить его как наложение

редактировать с промежуточным решением

Ранее я упоминал что создание изображения pyvips из массива numpy заняло 40 секунд. Причиной этого было то, что мой вход был транспонированным numpy массивом. Сама операция транспонирования очень быстрая, но я подозреваю, что она осталась в памяти, как и раньше, что приводило к большим потерям кэша при чтении из нее в транспонированном виде.

Так что в настоящее время следующая строка занимает 30 секунд (для записи файла размером 200 МБ)

    vips_img.tiffsave(filename, tile=True, compression='lzw', bigtiff=True, pyramid=True, Q=80)

Было бы неплохо, если бы это могло быть быстрее, но это кажется разумным.

Пример кода

В моем случае только ~ 15% плиток интересны и будут предварительно обработаны. Это по всему изображению, хотя. Я все еще хотел бы сохранить это в гигапиксельном формате, поскольку это позволяет мне использовать openslide для извлечения частей изображения, используя их удобную библиотеку. В этом примере я только что сгенерировал ~ 15% случайных данных для имитации процентного содержания черного / информации, и производительность этого примера аналогична реальной реализации, где данные больше разбросаны по изображению.

import numpy as np
import pyvips

def numpy2vips(a):
    dtype_to_format = {
    'uint8': 'uchar',
    'int8': 'char',
    'uint16': 'ushort',
    'int16': 'short',
    'uint32': 'uint',
    'int32': 'int',
    'float32': 'float',
    'float64': 'double',
    'complex64': 'complex',
    'complex128': 'dpcomplex',
    }
    height, width, bands = a.shape
    linear = a.reshape(width * height * bands)
    vi = pyvips.Image.new_from_memory(linear.data, width, height, bands,
                                      dtype_to_format[str(a.dtype)])
    return vi

left = np.random.randint(0, 256, (7500, 45000), np.uint8)
right = np.zeros((50000, 45000), np.uint8)
img = np.vstack((left, right))
vips_img = numpy2vips(np.expand_dims(img, axis=2))

start = time.time()
vips_img.tiffsave("t1", tile=True, compression='deflate', bigtiff=True, pyramid=True)
print("pyramid deflate took: ", time.time() - start)

start = time.time()
vips_img.tiffsave("t2", tile=True, compression='lzw', bigtiff=True, pyramid=True)
print("pyramid lzw took: ", time.time() - start)

start = time.time()
vips_img.tiffsave("t3", tile=True, compression='jpeg', bigtiff=True, pyramid=True)
print("pyramid jpg took: ", time.time() - start)

start = time.time()
vips_img.dzsave("t4", tile_size=256, depth='one', overlap=0, suffix='.jpg[Q=75]')
print("dzi took: ", time.time() - start)

output

pyramid deflate took:  32.69183301925659
pyramid lzw took:  32.10764741897583
pyramid jpg took:  59.79427194595337

Я не дождался окончания работы dzsave sh, так как это заняло пару минут.

1 Ответ

0 голосов
/ 13 апреля 2020

Я попробовал вашу тестовую программу на своем ноутбуке (Ubuntu 19.10) и вижу:

pyramid deflate took:  35.757954359054565
pyramid lzw took:  42.69455623626709
pyramid jpg took:  26.614688634872437
dzi took:  44.16632699966431

Я думаю, вы не используете libjpeg-turbo , SIMD-форк libjpeg , К сожалению, это очень сложно установить на macOS, потому что brew зависает в не-SIMD-версии, но это должно быть легко в вашей системе развертывания, просто установите пакет libjpeg-turbo вместо libjpeg (они двоично совместимы).

Существуют различные похожие проекты для zlib , которые должны значительно ускорить сжатие с раздувом.

...