user3908324
user3908324

Reputation: 15

Creating a good quality grayscale image from an multidimensional numpy array

I have an array that is 50(rows) by 33(columns). I am importing it to a numpy array on python. The values on my array range from 0 to 4096, so I am scaling it to 0-255 and then I trying to create a grayscale image from it, and save it.

    import numpy
    from PIL import Image
    data =  numpy.loadtxt('result.txt',delimiter='\t',dtype=None)
    rescaled = (255.0 / 4096 * (data)).astype(numpy.uint8)
    image = Image.fromarray(rescaled)
    image.save('test_fullsize.jpeg', quality=95)

This does not output any errors, however the produced image is only 50x33 pixels. This means the picture is very small(i can see that it is indeed the type of image I am looking for, however it is very very small)

I tried resizing the image by adding this line of code before I save it:

image = image.resize((480, 640), Image.ANTIALIAS)

However, that outputed a very pixelated(bad quality) 480,640 image. I need this image to be of a decent resolution/size to be able to put it on a poster.

Thank you.

Upvotes: 1

Views: 5640

Answers (4)

JaminSore
JaminSore

Reputation: 3936

From what I understand, you're trying to display the numbers in your matrix as different shades of grey and make it large enough to fit on a poster. So, like @pandita, I would also use Matplotlib as this can be done pretty easily.

import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np

height, width = 50, 33 #in pixels
spines = 'left', 'right', 'top', 'bottom'

labels = ['label' + spine for spine in spines]

tick_params = {spine : False for spine in spines}
tick_params.update({label : False for label in labels})

img = np.random.randint(0, 4097, (height, width))

img *= 255. / 4096

desired_width = 8 #in inches
scale = desired_width / float(width)

fig, ax = plt.subplots(1, 1, figsize=(desired_width, height*scale))
img = ax.imshow(img, cmap=cm.Greys_r, interpolation='none')

#remove spines
for spine in spines:
    ax.spines[spine].set_visible(False)

#hide ticks and labels
ax.tick_params(**tick_params)

#preview
plt.show()

#save
fig.savefig('test.png', dpi=300)

Produces the following image ... enter image description here

Upvotes: 3

Majaha
Majaha

Reputation: 143

You might just be able to zoom in on the smaller version of the file with whatever you're viewing it with, that would be a better solution I think. However, if you really need a higher resolution, try replacing

image = image.resize((480, 640), Image.ANTIALIAS)

with

image = image.resize((480, 640), Image.NEAREST)

or just

image = image.resize((480, 640))

as NEAREST is the default for this method.

The second parameter to the resize method is the interpolation/filter used when resizing. ANTIALIAS is a filter that reduces aliasing artifacts when downsampling. NEAREST sets all pixels the coulour of the nearest pixel in the original image. This results in a very "blocky" or "pixely" look, which I'm guessing you want.

Source:http://effbot.org/imagingbook/image.htm

Upvotes: 0

pandita
pandita

Reputation: 4979

As an alternative approach to what @John1024 suggests, if your data is following some trends along the rows/columns, you possibly could spread the data across say 480x640 pixels and interpolate the missing values. Pandas or scipy could help with this.

If your data is not following any trends, you could have a collection of pixels represent your single data point, e.g. the value in the first row and column would cover 480/33 x 640/50 pixels.

You can do the latter via a number of ways:

Parsing through individual pixels

magnifier = 10
im = Image.new('RGB',(rows*magnifier,cols*magnifier))
pix = im.load()

for row in range(im.size[0]):
    for col in range(im.size[1]):
        value = rescaled[row/rescaled.shape[0], col/rescaled.shape[1]],rescaled[row/rescaled.shape[0], col/rescaled.shape[1]],rescaled[row/rescaled.shape[0], col/rescaled.shape[1]]
        pix[row,col] = value
im.save('test_fullsize.jpeg', quality=95)

This results in very sharp edges without any blur at all.

Using matplotlib and scipy

import matplotlib.pyplot as plt
from scipy.ndimage.interpolation import zoom

# With interpolation
rescaled2 = zoom(rescaled,zoom=10)
# plt.imshow(rescaled2, cmap=plt.cm.hot)   # Color map
plt.imshow(rescaled2, cmap=plt.cm.gray)

# Just the data
plt.imshow(rescaled, cmap=plt.cm.gray)

The results are still a little blurry. If interpolation is ok, you could use zoom and oversample the image, i.e. make it much larger than you actually need it (two or three times of the final size, e.g.zoom=200) and then reduce the size of the image with PIL. This would get rid of some of the blur.

Another alternative could be to use a different representation for the data, e.g. different sized circles for each data point, depending on what you'd like to communicate.

Upvotes: 1

John1024
John1024

Reputation: 113814

To summarize, you have 50x33 image and you want to upsize to get a "decent resolution/size to be able to put it on a poster."

This class of problem is called super-resolution. It is an active research area. Some super-resolution algorithms are available as part of the opencv library. opencv has a python port. You can read about the available super-resolution algorithms here.

Depending on the type of image that you have, achieving a nice-looking 640x480 resolution image starting from 50x33 could be a bit of a stretch for even the best algorithms. You may need to temper your goals.

Upvotes: 1

Related Questions