I would like to read several png images by utilizing the ThreadPoolExecutor and cv2.imread.
Problem is that I don't know where to place cv2.IMREAD_UNCHANGED tag/argument to preserve alpha channel (transparency).
The following code works but alpha channel is lost. Where should I place the cv2.IMREAD_UNCHANGED argument?
import cv2
import concurrent.futures
images=["pic1.png", "pic2.png", "pic3.png"]
images_list=[]
with concurrent.futures.ThreadPoolExecutor() as executor:
images_list=list(executor.map(cv2.imread,images))
For example, the following return an error:
SystemError: <built-in function imread> returned NULL without setting an error
import cv2
import concurrent.futures
images=["pic1.png", "pic2.png", "pic3.png"]
images_list=[]
with concurrent.futures.ThreadPoolExecutor() as executor:
images_list=list(executor.map(cv2.imread(images,cv2.IMREAD_UNCHANGED)))
Use a lambda that accepts one argument img and pass the argument to the imread function along with the cv2.IMREAD_UNCHANGED.
import cv2
import concurrent.futures
images=["pic1.png", "pic2.png", "pic3.png"]
images_list=[]
with concurrent.futures.ThreadPoolExecutor() as executor:
images_list=list(executor.map(lambda img: cv2.imread(img, cv2.IMREAD_UNCHANGED),images))
One way of doing this is using functools.partial() which you can consider to be a function with its parameters "partially pre-filled":
#!/usr/bin/env python3
import cv2
import glob
from functools import partial
from multiprocessing.pool import ThreadPool
# List of image names
imageNames=glob.glob("*.png")
# Define a partially complete function where some parameters are pre-filled
loader = partial(cv2.imread, flags=cv2.IMREAD_UNCHANGED)
with ThreadPool() as pool:
images = list(pool.map(loader, imageNames))
Note that, in general, especially with images which tend to take a lot of memory, it is a poor idea to load large numbers of images all at the same time into lists in order to process them because you create exceptional strain on the memory of your computer.
So, say you want to identify exceptionally dark or light images, or images with lots of red in them, it is better to run a bunch of threads that each load one image, process it and then move to the next image than to accumulate all images in memory before processing them.
Related
My main aim is to read in around 16k images for a Data science project and I am barely able to perform that serially.
I have performed some parallelization in c++, but I am unfamiliar with using it in python. Essentially, all I need is to parallelize a for loop that calls a function that reads in the image using the matplotlib.image package and returns the image object. I then simply append that object to list. Here is the function,
def read_img(name):
try:
img = mpimg.imread(name)
return img
except:
return("Did not find image")
I ran my code for 100, 1000 and then 5000 images in one go to see if it can run at all, and it ran fine until I ran it for 5000 and my jupyter notebook just crashed. My system has 24gb ram and 12 cores so I def need to find a way to parallelize this.
I know there are 2 modules in python for parallelization, multiprocessing and joblib but I am not sure how to approach this problem which I know is very basic but any guidance would be much appreciated.
You can use the python ThreadPoolExecutor link
Here is the general program which is not perfect but if you fill this should work
# import or some variable from your code mpimg
def read_img(name):
try:
img = mpimg.imread(name)
return img
except:
return("Did not find image")
from concurrent.futures import ThreadPoolExecutor, as_completed
# suppose the files contains th 16k file names
files = ['f1.jpg', 'f2.jpg']
future_to_file = {}
images_read = []
with ThreadPoolExecutor(max_workers=4) as executor:
for file in files:
future = executor.submit(read_img, file)
future_to_file[future] = file
for future in as_completed(future_to_file):
file = future_to_file[future]
img_read = future.result()
if img_read != 'Did not find image':
images_read.append((file, img_read))
I would like to find some existing code/library for sharpness/blurriness estimation on normal images. (prefer in Python) I will need to compare the performance of different algorithms later.
I have 10000+ MRI scan images with different "quality"(sharpness/blurriness). I need to write code to filter images with certain "quality"(sharpness/blurriness) which is up to user. Hence, I am trying to research about image sharpness/blurriness estimation on medical images. My supervisor told me there are lots of existing code for sharpness/blurriness estimation on normal images(maybe it is no-reference sharpness metric) on internet. She asked me to search about them and try them on normal images first. Then try to learn about their algorithms.
I have searched about this on internet and found some pages which are relevant. However, lots of them are out of date.
For example:
On
Image sharpness metric
page,
Cumulative probability of blur detection (CPBD) https://ivulab.asu.edu/software/quality/cpbd
seems not working anymore. I guess the reason is that "imread" function is removed from new "scipy" library. (please see later code and error message) I think I can try the old version of "scipy" later. However, I would like to find some more currently available code/library about image sharpness/blurriness estimation.
Also, my working environment will be in Windows 10 or CentOS-7.
I have tried the following code with CPBD:
import sys, cpbd
from scipy import ndimage
input_image1 = ndimage.imread('D:\Work\Project\scripts\test_images\blur1.png', mode='L')
input_image2 = ndimage.imread('D:\Work\Project\scripts\test_images\clr1.png', mode='L')
print("blurry image sharpness:")
cpbd.compute(input_image1)
print("clear image sharpness:")
cpbd.compute(input_image2)
Error message from Python 3.7 shell (ran in Window 10):
Traceback (most recent call last):
File "D:\Work\Project\scripts\try_cpbd.py", line 1, in <module>
import sys, cpbd
File "D:\Program_Files_2\Python\lib\site-packages\cpbd\__init__.py", line 3, in <module>
from .compute import compute
File "D:\Program_Files_2\Python\lib\site-packages\cpbd\compute.py", line 14, in <module>
from scipy.misc import imread #Original: from scipy.ndimage import imread
ImportError: cannot import name 'imread' from 'scipy.misc' (D:\Program_Files_2\Python\lib\site-packages\scipy\misc\__init__.py)
Seems that cpbd package has not been updated from some time.
It worked for me with the following steps:
Edit "D:\Program_Files_2\Python\lib\site-packages\cpbd\compute.py":
Comment the last 4 lines starting with:
#if __name__ == '__main__':
Use the python code:
import cpbd
import cv2
input_image1 = cv2.imread('blur1.png')
if input_image1 is None:
print("error opening image")
exit()
input_image1 = cv2.cvtColor(input_image1, cv2.COLOR_BGR2GRAY)
print("blurry image sharpness:")
cpbd.compute(input_image1)
Since scipy.misc.imread is deprecated since 1.0.0, and removed in 1.2.0, I would use skimage.io.imread instead (which is in most ways a drop-in replacement).
Edit the code in cpbd/compute.py
import skimage.io
input_image1 = skimage.io.imread('blur1.png')
cv2 also works (or other options: imageio, PIL, ...) but skimage tends to be a bit easier to install/use.
The following steps worked for me:
Open the compute.py from C:\ProgramData\Anaconda3\Lib\site-packages\cpbd\compute.py or wherever you have installed it. You will find the following code:
from scipy.ndimage import imread
replace it with:
from skimage.io import imread
If you can't save the compute.py file, then copy it to desktop, edit it in the above mentioned way and replace the file in C:\ProgramData\Anaconda3\Lib\site-packages\cpbd\compute.py with it.
Following the answer from Baj Mile, I did the following and it worked for me.
opened the cpbd\compute.py file
commented the line : from scipy.ndimage import imread
Added the line: import cv2
Made the following changes to the main section:
if __name__ == '__main__':
#input_image = imread(argv[1], mode='L')
input_image=cv2.imread(argv[1])
sharpness = compute(input_image)
print('CPBD sharpness for %s: %f' % (argv[1], sharpness))
close the compute.py file.
In the main code:
import cpbd
import cv2
input_image1 = cv2.imread('testimage.jpg')
input_image1 = cv2.cvtColor(input_image1, cv2.COLOR_BGR2GRAY)
cpbd.compute(input_image1)
I'm writing a Python(3.4.3) program that uses VIPS(8.1.1) on Ubuntu 14.04 LTS to read many small tiles using multiple threads and put them together into a large image.
In a very simple test :
from concurrent.futures import ThreadPoolExecutor
from multiprocessing import Lock
from gi.repository import Vips
canvas = Vips.Image.black(8000,1000,bands=3)
def do_work(x):
img = Vips.Image.new_from_file('part.tif') # RGB tiff image
with lock:
canvas = canvas.insert(img, x*1000, 0)
with ThreadPoolExecutor(max_workers=8) as executor:
for x in range(8):
executor.submit(do_work, x)
canvas.write_to_file('complete.tif')
I get correct result. In my full program, the work for each thread involves read binary from a source file, turn them into tiff format, read the image data and insert into canvas. It seems to work but when I try to examine the result, I ran into trouble. Because the image is extremely large(~50000*100000 pixels), I couldn't save the entire image in one file, so I tried
canvas = canvas.resize(.5)
canvas.write_to_file('test.jpg')
This takes extremely long time, and the resulting jpeg has only black pixels. If I do resize three times, the program get killed. I also tried
canvas.extract_area(20000,40000,2000,2000).write_to_file('test.tif')
This results in error message segmentation fault(core dumped) but it does save an image. There are image contents in it, but they seem to be in the wrong place.
I'm wondering what the problem could be?
Below are the codes for the complete program. The same logic was also implemented using OpenCV + sharedmem (sharedmem handled the multiprocessing part) and it worked without a problem.
import os
import subprocess
import pickle
from multiprocessing import Lock
from concurrent.futures import ThreadPoolExecutor
import threading
import numpy as np
from gi.repository import Vips
lock = Lock()
def read_image(x):
with open(file_name, 'rb') as fin:
fin.seek(sublist[x]['dataStartPos'])
temp_array = np.fromfile(fin, dtype='int8', count=sublist[x]['dataSize'])
name_base = os.path.join(rd_path, threading.current_thread().name + 'tempimg')
with open(name_base + '.jxr', 'wb') as fout:
temp_array.tofile(fout)
subprocess.call(['./JxrDecApp', '-i', name_base + '.jxr', '-o', name_base + '.tif'])
temp_img = Vips.Image.new_from_file(name_base + '.tif')
with lock:
global canvas
canvas = canvas.insert(temp_img, sublist[x]['XStart'], sublist[x]['YStart'])
def assemble_all(filename, ramdisk_path, scene):
global canvas, sublist, file_name, rd_path, tilesize_x, tilesize_y
file_name = filename
rd_path = ramdisk_path
file_info = fetch_pickle(filename) # A custom function
# this info includes where to begin reading image data, image size and coordinates
tilesize_x = file_info['sBlockList_P0'][0]['XSize']
tilesize_y = file_info['sBlockList_P0'][0]['YSize']
sublist = [item for item in file_info['sBlockList_P0'] if item['SStart'] == scene]
max_x = max([item['XStart'] for item in file_info['sBlockList_P0']])
max_y = max([item['YStart'] for item in file_info['sBlockList_P0']])
canvas = Vips.Image.black((max_x+tilesize_x), (max_y+tilesize_y), bands=3)
with ThreadPoolExecutor(max_workers=4) as executor:
for x in range(len(sublist)):
executor.submit(read_image, x)
return canvas
The above module (imported as mcv) is called in the driver script :
canvas = mcv.assemble_all(filename, ramdisk_path, 0)
To examine the content, I used
canvas.extract_area(25000, 40000, 2000, 2000).write_to_file('test_vips1.jpg')
I think your problem has to do with the way libvips calculates pixels.
In systems like OpenCV, images are huge areas of memory. You perform a series of operations, and each operation modifies a memory image in some way.
libvips is not like this, though the interface looks similar. In libvips, when you perform an operation on an image, you are actually just adding a new section to a pipeline. It's only when you finally connect the output to some sink (a file on disk, or a region of memory you want filled with image data, or an area of the display) that libvips will actually do any calculations. libvips will then use a recursive algorithm to run a large set of worker threads up and down the whole length of the pipeline, evaluating all of the operations you created at the same time.
To make an analogy with programming languages, systems like OpenCV are imperative, libvips is functional.
The good thing about the way libvips does things is that it can see the whole pipeline at once and it can optimise away most of the memory use and make good use of your CPU. The bad thing is that long sequences of operations can need large amounts of stack to evaluate (whereas with systems like OpenCV you are more likely to be bounded by image size). In particular, the recursive system used by libvips to evaluate means that pipeline length is limited by the C stack, about 2MB on many operating systems.
Here's a simple test program that does more or less what you are doing:
#!/usr/bin/python3
import sys
import pyvips
if len(sys.argv) < 4:
print "usage: %s image-in image-out n" % sys.argv[0]
print " make an n x n grid of image-in"
sys.exit(1)
tile = pyvips.Image.new_from_file(sys.argv[1])
outfile = sys.argv[2]
size = int(sys.argv[3])
img = pyvips.Image.black(size * tile.width, size * tile.height, bands=3)
for y in range(size):
for x in range(size):
img = img.insert(tile, x * size, y * size)
# we're not interested in huge files for this test, just write a small patch
img.crop(10, 10, 100, 100).write_to_file(outfile)
You run it like this:
time ./bigjoin.py ~/pics/k2.jpg out.tif 2
real 0m0.176s
user 0m0.144s
sys 0m0.031s
It loads k2.jpg (a 2k x 2k JPG image), repeats that image into a 2 x 2 grid, and saves a small part of it. This program will work well with very large images, try removing the crop and running as:
./bigjoin.py huge.tif out.tif[bigtiff] 10
and it'll copy the huge tiff image 100 times into a REALLY huge tiff file. It'll be quick and use little memory.
However, this program will become very unhappy with small images being copied many times. For example, on this machine (a Mac), I can run:
./bigjoin.py ~/pics/k2.jpg out.tif 26
But this fails:
./bigjoin.py ~/pics/k2.jpg out.tif 28
Bus error: 10
With a 28 x 28 output, that's 784 tiles. The way we've built the image, repeatedly inserting a single tile, that's a pipeline 784 operations long -- long enough to cause a stack overflow. On my Ubuntu laptop I can get pipelines up to about 2,900 operations long before it starts failing.
There's a simple way to fix this program: build a wide rather than a deep pipeline. Instead of inserting a single image each time, make a set of strips, then join the strips. Now the pipeline depth will be proportional to the square root of the number of tiles. For example:
img = pyvips.Image.black(size * tile.width, size * tile.height, bands=3)
for y in range(size):
strip = pyvips.Image.black(size * tile.width, tile.height, bands=3)
for x in range(size):
strip = strip.insert(tile, x * size, 0)
img = img.insert(strip, 0, y * size)
Now I can run:
./bigjoin2.py ~/pics/k2.jpg out.tif 200
Which is 40,000 images joined together.
I'm looking for a way to output multiple values using the generic_filter module in scipy.ndimage like so:
import numpy as np
from scipy import ndimage
a = np.array([range(1,5),range(5,9),range(9,13),range(13,17)])
def summary(a):
minVal = np.min(a)
maxVal = np.max(a)
return [minVal,maxVal]
[arrMin, arrMax] = ndimage.generic_filter(a, summary, footprint=np.ones((3,3)))
But I keep getting the error that a float is expected.
I've played with the 'output' parameter, like so:
arrMin = np.zeros(np.shape(a))
arrMax = np.zeros(np.shape(a))
ndimage.generic_filter(a, summary, footprint=np.ones((3,3)), output = [arrMin, arrMax])
to no avail. I've also tried returning a named tuple, a class, or a dictionary, as per this question none of which have worked.
Based on the comments, you want to perform multiple filters simultaneously rather than performing them separately.
Unfortunately I do not think this filter works that way. It expects you to return a single filtered output value for each corresponding input value. I looked for a way to do simultaneous filters with numpy/scipy but couldn't find anything.
If you can manage a data flow that allows you to load the image, filter, process and produce some small result data in separate parallel paths (one for each filter), then you may get some benefit from using multiprocessing but if you use it naively it's likely to take more time than doing everything sequentially. If you really have a bottleneck that multiprocessing solves you should also look into sharing your input array rather than loading it in each process.
I'm finding that in PIL I can load an image from disk substantially more quickly than I can copy it. Is there a faster way to copy an image than by calling image.copy()? (and how is this even possible?)
Sample code:
import os, PIL.Image, timeit
test_filepath = os.path.expanduser("~/Test images/C.jpg")
load_image_cmd = "PIL.Image.open('{}')".format(test_filepath)
print((PIL.Image.open(test_filepath)).__class__)
print(min(timeit.repeat(load_image_cmd, setup='import PIL.Image', number=10000)))
print(min(timeit.repeat("img.copy()", setup='import PIL.Image; img = {}'.format(load_image_cmd), number=10000)))
Produces:
PIL.JpegImagePlugin.JpegImageFile
0.916192054749
1.85366988182
Adding gc.enable to the setup for timeit doesn't change things much.
According to the PIL documentation, open() is a lazy operation, which means that it's not really doing all the work to use the image yet.
To do a copy() however, it almost certainly has to read the whole thing in and process it.
EDIT:
To test whether this is true, you should access a pixel in each image as part of your timeit.
EDIT 2:
Another glance at the doc shows that a load() after the open() ought to do the trick of making it do all its work.