skimage.io.imread behaving differently when giving a string or Path - python

I just stumbled upon a weird situation with skimage.io.imread.
I was trying to open a MultiPage TIFF (dimensions: 96x512x512) like this:
import argparse
from pathlib import Path
import numpy as np
from skimage import io
def numpy_array_from_file(path):
""" method to load numpy array from tiff file"""
im_data = io.imread(path)
print ("image dimensions {}".format(im_data.shape))
return im_data
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Extract page from a MultiPage TIFF")
parser.add_argument("tiff_file", type=str, help="3D TIFF file to open)")
args = parser.parse_args()
tiff_file = Path(args.tiff_file).absolute()
numpy_array_from_file(tiff_file)
And I was obtaining in the output:
image dimensions (512, 512)
After trying many different things (because I was sure that my input image had 96 pages), I discovered that the problem was to use directly Path in the numpy_array_from_file instead of using a string. By changing the last line to:
numpy_array_from_file(str(tiff_file))
I got the expected:
image dimensions (96, 512, 512)
So, my question is ... Anyone know why I had that behaviour? I am not very experienced in python, but I would have expected to obtain an error if Path was not appropriate in that situation.

Indeed, pathlib.Path is relatively new, so support in scikit-image is generally patchy. What's happening is that, because the Path is not a string, the extension isn't checked, and imageio is used instead of tifffile. The behavior of imread is different for the two libraries, with imageio preferring to only return a single plane.
This is a known issue in our issue tracker. Until it's fixed, you can use str() or explicitly call plugin='tifffile'.

Related

Writing seemingly same array to two image files leads to different results

I'm trying to read and write tiff-images with the PIL library. While testing, I noticed that saving the seemingly same numpy array generated in different ways leads to different images on disk. Why is this and how can I fix it?
For testing purposes, I created an image with GIMP (upscaled from 8x8) which I saved as TIF, read to a numpy array and wrote back to a tif file:
img_gimp = Image.open('img_gimp.tif')
imgarray_gimp = np.array(img_gimp)
img_gimp = Image.fromarray(imgarray_gimp, mode = 'I;16')
img_gimp.save('final_gimp.tif')
The result is as expected, it is the same image as the original. So far so good.
Now I generated the same image directly in python code:
imgarray_direct = np.zeros(shape = (8, 8)).astype(int)
for i in range(2):
for j in range(2):
image[i][j] = 65535
Writing this array to disk...
img_direct = Image.fromarray(imgarray_direct, mode = 'I;16')
img_direct.save('final_direct.tif')
doesn't give me the expected result, instead I find this:
image generated by for loop (upscaled from 8x8)
Doing
print(np.array_equal(imgarray_gimp, imgarray_direct))
gives True, and looking at print(imgarray_gimp) and print(imgarray_direct), one cannot see any difference.
Is this intended behaviour? If yes, whats the reason for it?
Thank you for your answers!
As #MarkSetchell hinted in the comments, the issue is that your dtype for the numpy array of raw data does not match the PIL image mode string you are supplying afterwards. Changing the parameter passed into astype or simply passing in the right type on array creation fixed this issue for me. Here is what the modified code looks like:
import numpy as np
from PIL import Image
#Generate raw image data (16-bit!)
image_data = np.zeros(shape = (8, 8), dtype=np.uint16)#this is the big change
for i in range(2):
for j in range(2):
image_data[i][j] = 65535
#Save image as TIF to disk
image_direct = Image.fromarray(image_data, mode = 'I;16')
image_direct.save('final_direct.tif')
As a side note, I am surprised that the mode string I;16 you have used is valid; I could not find any mention about it in pillow's documentation.

Is there a way to examine how much memory an image is occupying with python?

pillow provides size to examine the resolution of an image.
>> from PIL import Image
>> img = Image.open('Lenna.png')
>> img.size
(512, 512)
is there a way to examine how many memory the image is occupying? is the image using 512*512*4 Bytes memory?
You could use the sys library to get the size of an object in bytes. The difference with Kai's answer is that he's calculating the size of the image on the disk, while this calculates the size of the loaded python object (with all its metadata):
import sys
sys.getsizeof(img)
EDIT: After seeing this website, sys.getsizeof() seems to work mainly for primitive types.
You could have a look at a more thorough implementation (deep_getsizeof()) here .
This post gives also a lot of details.
And finally, there is also the pympler library that provides tools to calculate the RAM memory used by an object.
from pympler import asizeof
asizeof.asizeof(img)
import os
print os.stat('somefile.ext').st_size
or
import os
os.path.getsize('path_to_file.jpg')`

OpenCV python canny Required argument 'threshold2' (pos 4) not found

I am trying to isolate text from an image with openCV before sending it to tesseract4 engine to maximize results.
I found this interesting post and I decided to copy the source and try by mysdelf
However I am getting issue with the first call to OpenCV
To reproduce:
Simply copy the code from the gist
launch command script.py /path/to/image.jpg
I am getting issue:
Required argument 'threshold2' (pos 4) not found
Do you maybe have an idea of what does it means.
I am a javascript, java and bash script developer but not python...
In a simple version:
import glob
import os
import random
import sys
import random
import math
import json
from collections import defaultdict
import cv2
from PIL import Image, ImageDraw
import numpy as np
from scipy.ndimage.filters import rank_filter
if __name__ == '__main__':
if len(sys.argv) == 2 and '*' in sys.argv[1]:
files = glob.glob(sys.argv[1])
random.shuffle(files)
else:
files = sys.argv[1:]
for path in files:
out_path = path.replace('.jpg', '.crop.png')
if os.path.exists(out_path): continue
orig_im = Image.open(path)
edges = cv2.Canny(np.asarray(orig_im), 100, 200)
Thanks in advance for your help
Edit: okay so this answer is apparently wrong, as I tried to send my own 16-bit int image into the function and couldn't reproduce the results.
Edit2: So I can reproduce the error with the following:
from PIL import Image
import numpy as np
import cv2
orig_im = Image.open('opencv-logo2.png')
threshold1 = 50
threshold2 = 150
edges = cv2.Canny(orig_im, 50, 100)
TypeError: Required argument 'threshold2' (pos 4) not found
So if the image was not cast to an array, i.e., the Image class was passed in, I get the error. The PIL Image class is a class with a lot of things other than the image data associated to it, so casting to a np.array is necessary to pass into functions. But if it was properly cast, everything runs swell for me.
In a chat with Dan MaĊĦek, my idea below is a bit incorrect. It is true that the newer Canny() method needs 16-bit images, but the bindings don't look into the actual numpy dtype to see what bit-depth it is to decide which function call to use. Plus, if you try to actually send a uint16 image in, you get a different error:
edges = cv2.Canny(np.array([[0, 1234], [1234, 2345]], dtype=np.uint16), 50, 100)
error: (-215) depth == CV_8U in function Canny
So the answer I originally gave (below) is not the total culprit. Perhaps you accidentally removed the np.array() casting of the orig_im and got that error, or, something else weird is going on.
Original (wrong) answer
In OpenCV 3.2.0, a new method for Canny() was introduced to allow users to specify their own gradient image. In the original implementation, Canny() would use the Sobel() operator for calculating the gradients, but now you could calculate say the Scharr() derivatives and pass those into Canny() instead. So that's pretty cool. But what does this have to do with your problem?
The Canny() method is overloaded. And it decides which function you want to use based on the arguments you send in. The original call for Canny() with the required arguments looks like
cv2.Canny(image, threshold1, threshold2)
but the new overloaded method looks like
cv2.Canny(grad_x, grad_y, threshold1, threshold2)
Now, there was a hint in your error message:
Required argument 'threshold2' (pos 4) not found
Which one of these calls had threshold2 in position 4? The newer method call! So why was that being called if you only passed three args? Note that you were getting the error if you used a PIL image, but not if you used a numpy image. So what else made it assume you were using the new call?
If you check the OpenCV 3.3.0 Canny() docs, you'll see that the original Canny() call requires an 8-bit input image for the first positional argument, whereas the new Canny() call requires a 16-bit x derivative of input image (CV_16SC1 or CV_16SC3) for the first positional argument.
Putting two and two together, PIL was giving you a 16-bit input image, so OpenCV thought you were trying to call the new method.
So the solution here, if you wanted to continue using PIL, is to convert your image to an 8-bit representation. Canny() needs a single-channel (i.e. grayscale) image to run, first off. So you'll need to make sure the image is single-channel first, and then scale it and change the numpy dtype. I believe PIL will read a grayscale image as single channel (OpenCV by default reads all images as three-channel unless you tell it otherwise).
If the image is 16-bit, then the conversion is easy with numpy:
img = (img/256).astype('uint8')
This assumes img is a numpy array, so you would need to cast the PIL image to ndarray first with np.array() or np.asarray().
And then you should be able to run Canny() with the original function call.
The issue was coming from an incompatibility between interfaces used and openCV version.
I was using openCV 3.3 so the correct way to call it is:
orig_im = cv2.imread(path)
edges = cv2.Canny(orig_im, 100, 200)

Using python to save a JPG image that was edited in the script

Referring to the answer to this question, I tried to save my own JPG image files, after some basic image processing. I've only applied a rotation and a shear. This is my code:
import numpy as np
import sys
from skimage import data, io, filter, color, exposure
import skimage.transform as tf
from skimage.transform import rotate, setup, AffineTransform
from PIL import Image
mypath = PATH_TO_FILENAME
readfile = FILENAME
img = color.rgb2gray(io.imread(mypath + readfile))
myimg = rotate(img, angle=10, order=2)
afine_tf = tf.AffineTransform(shear=0.1)
editedimg = tf.warp(myimg, afine_tf)
# IF I UNCOMMENT THE TWO LINES BELOW, I CAN SEE THE EDITED IMAGE AS EXPECTED
#io.imshow(editedimg)
#io.show()
saveimg= np.array(editedimg)
result = Image.fromarray((saveimg).astype(np.uint8))
newfile = "edited_" + readfile
result.save(path+newfile)
I know that the image processing was fine because if I display it before saving, it's just the original image with a bit of rotation and shearing, as expected. But I'm doing something wrong while saving it. I tried without the astype(np.uint8)) part but got an error. Then I removed some of the code from the link mentioned above because I guessed it was particularly for Fourier Transforms, since when I included some of their code, then I got an image that was all gray but with white lines in the direction of the shear I'd applied. But now the image that gets saved is just 2KB of nothing but blackness.
And when I tried something as simple as:
result = Image.fromarray(editedimg)
result.save(path+newfile)
then I got this error:
raise IOError("cannot write mode %s as JPEG" % im.mode)
IOError: cannot write mode F as JPEG
I don't really need to use PIL, if there's another way to simply save my image, I'm fine with that.
Look into the PIL fork, Pillow, is is not as outdated and what you should probably be using for this.
Also depending on your operating system you may need a few other libraries to compile PIL with JPEG support properly, see here
This may also help Says you need to convert your image to RGB mode before saving.
Image.open('old.jpeg').convert('RGB').save('new.jpeg')

Converting .jpg images to .png

I've looked around and read the docs, and found no way or solution, so I ask here. Is there any packages available to use Python to convert a JPG image to a PNG image?
You could always use the Python Image Library (PIL) for this purpose. There might be other packages/libraries too, but I've used this before to convert between formats.
This works with Python 2.7 under Windows (Python Imaging Library 1.1.7 for Python 2.7), I'm using it with 2.7.1 and 2.7.2
from PIL import Image
im = Image.open('Foto.jpg')
im.save('Foto.png')
Note your original question didn't mention the version of Python or the OS you are using. That may make a difference of course :)
Python Image Library: http://www.pythonware.com/products/pil/
From: http://effbot.org/imagingbook/image.htm
import Image
im = Image.open("file.png")
im.save("file.jpg", "JPEG")
save
im.save(outfile, options...)
im.save(outfile, format, options...)
Saves the image under the given filename. If format is omitted, the
format is determined from the filename extension, if possible. This
method returns None.
Keyword options can be used to provide additional instructions to the
writer. If a writer doesn't recognise an option, it is silently
ignored. The available options are described later in this handbook.
You can use a file object instead of a filename. In this case, you
must always specify the format. The file object must implement the
seek, tell, and write methods, and be opened in binary mode.
If the save fails, for some reason, the method will raise an exception
(usually an IOError exception). If this happens, the method may have
created the file, and may have written data to it. It's up to your
application to remove incomplete files, if necessary.
As I searched for a quick converter of files in a single directory, I wanted to share this short snippet that converts any file in the current directory into .png or whatever target you specify.
from PIL import Image
from os import listdir
from os.path import splitext
target_directory = '.'
target = '.png'
for file in listdir(target_directory):
filename, extension = splitext(file)
try:
if extension not in ['.py', target]:
im = Image.open(filename + extension)
im.save(filename + target)
except OSError:
print('Cannot convert %s' % file)
from glob import glob
import cv2
pngs = glob('./*.png')
for j in pngs:
img = cv2.imread(j)
cv2.imwrite(j[:-3] + 'jpg', img)
this url: https://gist.github.com/qingswu/1a58c9d66dfc0a6aaac45528bbe01b82
import cv2
image =cv2.imread("test_image.jpg", 1)
cv2.imwrite("test_image.png", image)
I don't use python myself, but try looking into:
http://www.pythonware.com/products/pil/
import Image
im = Image.open("infile.png")
im.save("outfile.jpg")
(taken from http://mail.python.org/pipermail/python-list/2001-April/700256.html )

Categories