(Live) viewing of large images using pyvips - python

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.

Related

how do i increase the resolution of my gif file?

As I am trying to create a gif file, the file has been created successfully but it is pixelating. So if anyone can help me out with how to increase resolution.
.Here is the code:-
import PIL
from PIL import Image
import NumPy as np
image_frames = []
days = np.arange(0, 12)
for i in days:
new_frame = PIL.Image.open(
r"C:\Users\Harsh Kotecha\PycharmProjects\pythonProject1\totalprecipplot" + "//" + str(i) + ".jpg"
)
image_frames.append(new_frame)
image_frames[0].save(
"precipitation.gif",
format="GIF",
append_images=image_frames[1:],
save_all="true",
duration=800,
loop=0,
quality=100,
)
Here is the Gif file:-
Here are the original images:-
image1
image2
iamge3
Updated Answer
Now that you have provided some images I had a go at disabling the dithering:
#!/usr/bin/env python3
from PIL import Image
# User editable values
method = Image.FASTOCTREE
colors = 250
# Load images precip-01.jpg through precip-12.jpg, quantize to common palette
imgs = []
for i in range(1,12):
filename = f'precip-{i:02d}.jpg'
print(f'Loading: {filename}')
try:
im = Image.open(filename)
pImage = im.quantize(colors=colors, method=method, dither=0)
imgs.append(pImage)
except:
print(f'ERROR: Unable to open {filename}')
imgs[0].save(
"precipitation.gif",
format="GIF",
append_images=imgs[1:],
save_all="true",
duration=800,
loop=0
)
Original Answer
Your original images are JPEGs which means they likely have many thousands of colours 2. When you make an animated GIF (or even a static GIF) each frame can only have 256 colours in its palette.
This can create several problems:
each frame gets a new, distinct palette stored with it, thereby increasing the size of the GIF (each palette is 0.75kB)
colours get dithered in an attempt to make the image look as close as possible to the original colours
different colours can get chosen for frames that are nearly identical which means colours flicker between distinct shades on successive frames - can cause "twinkling" like stars
If you want to learn about GIFs, you can learn 3,872 times as much as I will ever know by reading Anthony Thyssen's excellent notes here, here and here.
Your image is suffering from the first problem because it has 12 "per frame" local colour tables as well as a global colour table3. It is also suffering from the second problem - dithering.
To avoid the dithering, you probably want to do some of the following:
load all images and append them all together into a 12x1 monster image, and find the best palette for all the colours. As all your images are very similar, I think that you'll get away with generating a palette just from the first image without needing to montage all 12 - that'll be quicker
now palettize each image, with dithering disabled and using the single common palette
save your animated sequence of the palletised images, pushing in the singe common palette from the first step above
2: You can count the number of colours in an image with ImageMagick, using:
magick YOURIMAGE -format %k info:
3: You can see the colour tables in a GIF with gifsicle using:
gifsicle -I YOURIMAGE.GIF

Display specific part of tiff image using rasterio without having to load the entire file

I have a large tiff file (around 2GB) containing a map. I have been able to successfully read the data and even display it using the following python code:
import rasterio
from rasterio.plot import show
with rasterio.open("image.tif") as img:
show(img)
data = img.read()
This works just fine. However, I need to be able to display specific parts of this map without having to load the entire file into memory (as it takes up too much of the RAM and is not doable on many other PCs). I tried using the Window class of rasterio in order to that, but when I tried to display the map the outcome was different from how the full map is displayed (as if it caused data loss):
import rasterio
from rasterio.plot import show
from rasterio.windows import Window
with rasterio.open("image.tif") as img:
data = img.read(window=Window(0, 0, 100000, 100000))
show(data)
So my question is, how can I display a part of the map without having to load into memory the entire file, while also making it look as if it had been cropped from the full map image?
thanks in advance :)
The reason that it displays nicely in the first case, but not in the second, is that in the first case you pass an instance of rasterio.DatasetReader to show (show(img)), but in the second case you pass in a numpy array (show(data)). The DatasetReader contains additional information, in particular an affine transformation and color interpretation, which show uses.
The additional things show does in the first case (for RGB data) can be recreated for the windowed case like so:
import rasterio
from rasterio.enums import ColorInterp
from rasterio.plot import show
from rasterio.windows import Window
with rasterio.open("image.tif") as img:
window = Window(0, 0, 100000, 100000)
# Lookup table for the color space in the source file
source_colorinterp = dict(zip(img.colorinterp, img.indexes))
# Read the image in the proper order so the numpy array will have the colors in the
# order expected by matplotlib (RGB)
rgb_indexes = [
source_colorinterp[ci]
for ci in (ColorInterp.red, ColorInterp.green, ColorInterp.blue)
]
data = img.read(rgb_indexes, window=window)
# Also pass in the affine transform corresponding to the window in order to
# display the correct coordinates and possibly orientation
show(data, transform=img.window_transform(window))
(I figured out what show does by looking at the source code here)
In case of data with a single channel, the underlying matplotlib library used for plotting scales the color range based on the min and max value of the data. To get exactly the same colors as before, you'll need to know the min and max of the whole image, or some values that come reasonably close.
Then you can explicitly tell matplotlib's imshow how to scale:
with rasterio.open("image.tif") as img:
window = Window(0, 0, 100000, 100000)
data = img.read(window=window, masked=True)
# adjust these
value_min = 0
value_max = 255
show(data, transform=img.window_transform(window), vmin=value_min, vmax=value_max)
Additional kwargs (like vmin and vmax here) will be passed on to matplotlib.axes.Axes.imshow, as documented here.
From the matplotlib documenation:
vmin, vmax: float, optional
When using scalar data and no explicit norm, vmin and vmax define the data range that the colormap covers. By default, the colormap covers the complete value range of the supplied data. It is deprecated to use vmin/vmax when norm is given. When using RGB(A) data, parameters vmin/vmax are ignored.
That way you could also change the colormap it uses etc.

How to save an .EPS file to PNG with transparency in Python

I'm building a Paint-like app Since I want the freedom to reposition and modify the shape properties later, I am using Tkinter to draw shapes on Canvas instead of PIL Draw or anything else. From other answers, I found how to save a canvas as PNG by 1st creating a postscript file and then converting it to PNG using PIL.
Now the problem is the EPS file has transparent spaces but the PNG file fills those voids with a White background color. I'm not sure where I am going wrong.
Below is the function I used.
def saveImg(event):
global canvas
canvas.postscript(file="my_drawing.eps", colormode='color')
imgNew = Image.open("my_drawing.eps")
imgNew.convert("RGBA")
imgNew.thumbnail((2000,2000), Image.ANTIALIAS)
imgNew.save('testImg.png', quality=90)
Looks like transparency is not supported. From the docs:
The EPS driver can read EPS images in L, LAB, RGB and CMYK mode, but Ghostscript may convert the images to RGB mode rather than leaving them in the original color space.
When you load in RGB (instead of RGBA) the alpha channel information is discarded and converting it to RGBA later will not recover it.
Your best shot is porting it to more recent toolkits like cairo or QT or converting the file using GhostScript directly as suggested by PM2Ring.
For the GS approach in order to set the width and height of the output file you must use the -rN switch where N is the resolution in PPI (pixels per inch). You must do the math in order to get target resolution from the EPS bounding box and the desired output size.
Or you can render to a fixed resolution first, lets say, 100 PPI, see the width you got and do the math in order to get the correct resolution. For example, if rendering with -r100 gives you a file 500 pixels wide but you want it to be 1024:
desired_resolution = initial_resolution * desired_width // initial_width
In order to get a file 1024 pixels wide:
>>> 100 * 1024 // 500
204
So you must render the EPS again using -r204.
Edit 1:
I got the solution from this Question
We can set custom width and height using -gNNNNxMMMM
but the dpi value crops only a small area. I tried with the usual 72dpi and I got a decent output(I'm not sure if it's perfect or not). Now I need to find how to execute this command every time when I run the program and provide the custom image size value. :\

Pygame, set transparency on an image imported using convert_alpha()

in my pygame game, to import jpeg image, I use convert()
http://www.pygame.org/docs/ref/surface.html#pygame.Surface.convert
then, to play with the image transparency (how much we can see trough the image), I use set_alpha()
http://www.pygame.org/docs/ref/surface.html#pygame.Surface.set_alpha
However, to import my png image, which have a tranparent background, I use convert_alpha()
http://www.pygame.org/docs/ref/surface.html#pygame.Surface.convert_alpha
but with this way of importing, I can't play with the general transparency using set_alpha(). Any other idea to adjust the transparency (how much we see trough the image) ?
When you read the documentation for set_alpha you can read this :
If the Surface format contains per pixel alphas, then this alpha value will be ignored.
%In your case, with a png image, it's a per pixel alphas. So, you must manage alpha "per pixel". For example, you can do that (not the best code, but easy to understand. Work with png with no/yes transparency only):
def change_alpha(img,alpha=255):
width,height=img.get_size()
for x in range(0,width):
for y in range(0,height):
r,g,b,old_alpha=img.get_at((x,y))
if old_alpha>0:
img.set_at((x,y),(r,g,b,alpha))
Be carefull, it's "slow", because you manage each pixel which is not at 0 (transparent from your png).
If your png has multiple level of transparency, you should manage the transparency with a better formula, like this:
r,g,b,old_alpha=img.get_at((x,y))
img.set_at((x,y),(r,g,b,(alpha*old_alpha)/255))
And in this case, never modify the original image, but work on a copy to never lose your original alpha.
I hope it will help
===================== EDIT ===================
Add some optimisation because asking in comment
With some caching methodology:
class image_with_alpha(object):
def __init__(self,name=None):
self.img=None
self.alpha={}
if name:
self.load_image(name)
def load_image(self,name):
self.img=pygame.image.load(name)
self.alpha[255]=self.img
#self.pre_compute_alpha()
def pre_compute_alpha(self):
for alpha in range(0,10):
self.alpha[alpha]=change_alpha(self.img,alpha)
def get_img(self,a=255):
try:
return self.alpha[a]
except:
self.alpha[a]=change_alpha(self.img,a)
return self.alpha[a]
And use it like this :
Load image:
image=image_with_alpha("test.png")
Blit with 60 for alpha:
screen.blit(image.get_img(60),(0,0))
And now, it's fast I hope
The fastest solution is probably to use numpy array manipulation, it should be fast enough to avoid the need for caching. What's really slow about calculating the alpha value pixel-wise is iterating in Python, while numpy does it all in C.
Start out by referencing the image's alpha channel into a numpy array. This will create a lock on the image surface; let's remember that for later. Then take the minimum (pixel-wise) of your original alpha and an array full of ones (that will leave you with an array of only ones and zeros), multiply that (pixel-wise) by your desired alpha and copy the result back to the image's alpha channel (still represented by that array reference). Before you can blit the image to the screen, the array reference to the alpha array must be cleared, releasing the lock on the image surface.
def change_alpha(img, alpha=255):
chan = pygame.surfarray.pixels_alpha(img)
chan2 = numpy.minimum(chan, numpy.ones(chan.shape, dtype=chan.dtype)) * alpha
numpy.copyto(chan, chan2)
del chan
Ok I have the answer for you. Pygame's "convert_alpha()" function does not support per pixel alpha. Based on the docs setting alpha will have no effect. However, you can get around this limitation by doing the following. If you load your image using "convert()", and then setting alpha you can get your image to become transparent. Then all you have to do is use "set_colorkey(background color)" to eliminate the background image. Be carful with colorkey because any color in the image that is set as the colorkey will become transparent. The colorkey does not care about per pixel alpha so you can change the alpha of an image and use colorkey at the same time.
Here is the code...
#Loading Image
image = pygame.image.load("test.png").convert()
#Setting Alpha
image.set_alpha(some desired alpha)
#Set colorkey To Eliminate Background Color
image.set_colorkey(some background color)
I threw this test picture together for testing the code. The image does have transparency around the edges of it.
This is what it looks like blitted onto a green background without the added alpha. The white part was transparent until loaded with ".convert()"
This is the finished look of the image with the whole code applied. The alpha has been stripped and reset, and the colorkey has been set to white because it was the background
I hope this is what you are looking for and hoped my answer helped here.
NOTE* You may want to make a copy of the image before you change its alpha like this without the risk of the image having a "spillover" affect from previous uses.
use set_colorkey(color) to set a transparent color. for example, if you have an image of an apple, and everything but the apple is the color black, you'd use apple.set_colorkey(black), and everything but the apple would be transparent. also, if your having trouble using a jpg image, I'd suggest changing it to a png and then doing .convert().

Video Manipulation

Before a couple days ago I had never used OpenCV or done any video processing. I've been asked to computationally overlay a video based upon some user inputs and build a new video with the overlays incorporated for download in AVI format. Essentially, the goal is to have a form that takes as input 3 images (icon, screenshot #1, screenshot #1) and 3 text inputs and overlays the original video with them. Here is a link to the video. When the video is running you'll notice the icon in the center of the iPhone at the beginning is stretched and pulled. I've been iteratively testing OpenCV methods by breakding down the video frame by frame and doing stuff to each one, then rebuilding (obviously this is probably the only way to successfully rebuild a video with OpenCV with edits, but anyway). this video is one I overlayed a colored circle that moves back and forth with.
# the method I've been using
import cv2 as cv
import numpy as np
cap = cv.VideoCapture('the_vid.avi')
flag, frame = cap.read()
width = np.size(frame,1)
height = np.size(frame,0)
writer = cv.VideoWriter('output.avi', cv.VideoWriter_fourcc('I','4','2','0'), fps=35, (width,height), 1)
while True:
flag, frame = cap.read()
if flag == 0:
break
x = width/2
y = height/2
# add a line or circle or something
origin radius
cv.circle(frame, (x,y), 20, (0,0,255), -1)
# write our new frame
writer.write(frame)
Now we've got an output of this very large uncompressed AVI file which can be compressed using ffmpeg
ffmpeg -i output.avi -vcodec msmpeg4v2 compressed_output.avi
Ok, so that's the method I've been using to rebuild this video, and from that method I'm not seeing it possible to take a static image and stretch it around like is shown in the first 90 frames or so. The only other possibility I saw was maybe doing something like below. If you can tell me if there is even a way to implement this pseudo-code that would be awesome, I'm thinking it will be extremely difficult:
# example for the first image and first few seconds of video only
first_image = cv.imread('user_uploaded_icon.png')
flag, first_frame = cap.read()
# section of the frame that contains the original icon
the_section = algorithm_to_get_array_of_original_icon_in_first_frame(first_frame)
rows, cols = the_section.shape
# somehow find the array within the first image that is the same size as the_section
# containing JUST the icon
icon = array_of_icon(first_image)
# build a blank image with the size of the original icon in the current frame
blank_image = np.zeros((rows,cols,3),np.uint8)
for i in xrange(row):
for j in xrange(col):
blank_image[i,j] = icon[i,j]
What seems like it might not work about this is the fact that the_section in the first_frame will be stretched to different dimensions than the static image...so I'm not sure if there is ANY viable way to handle this. I appreciate all the time saving help in advance.

Categories

Resources