У меня есть тысячи плиток в оттенках серого размером 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, так как это заняло пару минут.