How do I downsample an image of any resolution to a quarter of the size by averaging the pixels in numpy?
What I came up through research only works for images that are square (i.e 512 X 512 down to 128 X 128) but will not work for images that are different dimensions (i.e 2400 X 1800 down to 600 X 450). In those cases I get a IndexError: index 450 is out of bounds for axis 1 with size 450.
I am trying to perform this task with numpy array manipulation and without installing other packages and libraries.
I researched a function
numpy.mean()
but I don't know how to use it in reference to this problem.
import cv2
import numpy as np
def quarter_res_avg(im):
original_width = im.shape[1]
original_height = im.shape[0]
width = original_width / 4
height = original_height / 4
resized_image = np.zeros(shape=(width, height, 3), dtype=np.uint8)
scale = 4
for i in range(width):
for j in range(height):
temp = np.array([0, 0, 0])
for x in range(scale):
for y in range(scale):
temp += im[i*scale+x, j*scale+y]
resized_image[i, j] = temp/(scale*scale)
return resized_image
im = cv2.imread('Lenna_test_image.png', 1)
cv2.imwrite('Lenna_test_image_avg.png', quarter_res_avg(im))
Any ideas are much appreciated.
Thanks.
import numpy as np
import skimage.measure
your_array = np.random.rand(2400, 800)
new_array = skimage.measure.block_reduce(your_array, (4,4), np.mean)
print(new_array.shape)
Out[18]: (600, 450)
First reshape your M x N image into a (M//4) x 4 x (N//4) x 4 array, then use np.mean in the second and last dimensions.
from typing import Tuple
import numpy as np
def downsample_by_averaging(img: np.ndarray, window_shape: Tuple[int, int]) -> np.ndarray:
return np.mean(
img.reshape((
*img.shape[:-2],
img.shape[-2] // window_shape[-2], window_shape[-2],
img.shape[-1] // window_shape[-1], window_shape[-1],
)),
axis=(-1, -3),
)
downsample_by_averaging(img, (4, 4))
The answer that worked for me with the help from #MarkSetchell in the comments of the question.
Without using np.mean()
def quarter_res_avg(im):
original_width = im.shape[1]
original_height = im.shape[0]
width = original_width / 4
height = original_height / 4
resized_image = np.zeros(shape=(height, width, 3), dtype=np.uint8)
scale = 4
for i in range(height):
for j in range(width):
temp = np.array([0, 0, 0])
for x in range(scale):
for y in range(scale):
temp += im[i*scale + x, j*scale + y]
resized_image[i, j] = temp/(scale*scale)
return resized_image
im = cv2.imread('Lenna_test_image.png', 1)
cv2.imwrite('Lenna_test_image_resized.png', quarter_res_avg(im))
By using np.mean() replace the for loops with:
for i in range(0, original_height, scale):
for j in range(0, original_width, scale):
resized_image[i/scale, j/scale] = np.mean(im[i:i + scale, j:j+scale], axis=(0,1))
Related
I'm working on a preprocessing function that takes DICOM files a input and returns a 3D np.array (image stack). The problem is that I need to keep the association between ImagePositionPatient[2] and the relative position of the processed images in the output array.
For example, if a slice with ImagePositionPatient[2] == 5 is mapped to a processed slice in position 3 in the returned stack, I need to return another array that has 5 in the third position, and the same for all original slices. For slices created during processing by interpolation or padding, the array shall contain a palceholder value like -99999 instead.
I paste my code here.
EDIT: new simplified version
def lung_segmentation(patient_dir):
"""
Load the dicom files of a patient, build a 3D image of the scan, normalize it to (1mm x 1mm x 1mm) and segment
the lungs
:param patient_dir: directory of dcm files
:return: a numpy array of size (384, 288, 384)
"""
""" LOAD THE IMAGE """
# Initialize image and get dcm files
dcm_list = glob(patient_dir + '/*.dcm')
img = np.zeros((len(dcm_list), 512, 512), dtype='float32') # inizializza un
# vettore di len(..) di matrici di 0 e di ampiezza 512x512
z = []
# For each dcm file, get the corresponding slice, normalize HU values, and store the Z position of the slice
for i, f in enumerate(dcm_list):
dcm = dicom.read_file(f)
img[i] = float(dcm.RescaleSlope) * dcm.pixel_array.astype('float32') + float(dcm.RescaleIntercept)
z.append(dcm.ImagePositionPatient[-1])
# Get spacing and reorder slices
spacing = list(map(float, dcm.PixelSpacing)) + [np.median(np.diff(np.sort(z)))]
print("LO SPACING e: "+str(spacing))
# spacing = list(map(lambda dcm, z: dcm.PixelSpacing + [np.median(np.diff(np.sort(z)))]))
img = img[np.argsort(z)]
""" NORMALIZE HU AND RESOLUTION """
# Clip and normalize
img = np.clip(img, -1024, 4000) # clippa con minimo a 1024 e max a 4k
img = (img + 1024.) / (4000 + 1024.)
# Rescale 1mm x 1mm x 1mm
new_shape = map(lambda x, y: int(x * y), img.shape, spacing[::-1])
old_shape = img.shape
img = resize(img, new_shape, preserve_range=True)
print('nuova shape calcolata'+ str(img.shape)+' con calcolo eseguito su img_shape: '+str(old_shape)+' * '+str(spacing[::-1]))
lungmask = np.zeros(img.shape) # WE NEED LUNGMASK FOR CODE BELOW
lungmask[int(img.shape[0]/2 - img.shape[0]/4) : int(img.shape[0]/2 + img.shape[0]/4),
int(img.shape[1]/2 - img.shape[1]/4) : int(img.shape[1]/2 + img.shape[1]/4),
int(img.shape[2]/2 - img.shape[2]/4) : int(img.shape[2]/2 + img.shape[2]/4)] = 1
# I set to value = 1 some pixel for executing code below, free to change
""" CENTER AND PAD TO GET SHAPE (384, 288, 384) """
# Center the image
sum_x = np.sum(lungmask, axis=(0, 1))
sum_y = np.sum(lungmask, axis=(0, 2))
sum_z = np.sum(lungmask, axis=(1, 2))
mx = np.nonzero(sum_x)[0][0]
Mx = len(sum_x) - np.nonzero(sum_x[::-1])[0][0]
my = np.nonzero(sum_y)[0][0]
My = len(sum_y) - np.nonzero(sum_y[::-1])[0][0]
mz = np.nonzero(sum_z)[0][0]
Mz = len(sum_z) - np.nonzero(sum_z[::-1])[0][0]
img = img * lungmask
img = img[mz:Mz, my:My, mx:Mx]
# Pad the image to (384, 288, 384)
nz, nr, nc = img.shape
pad1 = int((384 - nz) / 2)
pad2 = 384 - nz - pad1
pad3 = int((288 - nr) / 2)
pad4 = 288 - nr - pad3
pad5 = int((384 - nc) / 2)
pad6 = 384 - nc - pad5
# Crop images too big
if pad1 < 0:
img = img[:, -pad1:384 - pad2]
pad1 = pad2 = 0
if img.shape[0] == 383:
pad1 = 1
if pad3 < 0:
img = img[:, :, -pad3:288 - pad4]
pad3 = pad4 = 0
if img.shape[1] == 287:
pad3 = 1
if pad5 < 0:
img = img[:, :, -pad5:384 - pad6]
pad5 = pad6 = 0
if img.shape[2] == 383:
pad5 = 1
# Pad
img = np.pad(img, pad_width=((pad1 - 4, pad2 + 4), (pad3, pad4), (pad5, pad6)), mode='constant')
# The -4 / +4 is here for "historical" reasons, but it can be removed
return img
reference library for resize methods etc. is skimage
I will try to give at least some hints to the answer. As has been discussed in the comments, resizing may remove the processed data at the original positions due to needed interpolation - so in the end you have to come up with a solution for that, either by changing the resizing target to a multipe of the actual resolution, or by returning the interpolated positions instead.
The basic idea is to have your positions array z be transformed the same as the images are in z direction. So for each operation in processing that changes the z location of the processed image, a similar operation has to be done for z.
Let's say you have 5 slices with a slice distance of 3mm:
>>> z
[0, 6, 3, 12, 9]
We can make a numpy array from it for easier handling:
z_out = np.array(y)
This corresponds to the unprocessed img list.
Now you sort the image list, so you have to also sort z_out:
img = img[np.argsort(z)]
z_out = np.sort(z_out)
>>> z_out
[0, 3, 6, 9, 12]
Next, the image is resized, introducing interpolated slices.
I will assume here that the resizing is done so that the slice distance is a multiple of the target resolution during resizing. In this case you to calculate the number of interpolated slices, and fill the new position array with corresponding placeholder values:
slice_distance = int((max(z) - min(z)) / (len(z) - 1))
nr_interpolated = slice_distance - 1 # you may adapt this to your algorithm
index_diff = np.arange(len(z) - 1) # used to adapt the insertion index
for i in range(nr_interpolated):
index = index_diff * (i + 1) + 1 # insertion index for placeholders
z_out = np.insert(z_out, index, -99999) # insert placeholder for interpolated positions
This gives you the z array filled with the placeholder value where interpolated slices occur in the image array:
>>> z_out
[0, -99999, -999999, 3, -99999, -999999, 6, -99999, -999999, 9, -99999, -999999, 12]
Then you have to do the same padding as for the image in the z direction:
img = np.pad(img, pad_width=((pad1 - 4, pad2 + 4), (pad3, pad4), (pad5, pad6)), mode='constant')
# use 'minimum' so that the placeholder is used
z_out = np.pad(z_out, pad_width=(pad1 - 4, pad2 + 4), mode='minimum')
Assuming padding values 1 and 3 for simplicity this gives you:
>>> z_out
[-99999, 0, -99999, -999999, 3, -99999, -999999, 6, -99999, -999999, 9, -99999, -999999, 12, -99999, -999999, -99999]
If you have more transformations in z directions, you have to do the corresponding changes to z_out. If you are done, you can return your position list together with the image list:
return img, z_out
As an aside: your code will only work as intented if your image has a transverse (axial) orientation, otherwise you have to calculate the z position array from Image Position Patient and Image Orientation Patient, instead of just using the z component of the image position.
I have two ndArray.
ex:
x = np.array([110,200, 500,100])
y = np.array([50,150,30,70])
Now based on their value I have created an image.
x_shape = np.max(x) #x_shape=500
y_shape = np.max(y) #y-shape=150
image = np.zeros((x_shape+1, y_shape+1))
according to my data now my image size is (501,151)
Now, How can I insert data from (x, y) as x,y pair? I mean for the pixel value:
(110,50), (200,150), (500,30), (100,70)
I want the image will be white and the rest pixel will be dark. How can I achieve this?
Based on OP's own answer, one can improve it by using a vectorized approach:
import numpy as np
import matplotlib.pyplot as plt
x = np.array([110,200, 500,100])
y = np.array([50,150,30,70])
x = np.floor(x / 10).astype(int)
y = np.floor(y / 10).astype(int)
x_shape = np.max(x) # x_shape = 500
y_shape = np.max(y) # y_shape = 150
image = np.zeros((x_shape + 10, y_shape + 10))
image[x, y] = 10
plt.imshow(image)
(To be fair, I did not understand from the question that this is what OP was after).
EDIT
To address the "visualization issue" without resizing from the comments:
import numpy as np
import matplotlib.pyplot as plt
x = np.array([110, 200, 500, 100])
y = np.array([50, 150, 30, 70])
x_shape = np.max(x)
y_shape = np.max(y)
image = np.zeros((x_shape + 1, y_shape + 1))
image[x, y] = 10
plt.figure(figsize=(20, 20))
plt.imshow(image.transpose(), interpolation='nearest', aspect='equal')
Well, I got the answer. It was easy and as I am new it makes me confused.
import numpy as np
import matplotlib.pyplot as plt
x = np.array([110,200, 500,100])
y = np.array([50,150,30,70])
x = np.floor(x/10).astype(int) #devided by 10 to reduce the img size
y = np.floor(y/10).astype(int) #devided by 10 to reduce the img size
x_shape = np.max(x) #x_shape=500
y_shape = np.max(y) #y-shape=150
image = np.zeros((x_shape+10, y_shape+10))
for x, y in zip(x,y):
image[x,y]=200
plt.imshow(image)
not sure exactly what do you need you may try
a = np.array([110, 200, 500, 100])
b = np.array([50, 150, 30, 70])
np.array([zip(x,y) for x,y in zip(a,b)])
pd.DataFrame(list(zipped))```
##or another representation
np.dstack((x,y))
both are taken from https://stackoverflow.com/questions/49461605/why-do-we-need-to-convert-a-zipped-object-into-a-list
[1]: https://stackoverflow.com/questions/49461605/why-do-we-need-to-convert-a-zipped-object-into-a-list
I have a small binary numpy array X
eg.
[[0,0,0,0,0],
[0,0,1,0,0],
[0,1,0,1,0],
[0,0,1,0,0],
[0,0,0,0,0]]
I save it to an image using
plt.imsave('file.png', X, cmap=cm.gray)
the only problem is that the image is tiny at 5x5 resolution. How could I increase the resolution of the image while still keeping the information in the image?
You can use PyPNG Library. It can be very simple with this library like
import png
png.from_array(X, 'L').save("file.png")
You can also use scipy like following
import scipy.misc
scipy.misc.imsave('file.png', X)
You can use Numpy to maximize the dimension of your array and increase the number of ones around each index respectively:
In [48]: w, h = a.shape
In [49]: new_w, new_h = w * 5, h * 5
In [50]: new = np.zeros((new_w, new_h))
In [51]: def cal_bounding_square(x, y, new_x, new_y):
x = x * 5
y = y * 5
return np.s_[max(0, x-5):min(new_x, x+5),max(0, y-5):min(new_y, y+5)]
....:
In [52]: one_indices = np.where(a)
In [53]: for i, j in zip(*one_indices):
slc = cal_bounding_square(i, j, new_w, new_h)
new[slc] = 1
....:
An image that has more pixels will hold more information, but the pixels could be redundant. You could make a larger image in which each rectangle is black or white:
your_data = [[0,0,0,0,0],
[0,0,1,0,0],
[0,1,0,1,0],
[0,0,1,0,0],
[0,0,0,0,0]]
def enlarge(old_image, horizontal_resize_factor, vertical_resize_factor):
new_image = []
for old_row in old_image:
new_row = []
for column in old_row:
new_row += column*horizontal_resize_factor
new_image += [new_row]*vertical_resize_factor
return new_image
# Make a 7x7 rectangle of pixels for each of your 0 and 1s
new_image = enlarge(your_data, 7, 7)
This problem can be solved using a simply numpy hack. Resize the array and fill it with zeros.
Consider X as your present numpy array
X = np.array([[0,0,0,0,0],
[0,0,1,0,0],
[0,1,0,1,0],
[0,0,1,0,0],
[0,0,0,0,0]])
Making a new array of zeros with the required dimensions
new_X = np.zeros((new_height,new_width))
Add your original array to it
new_X[:X.shape[0], :X.shape[1]] = X
new_X is required array, now save it using any method you like.
need to read an image as an array and for each pixel select 7*7 neighbor pixels then reshape it and put as a first row of training set:
import numpy as np
from scipy import misc
face1=misc.imread('face1.jpg')
face1 dimensions are (288, 352, 3) , need to find 7*7 neighbor pixels for every pixel , so 49*3 color then reshape it as a (1,147) array and stack it into an array for all pixels , i took the following approach:
X_training=np.zeros([1,147] ,dtype=np.uint8)
for i in range(3, face1.shape[0]-3):
for j in range(3, face1.shape[1]-3):
block=face1[i-3:i+4,j-3:j+4]
pxl=np.reshape(block,(1,147))
X_training=np.vstack((pxl,X_training))
resulting X_training shape is (97572, 147)
and as last row contains all zeros then:
a = len(X_training)-1
X_training = X_training[:a]
above code works well for one picture but with Wall time: 5min 19s i have 2000 images, so it will take ages to do it for all the images. I am looking for a faster way to iterate over every pixel and do the above task.
Edit:
this is what i mean by neighbor pixels , for every pixel face1[i-3 : i+4 ,j-3:j+4]
An efficient way is to use stride_tricks to create a 2d rolling window over the image, then flatten it out:
import numpy as np
face1 = np.arange(288*352*3).reshape(288, 352, 3) # toy data
n = 7 # neighborhood size
h, w, d = face1.shape
s = face1.strides
tmp = np.lib.stride_tricks.as_strided(face1, strides=s[:2] + s,
shape=(h - n + 1, w - n + 1, n, n, d))
X_training = tmp.reshape(-1, n**2 * d)
X_training = X_training[::-1] # to get the rows into same order as in the question
tmp is a 5D view into the image, where tmp[x, y, :, :, c] is equivalent to the neigborhood face1[x:x+n, y:y+n, c] in color channel c.
The following is < 1s on my laptop:
import scipy as sp
im = sp.rand(300, 300, 3)
size = 3
ij = sp.meshgrid(range(size, im.shape[0]-size), range(size, im.shape[1]-size))
i = ij[0].T.flatten()
j = ij[1].T.flatten()
N = len(i)
L = (2*size + 1)**2
X_training = sp.empty(shape=[N, 3*L])
for pixel in range(N):
si = (slice(i[pixel]-size, i[pixel]+size+1))
sj = (slice(j[pixel]-size, j[pixel]+size+1))
X_training[pixel, :] = im[si, sj, :].flatten()
X_training = X_training[-1::-1, :]
I'm always a bit sad when I can't think of one-line vectorized version, but at least it's faster for you.
Using scikit-image:
import numpy as np
from skimage import util
image = np.random.random((288, 352, 3))
windows = util.view_as_windows(image, (7, 7, 3))
out = windows.reshape(-1, 7 * 7 * 3)
Is there a more idiomatic way to display a grid of images as in the below example?
import numpy as np
def gallery(array, ncols=3):
nrows = np.math.ceil(len(array)/float(ncols))
cell_w = array.shape[2]
cell_h = array.shape[1]
channels = array.shape[3]
result = np.zeros((cell_h*nrows, cell_w*ncols, channels), dtype=array.dtype)
for i in range(0, nrows):
for j in range(0, ncols):
result[i*cell_h:(i+1)*cell_h, j*cell_w:(j+1)*cell_w, :] = array[i*ncols+j]
return result
I tried using hstack and reshape etc, but could not get the right behaviour.
I am interested in using numpy to do this because there is a limit to how many images you can plot with matplotlib calls to subplot and imshow.
If you need sample data to test you can use your webcam like so:
import cv2
import matplotlib.pyplot as plt
_, img = cv2.VideoCapture(0).read()
plt.imshow(gallery(np.array([img]*6)))
import numpy as np
import matplotlib.pyplot as plt
def gallery(array, ncols=3):
nindex, height, width, intensity = array.shape
nrows = nindex//ncols
assert nindex == nrows*ncols
# want result.shape = (height*nrows, width*ncols, intensity)
result = (array.reshape(nrows, ncols, height, width, intensity)
.swapaxes(1,2)
.reshape(height*nrows, width*ncols, intensity))
return result
def make_array():
from PIL import Image
return np.array([np.asarray(Image.open('face.png').convert('RGB'))]*12)
array = make_array()
result = gallery(array)
plt.imshow(result)
plt.show()
yields
We have an array of shape (nrows*ncols, height, weight, intensity).
We want an array of shape (height*nrows, width*ncols, intensity).
So the idea here is to first use reshape to split apart the first axis into two axes, one of length nrows and one of length ncols:
array.reshape(nrows, ncols, height, width, intensity)
This allows us to use swapaxes(1,2) to reorder the axes so that the shape becomes
(nrows, height, ncols, weight, intensity). Notice that this places nrows next to height and ncols next to width.
Since reshape does not change the raveled order of the data, reshape(height*nrows, width*ncols, intensity) now produces the desired array.
This is (in spirit) the same as the idea used in the unblockshaped function.
Another way is to use view_as_blocks . Then you avoid to swap axes by hand :
from skimage.util import view_as_blocks
import numpy as np
def refactor(im_in,ncols=3):
n,h,w,c = im_in.shape
dn = (-n)%ncols # trailing images
im_out = (np.empty((n+dn)*h*w*c,im_in.dtype)
.reshape(-1,w*ncols,c))
view=view_as_blocks(im_out,(h,w,c))
for k,im in enumerate( list(im_in) + dn*[0] ):
view[k//ncols,k%ncols,0] = im
return im_out
This answer is based off #unutbu's, but this deals with HWC ordered tensors. Furthermore, it shows black tiles for any channels that do not factorize evenly into the given rows/columns.
def tile(arr, nrows, ncols):
"""
Args:
arr: HWC format array
nrows: number of tiled rows
ncols: number of tiled columns
"""
h, w, c = arr.shape
out_height = nrows * h
out_width = ncols * w
chw = np.moveaxis(arr, (0, 1, 2), (1, 2, 0))
if c < nrows * ncols:
chw = chw.reshape(-1).copy()
chw.resize(nrows * ncols * h * w)
return (chw
.reshape(nrows, ncols, h, w)
.swapaxes(1, 2)
.reshape(out_height, out_width))
Here's a corresponding detiling function for the reverse direction:
def detile(arr, nrows, ncols, c, h, w):
"""
Args:
arr: tiled array
nrows: number of tiled rows
ncols: number of tiled columns
c: channels (number of tiles to keep)
h: height of tile
w: width of tile
"""
chw = (arr
.reshape(nrows, h, ncols, w)
.swapaxes(1, 2)
.reshape(-1)[:c*h*w]
.reshape(c, h, w))
return np.moveaxis(chw, (0, 1, 2), (2, 0, 1)).reshape(h, w, c)