I have a notebook running inside a docker container on a machine with limited ram, with python 3.6. I am trying to load a large number of images using something like the following code.
class DataGenerator(keras.utils.Sequence):
def get_images(self):
img_list = []
loop through the contents of folder:
img = cv2.imread(img_path)
img = cv2.resize(img, (self.img_size, self.img_size))
img = img / 255.0
img = img - np.mean(img)
img_list.append(img)
img_list = np.asarray(img_list, dtype = np.float16)
return img_list
I am running out of ram very quickly and the kernel crashes. There is no other code, I narrowed it down to this part that takes up all the ram. Ideally shouldn't the variables be deleted once the function call is finished?
Initially the code looked like this
class DataGenerator(keras.utils.Sequence):
def get_images(self):
img_list = []
loop through the contents of folder:
img = cv2.imread(img_path)
img = cv2.resize(img, (self.img_size, self.img_size))
img - np.array(img) / 255.0
img = img - np.mean(img)
img_list.append(img)
return np.array(img_list)
But I saw using debugger the list created by this part of the code takes up too much memory and even after the function returns the numpy array, img_list variable is taking up space in the memory, so I assigned the numpy array to the name img_list, hoping that it would remove the reference to the list. Also I changed np.array to np.asarray to convert it into a numpy array inplace instead of creating a copy. I also changed the dtype to np.float16.
After assigning the name img_list to the numpy array, the original list does not show up in debugger anymore, but the amount memory consumed remains the same.
I checked the size of the returned numpy array and it is 1 GB, but this small function call takes up 6 GB of my memory and that memory is not being freed even after I manually call garbage collector using gc.collect().
I am running it on jupyter lab if that is relevant. I have looked everywhere and according to all the answers calling garbage collector manually is not necessary in most cases as pythons garbage collection is reliable. And if it has to be called, calling it should solve all the issues. But for me it doesn't do anything at all. It shows a number of garbage is deleted, but that doesn't free up the ram at all, or even if it does it's negligible.
Related
Iam try to create a large video(longer than 3h)by CompositeVideoClip using moviepy.
The problem is it take too much ram (i have 32gb ram).It takes the whole ram (99%) by create a bunch of ffmpeg-win64-v4.2.2.exe ffmpeg-win64-v4.2.2.exe
after it a while it said Unable to allocate 47.5 MiB for an array with shape (1080, 1920, 3) and data type float64.
here is my code :
def CombieVideo():
global curentVideoLengt
masterVideo = NULL
for videoUrl in videoFiles:
print(videoUrl)
video = VideoFileClip(videoUrl).fx(vfx.fadein,1).fx(vfx.fadeout,1)
curentVideoLengt += video.duration
if curentVideoLengt >= (audioLen*60*60):
break
if masterVideo== NULL:
masterVideo= video
else:
masterVideo = CompositeVideoClip([masterVideo,video])
if curentVideoLengt < (audioLen*60*60):
videoUrl=random.choice(videoFiles)
print(videoUrl)
video =video(videoUrl).fx(vfx.fadein,1).fx(vfx.fadeout,1)
curentVideoLengt= curentVideoLengt+video.duration
masterVideo = CompositeVideoClip([masterVideo,video])
CombieVideo()
else:
masterVideo.audio = CompositeAudioClip(audios)
masterVideo.write_videofile('./MasterVideo/output_video.avi', fps=30, threads=4, codec="png")
CombieVideo()
Your issue isn't running allocating arrays, but rather you're opening too many instances of FFMPEG_VideoReader via VideoFileClip instances. Each of FFMPEG_VideoReader spawns an instance of ffmpeg, which eats up your memory resource.
You can try to close the reader immediately after instantiation:
video = VideoFileClip(videoUrl).fx(vfx.fadein,1).fx(vfx.fadeout,1)
video.close()
# continue as is
No guarantee if this will work, but it should points you in a right direction. This should fix your problem as moviepy documentation of VideoFileClip is suggesting for the immediate closure.
On a side note, do you really want to recurse this function? It's picking one random file then adding more files from the top of the videoFiles list if the random one isn't long enough.
I have a neural network in PyTorch which gives an image as a Tensor. I have converted it to a numpy array and then followed the explanation here to send that image to the html. The problem is that it's always black.
This is my code in the PyTorch:
def getLinearImage():
with torch.no_grad():
test_z = torch.randn(1, 100).to(device)
generated = G(test_z)
generated = generated.cpu()
numpy = generated[0].view(64, 64).numpy()
numpu = (numpy + 1) * 128
return numpy
This is the code in the flask where arr is the returned value from getLinearImage()
def getImage(arr):
img = Image.fromarray(arr.astype("uint8"))
file_object = io.BytesIO()
img.save(file_object, "PNG")
file_object.seek(0)
return send_file(file_object, mimetype="image/PNG")
If I open a static image and I send it to getImage() it works but won't work with the generated one. In the html I call it like:
<img src="/getLinearImage" alt="User Image" height="100px" width="100px">
Logically speaking, since the static image works, the error is somewhere in your getLinearImage code. I would suggest running things through using PDB (or a debugger of your choice) to figure out why it's not generated correctly.
That said, I create a variable in your code:
numpu = (numpy + 1) * 128
which you don't seem to use, since you return the other variable afterwards:
return numpy
Could that be your problem?
Also: I presume that when you created this, you saved the original image locally to ensure something gets generated in the first place?
I'm struggling with a memory issue on Heroku when running a Django application (with gunicorn).
I have the following code that takes a user-uploaded image, removes all EXIF data, and returns the image ready for it to be uploaded to S3. This is used both as a form data cleaner and when reading base64 data into memory.
def sanitise_image(img): # img is InMemoryUploadedFile
try:
image = Image.open(img)
except IOError:
return None
# Move all pixel data into a new PIL image
data = list(image.getdata())
image_without_exif = Image.new(image.mode, image.size)
image_without_exif.putdata(data)
# Create new file with image_without_exif instead of input image.
thumb_io = StringIO.StringIO()
image_without_exif.save(thumb_io, format=image.format)
io_len = thumb_io.len
thumb_file = InMemoryUploadedFile(thumb_io, None, strip_tags(img.name), img.content_type,
io_len, None)
# DEL AND CLOSE EVERYTHING
thumb_file.seek(0)
img.close()
del img
thumb_io.close()
image_without_exif.close()
del image_without_exif
image.close()
del image
return thumb_file
I basically take an InMemoryUploadedFile and return a new one with just the pixel data.
del and closes may be redundant, but they represent my attempt to fix the situation where Heroku memory usage keeps growing and is not released every time this function terminates, even remaining overnight:
Running this on localhost with Guppy and following the tutorial, there are no remaining InMemoryUploadedFiles nor StringIOs nor PIL Image left in the heap, leaving me puzzled.
My suspicion is Python does not release the memory back to the OS, as I've read in multiple threads on SO. Has anyone played around with InMemoryUploadedFile and can give me an explanation as to why this memory is not being released?
When I do not perform this sanitisation, the issue does not occur.
Thanks a lot!
I think the issue is creating the temporary list object:
data = list(image.getdata())
Try:
image_without_exif.putdata(image.getdata())
This is why I think that is the issue:
>>> images = [Image.new('RGBA', (100, 100)) for _ in range(100)]
Python memory usage increased ~4Mb.
>>> get_datas = [image.getdata() for image in images]
No memory increase.
>>> pixel_lists = [list(image.getdata()) for image in images]
Python memory usage increased by ~85Mb.
You probably don't want to make getdata() into a list unless you need the numbers explicitly. From the Pillow docs:
Note that the sequence object returned by this method is an internal PIL data type, which only supports certain sequence operations. To convert it to an ordinary sequence (e.g. for printing), use list(im.getdata()).
I found my own answer eventually. Huge thanks to Ryan Tran for pointing me in the right direction. list() does indeed cause the leak.
Using the equivalent split() and merge() method (docs) this is the updated code:
with Image.open(img) as image:
comp = image.split()
image_without_exif = Image.merge(image.mode, comp)
thumb_io = StringIO.StringIO()
image_without_exif.save(thumb_io, format=image.format)
io_len = thumb_io.len
clean_img = InMemoryUploadedFile(thumb_io, None, strip_tags(img.name), img.content_type,
io_len, None)
clean_img.seek(0)
return clean_img
I have the following minimal code that gets the bytes from an image:
import Image
im = Image.open("kitten.png")
im_data = [pix for pixdata in im.getdata() for pix in pixdata]
This is rather slow (I have gigabytes of images to process) so how could this be sped up? I'm also unfamiliar with what exactly that code is trying to do. All my data is 1280 x 960 x 8-bit RGB, so I can ignore corner cases, etc.
(FYI, the full code is here - I've already replaced the ImageFile loop with the above Image.open().)
You can try
scipy.ndimage.imread()
If you mean speeding up by algorythamically i can suggest you accessing file with multiple threads simultaneously (only if you don't have a connection between processing sequence)
divide file logically by few sections and access each part simultaneously with threads (you have to put your operation inside a function and call it with threads)
here is a link to tutorial about threading in python
threding in python
I solved my problem, I think:
>>> [pix for pixdata in im.getdata() for pix in pixdata] ==
numpy.ndarray.tolist(numpy.ndarray.flatten(numpy.asarray(im)))
True
This cuts down the runtime by half, and with a bit of bash magic I can run the conversion on the 56 directories in parallel.
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.