How to view real time mosaicing of large image? - python

I have built a code which will stitch 100X100 images approx. I want to view this stitiching process in real time. I am using pyvips to create large image. I am saving final image in .DZI format as it will take very less memory footprint to display.
Below code is copied just for testing purpose https://github.com/jcupitt/pyvips/issues/43.
#!/usr/bin/env python
import sys
import pyvips
# overlap joins by this many pixels
H_OVERLAP = 100
V_OVERLAP = 100
# number of images in mosaic
ACROSS = 40
DOWN = 40
if len(sys.argv) < 2 + ACROSS * DOWN:
print 'usage: %s output-image input1 input2 ..'
sys.exit(1)
def join_left_right(filenames):
images = [pyvips.Image.new_from_file(filename) for filename in filenames]
row = images[0]
for image in images[1:]:
row = row.merge(image, 'horizontal', H_OVERLAP - row.width, 0)
return row
def join_top_bottom(rows):
image = rows[0]
for row in rows[1:]:
image = image.merge(row, 'vertical', 0, V_OVERLAP - image.height)
return image
rows = []
for y in range(0, DOWN):
start = 2 + y * ACROSS
end = start + ACROSS
rows.append(join_left_right(sys.argv[start:end]))
image = join_top_bottom(rows)
image.write_to_file(sys.argv[1])
To run this code:
$ export VIPS_DISC_THRESHOLD=100
$ export VIPS_PROGRESS=1
$ export VIPS_CONCURRENCY=1
$ mkdir sample
$ for i in {1..1600}; do cp ~/pics/k2.jpg sample/$i.jpg; done
$ time ./mergeup.py x.dz sample/*.jpg
here cp ~/pics/k2.jpg will copy k2.jpg image 1600 times from pics folder, so change according to your image name and location.
I want to display this process in real time. Right now after creating final mosaiced image I am able to display. Just an idea,I am thinking to make a large image and display it, then insert smaller images. I don't know, how it can be done. I am confused as we also have to make pyramidal structure. So If we create large image first we have to replace each level images with the new images. Creating .DZI image is expensive, so I don't want to create it in every running loop. Replacing images may be a solution. Any suggestion folks??

I suppose you have two challenges: how to keep the pyramid up-to-date on the server, and how to keep it up-to-date on the client. The brute force method would be to constantly rebuild the DZI on the server, and periodically flush the tiles on the client (so they reload). For something like that you'll also need to add a cache bust to the tile URLs each time, or the browser will think it should just use its local copy (not realizing it has updated). Of course this brute force method is probably too slow (though it might be interesting to try!).
For a little more finesse, you'd want to make a pyramid that's exactly aligned with the sub images. That way when you change a single sub image, it's obvious which tiles need to be updated. You can do this with DZI if you have square sub images and you use a tile size that is some even fraction of the sub image size. Also no tile overlap. Of course you'll have to build your own DZI constructor, since the existing ones aren't primed to simply replace individual tiles. If you know which tiles you changed on the server, you can communicate that to the client (either via periodic polling or with something like web sockets) and then flush only those tiles (again with the cache busting).
Another solution you could experiment with would be to not attempt a pyramid, per se, but just a flat set of tiles at a reasonable resolution to allow the user to pan around the scene. This would greatly simplify your pyramid updating on the server, since all you would need to do would be replace a single image for each sub image. This could be loaded and shown in a custom (non-OpenSeadragon) fashion on the client, or you could even use OpenSeadragon's multi-image feature to take advantage of its panning and zooming, like here: http://www.letsfathom.com/ (each album cover is its own independent image object).

Related

How do I generate a small image randomly in different parts of the big image?

Let's assume there are two images. One is called small image and another one is called big image. I want to randomly generate the small image inside the different parts of the big image one at a time everytime I run.
So, currently I have this image. Let's call it big image
I also have smaller image:
def mask_generation(blob_index,image_index):
experimental_image = markup_images[image_index]
h, w = cropped_images[blob_index].shape[:2]
x = np.random.randint(experimental_image.shape[0] - w)
y = np.random.randint(experimental_image.shape[1] - h)
experimental_image[y:y+h, x:x+w] = cropped_images[blob_index]
return experimental_image
I have created above function to generate the small image in big image everytime I call this function. Note: blob index is the index that I use to call specific 'small image' since I have a collection of those small images and image_index is the index to call specific 'big images'. Big images are stored in the list called experimental_image and small images are stored in list called markup images
However, when I run this, I do get the small image randomly generated but the previously randomly generated image never gets deleted and I am not too sure how to proceed with it,
Example: When I run it once
When I run it twice
How do I fix this? Any help will be appreciated. Thank you
I tried the above code but didn't work as I wanted it to work
I suppose you only want the small random image you generated in this iteration in your code.
The problem you have, is due to the modification of your calling args.
When you call your function multiple times with the same big image
markup_image = ...
result_1 = mask_generation(blob_index=0, image_index=0)
result_2 = mask_generation(blob_index=1, image_index=0)
You get in result_2 both small images.
This is due to the writing to the original image in
experimental_image[y:y+h, x:x+w] = cropped_images[blob_index]
This adds the small image to your original image in your list of images.
When getting this image the next time, the small image is already there.
To fix:
Do not alter your images, e.g. by first copying the image and then adding the small image in your function
Probably even better: Only give your function a big and small image, and make sure that they always receive a copy

Merging images in Python PIL to produce animated gifs

I've been using ImageMagick for a while to create simple animated gifs that demonstrate how GAN-generated faces (from thispersondoesnotexist dot com) all have a "family resemblance".
The animated gif starts by showing an initial image, progressively merges it with the second image & then progressively demerges it until the second image is shown.
I've used a crude bash script that works fine but is slow, and as I code a lot in Python, I wanted to try to do the same in PIL.
I don't know much about image processing & I'm not a professional programmer.
The bash script is like this:
#!/bin/bash
# $1, $2 are input files, $3 is a string
#
for i in {00..100..05}
do composite $1 $2 -blend $i $3_$i.png
done
convert $3_*png -set delay 26 animated.gif
This creates an animated gif like this
My first attempt was using PIL.Image.blend() method:
from PIL import Image
one = Image.open("somepath/some_jpg1.jpg")
two = Image.open("somepath/some_jpg2.jpg)
img_list = [Image.blend(one, two, i/100) for i in range(0, 105, 5)]
img_list[0].save('test_animation.gif', save_all=True, append_images=img_list[1:], duration=250)
This works after a fashion but the images are quite degraded (if it were film I'd call it "reticulation")
I've looked at the PIL docs for other methods such as PIL.Image.composite() and PIL.Image.paste() in case there's other ways of doing this, but I can't understand how to create & deploy transparency masks to achieve what I want.
I don't understand how the images appear to be being degraded or how to stop this happening.
It looks like the PIL palette optimisation and dither code is not very fancy. GIF is a really terrible image format and getting nice results takes a lot of work, unfortunately.
I realise you asked for PIL solutions, but pyvips has a high-quality GIF writer -- it uses libimagequant (the quantisation and dither library from pngquant) and the results are even better than imagemagick.
I tried:
#!/usr/bin/python3
import sys
import pyvips
if len(sys.argv) < 4:
print(f"usage: {sys.argv[0]} OUTPUT-FILENAME IMAGE1 IMAGE2 ...")
sys.exit(1)
# load the images at 200 pixels across
# add a copy of the first face to the end, so we loop
faces = [pyvips.Image.thumbnail(filename, 200).copy_memory()
for filename in sys.argv[2:]]
faces.append(faces[0])
# fade between two images
def fade(a, b):
# factor is a pecentage, so 20 steps
frames = [a * (factor / 100) + b * (1 - factor / 100)
for factor in range(100, 0, -5)]
return pyvips.Image.arrayjoin(frames, across=1)
# fade between face1 and face2, then between face2 and face3, etc
fades = []
for a, b in zip(faces, faces[1:]):
fades.append(fade(a, b))
# join all the fades into a single image and save it
pyvips.Image.arrayjoin(fades, across=1) \
.write_to_file(sys.argv[1], page_height=faces[0].height)
If I run like this, using three faces from thispersondoesnotexist:
$ ./fade.py x.gif ~/pics/face-*
It finishes in about 3 seconds and I get:

(Live) viewing of large images using pyvips

I recently discovered the awesome pyvips package and would like to use it to analyze data that was taken on a homebuilt slide scanner (not built by me). I scan about 4000 tiles of 1024x1024 pixels each along the edges of a square-shaped sample (the center part of the sample is not recorded). All tiles are saved as a single binary file. I have written a python class that returns a desired tile as a numpy array from the binary file and which also gives the (x, y) coordinates of the specific tile. Unfortunately, the tiles are not arranged on a grid.
I first determine the total width and height of the full image and initialize a black image of the correct size and subsequently place the tiles at the correct locations using the insert function. The composite image is about 120k x 120k pixels, but most of the image is empty. Finally, I plot the resulting image using matplotlib.
import pyvips
import numpy as np
import matplotlib.pyplot as plt
# class to read tiles from data file
sr = TileReader("path_to_scan_file")
# some stuff to determine the width and height of the total image...
# create empty image for inserting tiles
im = pyvips.Image.black(width, height)
# loop over all tiles and place tile at correct position
for i in range(sr.num_tiles()):
frame, coord = sr.ReadFrame(i)
tile = pyvips.Image.new_from_array(frame)
im = im.insert(tile, coord[0], coord[1])
# plot result
plt.imshow(im.numpy())
plt.show()
# save file
im.write_to_file('full_image.tiff')
Generating the full image in the loop seems to be very fast. However, plotting or saving the data is not. (Obviously,) the plotting only works for a small number of tiles (~10). I also tried saving the data to a pyramidal tiff. However, writing the image took several hours and the generated file seems to be corrupted or too large to be opened. Unfortunately I could not get nip2 installed without admin rights.
I would like to be able to manually select regions of interest of the composite image that I can use for further processing. What is the best/fastest way to interact with the generated image to enable this?
You can use crop to cut out a chunk of the image and pass that on to something else. It won't make the whole thing, it'll just render the bit you need, so it'll be quick.
Something like:
# loop over all tiles and place at correct position
# do this once on startup
for i in range(sr.num_tiles()):
frame, coord = sr.ReadFrame(i)
tile = pyvips.Image.new_from_array(frame)
im = im.insert(tile, coord[0], coord[1])
# left, top, width, height
# hook these numbers up to eg. a scrollbar
# do the crop again for each scrollbar movement
tile = im.crop(0, 0, 1000, 1000)
# plot result
plt.imshow(tile.numpy())
plt.show()
If you want to get fancy, the best solution is probably vips_sink_screen():
https://www.libvips.org/API/current/libvips-generate.html#vips-sink-screen
That'll let you generate pixels from any pipeline asynchronously as you pan and zoom, but it needs C, sadly. There's an example image viewer using this API here:
https://github.com/jcupitt/vipsdisp
That's running vips_sink_screen() in the background to generate GPU textures at various scales, then using that set of textures to paint the screen at 60 fps (ish) as you pan and zoom around. It can display huge dynamically computed images very quickly.

Speeding up grabbing alphachannel

I don't know if there is anything that can be done to speed up my code at all, probably not by much if at all, but I thought I would ask here.
I am working on a python script for a program that uses a custom embedded python interpreter so I can only use the default libraries. External libraries like Pillow and Numpy don't work because they changed the name of the python dll and so the precompiled libraries can't interact with it.
This program doesn't support pasting transparent images from the clipboard outside of its own proprietary format. So I'm writing a script to cover that feature. It grabs the CF_DIBv5 format from the clipboard using ctypes and checks to see if it is 32bpp and that an alphamask exists.
Here's the slow part. I then need to isolate the alpha channel and save it as its own separate image. I can do this easily enough. Just grab a Long from the byte string, & the mask to get the alpha channel, and add pack it back to my new bitmap bytestring. On a small 300x300 image, this takes close to 10 seconds. Which isn't horrible. I will gladly live with that. However, I fear it's going to be horribly slow on larger megapixel images.
I'm not showing the complete code here because it's a horrible ugly mess and most of it is just defining the structures I'm using for my bitmap class and getting ctypes working. But here are the important parts where I loop over the data.
rowsizemask = calcRowSize(24,bmp.header.bV5Width) #returns bytes per row needed
rowmaskpadding = b'\x00'*(rowsizemask - bmp.header.bV5Width*3) #creates padding bytes
#loop over image data
for y in range(bmp.header.bV5Height):
for x in range(bmp.header.bV5Width):
offset, color = unpack(offset,">L",buff) #calls struct.unpack in custom function
color = color[0] & bmp.header.bV5AlphaMask #gets alpha channel
newbmp.pixels += struct.pack(">3B", color,color,color) #creates 24bpp listing
newbmp.pixels += rowmaskpadding #pad row to meet BMP specs
So what do you think? Am I missing something obvious? Or is this about as good as it's going to get with pure python only?
Okay, so after some more digging. I realized I could use ctypes.create_string_buffer to create a binary string of the perfect size and then use slices to change the values.
There are more tiny optimizations and code cleanups I can do but this has taken it from a script that can easily take several minutes to complete on a 900x900 pixel image, to just a few seconds.
Is this the best option? No idea, but it works. And it's faster than I had thought possible. See the edited code here. The changes are minor.
rowSizeMask = calcRowSize(24,bmp.header.bV5Width) #returns bytes per row needed
paddingLength = (rowSizeMask = bmp.header.bV5Width*3)
rowMaskPadding = b'\x00'*paddingLength #creates padding bytes
writeOffset = 0
#create pixel buffer
#rowsize mask includes padding, multiply by height for total byte count
newBmp.pixels = ctypes.create_string_buffer(bmp.heaer.bV5Height * rowSizeMask)
#loop over image data
for y in range(bmp.header.bV5Height):
for x in range(bmp.header.bV5Width):
offset, color = unpack(offset,">L",buff) #calls struct.unpack in custom function
color = color[0] & bmp.header.bV5AlphaMask #gets alpha channel
newBmp.pixels[writeOffset:writeOffset+3] = struct.pack(">3B", color,color,color) #creates 24bpp listing
writeOffset += 3
newBmp.pixels += rowMaskPadding #pad row to meet BMP specs
writeOffset += paddingLength

Joining multiple huge images with pyvips

I'm trying to figure out how to join multiple images with vips via python. I have in a folder lets say 30 ( but can be more than 600 ) png files that are stripes, they have resolution 854x289920 ( all the same resolution )...
PIL in python will immediately die if I try join them together horizontally with MemmoryError. So I google around and found VIPS which can do both things I need join the images and make deep zoom image from result.
Unfortunately I'm not sure how to correctly join them horizontally in python.
I have in array a list of images from folder, but how would I loop through them and sequentially write joined image out to disk ?
Just for reference, you can also do this at the command line. Try:
vips arrayjoin "a.png b.png c.png" mypyr.dz --across 3
Will join three PNG images horizontally and save the result as a deepzoom pyramid called mypyr. The arrayjoin docs have all of the options:
https://www.libvips.org/API/current/libvips-conversion.html#vips-arrayjoin
You can give the pyramid builder parameters by enclosing them in square brackets after the .dz.
vips arrayjoin "a.png b.png c.png" mypyr.dz[overlap=0,container=zip] --across 3
On Windows, deepzoom pyramids can be very slow to write since Windows hates creating files, and hates huge directories. If you write with container=zip, vips will directly create a .zip file containing the pyramid. This makes pyramid creation around 4x faster.
This also seems to be working fine for opening large number of images and doing joinarray on them so they are next to each other. Thanks #user894763
import os
import pyvips
# natsort helps with sorting the list logically
from natsort import natsorted
source = r"E:/pics/"
output = r"E:/out/"
save_to = output + 'final' + '.tif'
# define list of pictures we are going to get from folder
list_of_pictures = []
# get the
for x in os.listdir(source):
list_of_pictures.append(source + x)
# list_of_pictures now contains all the images from folder including full path
# since os.listdir will not guarantee proper order of files we use natsorted to do it
list_of_pictures = natsorted(list_of_pictures)
array_images = []
image = None
# lets create array of the images for joining, using sequential so it use less ram
for i in list_of_pictures:
tile = pyvips.Image.new_from_file(i, access="sequential")
array_images.append(tile)
# Join them, across is how many pictures there be next to each other, so i just counted all pictures in array with len
out = pyvips.Image.arrayjoin(array_images, across=len(list_of_pictures))
# write it out to file....
out.write_to_file(save_to, Q=95, compression="lzw", bigtiff=True)
I figured it out with some questions still:
import pyvips
list_of_pictures = []
for x in os.listdir(source):
list_of_pictures.append(source + x)
image = None
for i in list_of_pictures:
tile = pyvips.Image.new_from_file(i, access="sequential")
image = tile if not image else image.join(tile, "horizontal")
image.write_to_file(save_to)
which yes, produce tif with joined pictures... but holly cow ! the original pictures are png (30x ) all together 4.5GB the result tiff is 25GB ! what gives, why is there such a huge size difference ?

Categories

Resources