Making a collage in PIL - python

I. Am. Stuck.
I have been working on this for over a week now, and I cannot seem to get my code to run correctly. I am fairly new to PIL and Python as a whole. I am trying to make a 2x3 collage of some pictures. I have my code listed below. I am trying to get my photos to fit without any access black space in the newly created collage, however when I run my code I can only get 2 pictures to be placed into the collage, instead of the 6 I want. Any suggestions would be helpful.
*CODE EDITED
from PIL import Image
im= Image.open('Tulips.jpg')
out=im.convert("RGB", (
0.412453, 0.357580, 0.180423, 0,
0.212671, 0.715160, 0.072169, 0,
0.019334, 0.119193, 0.950227, 0 ))
out.save("Image2" + ".jpg")
out2=im.convert("RGB", (
0.9756324, 0.154789, 0.180423, 0,
0.212671, 0.715160, 0.254783, 0,
0.123456, 0.119193, 0.950227, 0 ))
out2.save("Image3" + ".jpg")
out3= im.convert("1")
out3.save("Image4"+".jpg")
out4=im.convert("RGB", (
0.986542, 0.154789, 0.756231, 0,
0.212671, 0.715160, 0.254783, 0,
0.123456, 0.119193, 0.112348, 0 ))
out4.save("Image5" + ".jpg")
out5=Image.blend(im, out4, 0.5)
out5.save("Image6" + ".jpg")
listofimages=['Tulips.jpg', 'Image2.jpg', 'Image3.jpg', 'Image4.jpg', 'Image5.jpg', 'Image6.jpg']
def create_collage(width, height, listofimages):
Picturewidth=width//3
Pictureheight=height//2
size=Picturewidth, Pictureheight
new_im=Image.new('RGB', (450, 300))
for p in listofimages:
Image.open(p)
for col in range(0,width):
for row in range(0, height):
image=Image.eval(p, lambda x: x+(col+row)/30)
new_im.paste(p, (col,row))
new_im.save("Collage"+".jpg")
create_collage(450,300,listofimages)

Here's some working code.
When you call Image.open(p), that returns an Image object, so you need to store than in a variable: im = Image.open(p).
I'm not sure what image=Image.eval(p, lambda x: x+(col+row)/30) is meant to do so I removed it.
size is the size of the thumbnails, but you're not using that variable. After opening the image, it should be resized to size.
I renamed Picturewidth and Pictureheight to thumbnail_width and thumbnail_height to make it clear what they are and follow Python naming conventions.
I also moved the number of cols and rows to variables so they can be reused without magic numbers.
The first loop opens each image into an im, thumbnails it and puts it in a list of ims.
Before the next loops we initialise i,x, andy` variables to keep track of which image we're looking at, and the x and y coordinates to paste the thumbnails into the larger canvas. They'll be updated in the next loops.
The first loop is for columns (cols), not pixels (width). (Also range(0, thing) does the same as range(thing).)
Similarly the second loop is for rows instead of pixels. Inside this loop we paste the current image at ims[i] into the big new_im at x, y. These are pixel positions, not row/cols positions.
At the end of the inner loop, increment the i counter, and add thumbnail_height to y.
Similarly, at the end of the outer loop, and add thumnnail_width to x and reset y to zero.
You only need to save new_im once, after these loops have finished.
There's no need for concatenating "Image2" + ".jpg" etc., just do "Image2.jpg".
This results in something like this:
This code could be improved. For example, if you don't need them for anything else, there's no need to save the intermediate ImageX.jpg files, and rather than putting those filenames in listofimages, put the images directly there: listofimages = [im, out1, out2, etc...], and then replace for p in listofimages: with for im in listofimages: and remove im = Image.open(p).
You could also calculate some padding for the images so the blackspace is even.
from PIL import Image
im= Image.open('Tulips.jpg')
out=im.convert("RGB", (
0.412453, 0.357580, 0.180423, 0,
0.212671, 0.715160, 0.072169, 0,
0.019334, 0.119193, 0.950227, 0 ))
out.save("Image2.jpg")
out2=im.convert("RGB", (
0.9756324, 0.154789, 0.180423, 0,
0.212671, 0.715160, 0.254783, 0,
0.123456, 0.119193, 0.950227, 0 ))
out2.save("Image3.jpg")
out3= im.convert("1")
out3.save("Image4.jpg")
out4=im.convert("RGB", (
0.986542, 0.154789, 0.756231, 0,
0.212671, 0.715160, 0.254783, 0,
0.123456, 0.119193, 0.112348, 0 ))
out4.save("Image5.jpg")
out5=Image.blend(im, out4, 0.5)
out5.save("Image6.jpg")
listofimages=['Tulips.jpg', 'Image2.jpg', 'Image3.jpg', 'Image4.jpg', 'Image5.jpg', 'Image6.jpg']
def create_collage(width, height, listofimages):
cols = 3
rows = 2
thumbnail_width = width//cols
thumbnail_height = height//rows
size = thumbnail_width, thumbnail_height
new_im = Image.new('RGB', (width, height))
ims = []
for p in listofimages:
im = Image.open(p)
im.thumbnail(size)
ims.append(im)
i = 0
x = 0
y = 0
for col in range(cols):
for row in range(rows):
print(i, x, y)
new_im.paste(ims[i], (x, y))
i += 1
y += thumbnail_height
x += thumbnail_width
y = 0
new_im.save("Collage.jpg")
create_collage(450, 300, listofimages)

I made a solution inspired by #Hugo's answer which only requires the input list of images. The function automatically creates a grid based on the number of images input.
def find_multiples(number : int):
multiples = set()
for i in range(number - 1, 1, -1):
mod = number % i
if mod == 0:
tup = (i, int(number / i))
if tup not in multiples and (tup[1], tup[0]) not in multiples:
multiples.add(tup)
if len(multiples) == 0:
mod == number % 2
div = number // 2
multiples.add((2, div + mod))
return list(multiples)
def get_smallest_multiples(number : int, smallest_first = True) -> Tuple[int, int]:
multiples = find_multiples(number)
smallest_sum = number
index = 0
for i, m in enumerate(multiples):
sum = m[0] + m[1]
if sum < smallest_sum:
smallest_sum = sum
index = i
result = list(multiples[i])
if smallest_first:
result.sort()
return result[0], result[1]
def create_collage(listofimages : List[str], n_cols : int = 0, n_rows: int = 0,
thumbnail_scale : float = 1.0, thumbnail_width : int = 0, thumbnail_height : int = 0):
n_cols = n_cols if n_cols >= 0 else abs(n_cols)
n_rows = n_rows if n_rows >= 0 else abs(n_rows)
if n_cols == 0 and n_rows != 0:
n_cols = len(listofimages) // n_rows
if n_rows == 0 and n_cols != 0:
n_rows = len(listofimages) // n_cols
if n_rows == 0 and n_cols == 0:
n_cols, n_rows = get_smallest_multiples(len(listofimages))
thumbnail_width = 0 if thumbnail_width == 0 or n_cols == 0 else round(thumbnail_width / n_cols)
thumbnail_height = 0 if thumbnail_height == 0 or n_rows == 0 else round(thumbnail_height/n_rows)
all_thumbnails : List[Image.Image] = []
for p in listofimages:
thumbnail = Image.open(p)
if thumbnail_width * thumbnail_scale < thumbnail.width:
thumbnail_width = round(thumbnail.width * thumbnail_scale)
if thumbnail_height * thumbnail_scale < thumbnail.height:
thumbnail_height = round(thumbnail.height * thumbnail_scale)
thumbnail.thumbnail((thumbnail_width, thumbnail_height))
all_thumbnails.append(thumbnail)
new_im = Image.new('RGB', (thumbnail_width * n_cols, thumbnail_height * n_rows), 'white')
i, x, y = 0, 0, 0
for col in range(n_cols):
for row in range(n_rows):
if i > len(all_thumbnails) - 1:
continue
print(i, x, y)
new_im.paste(all_thumbnails[i], (x, y))
i += 1
y += thumbnail_height
x += thumbnail_width
y = 0
extension = os.path.splitext(listofimages[0])[1]
if extension == "":
extension = ".jpg"
destination_file = os.path.join(os.path.dirname(listofimages[0]), f"Collage{extension}")
new_im.save(destination_file)
Example usage:
listofimages=['Tulips.jpg', 'Image2.jpg', 'Image3.jpg', 'Image4.jpg', 'Image5.jpg', 'Image6.jpg']
create_collage(listofimages)
In this case, because the input images are 6, the function returns a 3x2 (3 rows, 2 columns) collage of the images.
To do so, the function finds the two smallest integer multiples of the length of the input list of graphs (e.g. for 12, it returns 3 and 4 rather than 2 and 6) and creates a grid, where the first number is always the smallest of the multiples and it is taken to be the number of columns (i.e. by default the grid gets fewer columns than rows; for 12 images, you get a 4x3 matrix: 4 rows, 3 columns). This it can be customized via the smallest_first argument (only exposed in get_smallest_multiples()).
Optional arguments also allow to force a number of rows/columns.
The final image size is the sum of the sizes of the single images, but an optional thumbnail_scale argument allows to specify a percentage of scaling for all the thumbnails (defaults to 1.0, i.e. 100%, no scaling).
This function works well when the size of the images are all roughly the same. I have not covered more complex scenarios.

Related

Reading .PNGs, how do you identify clusters of color and rewrite the image file so that every cluster has a unique RGB code?

Continued from this question: How could you rewrite a list of lists so that "islands" of values are unique from one another?
Brief: How would you parse an image, for example:
in such a way that you identify the several clusters of distinct pixels and rewrite the file so that each cluster has a unique color, for example:
Here's how I have tried to implement it with assistance from a few sources, including stackoverflow user #Rabinzel: (detailed reasoning below main code block)
from scipy import ndimage
import numpy as np
from PIL import Image
#set the file path to wherever your provinces.png is located
im = Image.open(r"C:\\Users\\scoop\\Desktop\\prov_test.png")
print('-------------------------------------------')
#DEBUGGING: simply prints the format, size, and mode of your file
print(im.format, im.size, im.mode)
#saves the width and depth of the file
im_xsize = im.size[0]
im_ysize = im.size[1]
#DEBUGGING: prints it
print(im_xsize, im_ysize)
#DEBUGGNG: prints data bands, should be R, G, B
print(im.getbands())
#DEBUGGING: prints RGB value of pixel of choice
print(im.getpixel((0,0)))
print('-------------------------------------------')
#creates array for pixel RGBs
rgb_array = [[None] * im_ysize for length in range(0,im_xsize)]
#fills pixel RGB array
for x in range(0,im_xsize):
for y in range(0,im_ysize):
rgb_array[x][y] = im.getpixel((x,y))
#find unique clusters of identical RGB codes
def find_clusters(array):
clustered = np.empty_like(array)
unique_vals = np.unique(array)
cluster_count = 0
for val in unique_vals:
labelling, label_count = ndimage.label(array == val)
for k in range(1, label_count + 1):
clustered[labelling == k] = cluster_count
cluster_count += 1
return clustered, cluster_count
clusters, cluster_count = find_clusters(rgb_array)
print("Found {} clusters:".format(cluster_count))
#print(clusters)
#defining a list of unique colors
province_color_list = [[0] * 3 for length in range(0,cluster_count)]
#DEBUGGING
print('province count...', cluster_count)
#variables
r = 255
g = 0
b = 0
count = 0
#generating colors
for length in range(0,cluster_count):
province_color_list[length][0] = r
province_color_list[length][1] = g
province_color_list[length][2] = b
g += 25
b += 25
count += 1
if count >= 11:
r -= 1
g = 0
b = 0
count = 0
#DEBUGGING
print('# of colors... ', len(province_color_list))
print(province_color_list)
print('-------------------------------------------')
#writing colors to pixels
for x in range(0,im_xsize):
for y in range(0,im_ysize):
#places province color based on which province current pixel is assigned to
im.putpixel((x,y), (province_color_list[0][0], province_color_list[0][1], province_color_list[0][2]))
#im.save(r"C:\\Users\\scoop\\Desktop\\prov_test.png", im.format)
I load the image using PIL:
im = Image.open(r"C:\\Users\\scoop\\Desktop\\prov_test.png")
I create an array to more easily(?) access the image array, which stores each pixel's color as an RGB color code in tuple form. Then this method identifies the relevant pixel clusters.
rgb_array = [[None] * im_ysize for length in range(0,im_xsize)]
#fills pixel RGB array
for x in range(0,im_xsize):
for y in range(0,im_ysize):
rgb_array[x][y] = im.getpixel((x,y))
#find unique clusters of identical RGB codes
def find_clusters(array):
clustered = np.empty_like(array)
unique_vals = np.unique(array)
cluster_count = 0
for val in unique_vals:
labelling, label_count = ndimage.label(array == val)
for k in range(1, label_count + 1):
clustered[labelling == k] = cluster_count
cluster_count += 1
return clustered, cluster_count
clusters, cluster_count = find_clusters(rgb_array)
Then I create a list of unique RGB codes the length of the # of pixel clustes that exist.
province_color_list = [[0] * 3 for length in range(0,cluster_count)]
#DEBUGGING
print('province count...', cluster_count)
#variables
r = 255
g = 0
b = 0
count = 0
#generating colors
for length in range(0,cluster_count):
province_color_list[length][0] = r
province_color_list[length][1] = g
province_color_list[length][2] = b
g += 25
b += 25
count += 1
if count >= 11:
r -= 1
g = 0
b = 0
count = 0
and finally, I rewrite each pixel with the new RGB code associated with the unique cluster from earlier (and save the image).
#writing colors to pixels
for x in range(0,im_xsize):
for y in range(0,im_ysize):
#places province color based on which province current pixel is assigned to
im.putpixel((x,y), (province_color_list[clusters[x][y]][0], province_color_list[clusters[x][y]][1], province_color_list[clusters[x][y]][2]))
#im.save(r"C:\\Users\\scoop\\Desktop\\prov_test.png", im.format)
Unfortunately there's multiple issues with this script and I get the feeling its degenerated into a bit of nonsense. The chief issues seem to be accessing the RGB tuples of the .PNG Image class and changing them to integers to identify them properly as well as differentiating between distinct clusters not just distinct colors. I haven't even been able to get the script to write the image as anything but a flat color so far.
For reference, I hope to be able to scale this up to handle an image like this:
and give each of those little clusters a unique color. Any and all help appreciated.
OK, let's see if that works for you. If I understood it right what you are trying to achieve, here is my (beginner) solution.
Essentially I take the image, in a 3D array, find all unique colors in the picture and replace them with an integer( function: arr_to_int). Then find all the clusters with the function find_clusters. Create a dictionary with new colors with as many colors as number of clusters (so every int of every cluster gets replaced with a color again).
At the end replace all int with colors again and save the picture.
This was the image I used to start with:
and that's the new picture I got as output:
If you change the process of how to apply them clusters the specific colors you want to use, I think I'm pretty close to what you are trying to achieve (hope so :) )
import numpy as np
import cv2
from scipy import ndimage
# array of GBR colors to single int
def arr_to_int(arr, col_mask):
out = np.ndarray(shape=arr.shape[:2], dtype=int)
out[:,:] = -1
for rgb, idx in col_mask.items():
out[(arr==rgb).all(2)] = idx
return out
# find unique clusters of identical RGB codes
def find_clusters(array):
clustered = np.empty_like(array)
unique_vals = np.unique(array)
cluster_count = 0
for val in unique_vals:
labelling, label_count = ndimage.label(array == val)
for k in range(1, label_count + 1):
clustered[labelling == k] = cluster_count
cluster_count += 1
return clustered, cluster_count
# Load image
im = cv2.imread("prov_test.png")
#im = cv2.resize(im, (2, 3)) #resize for debugging
#print('original image: \n', im, '\n')
#find all unique colors in image (cv2 presents in BGR format!!!)
unique_col_BGR = list(set(tuple(v) for m2d in im for v in m2d))
print('unique values: ', unique_col_BGR, '\n')
#create dict with GBR_colors as keys and unique integers as value
mask_GBR_int = {color:idx for idx,color in enumerate(unique_col_BGR)}
print('mask dict: ', mask_GBR_int, '\n')
#change all color values in im to a single int (mask)
im_with_ints = arr_to_int(im, mask_GBR_int)
#print('pic with mask values: \n', im_with_ints, '\n')
# due to replacing array of 3 values to a single int, new array has one dimension less
print('orig pic resized shape', im.shape)
print('Mask int pic shape', im_with_ints.shape, '\n')
clusters, cluster_count = find_clusters(im_with_ints)
print(f'Found {cluster_count} clusters', '\n')
#print(clusters)
#create dict with length equal to number of clusters and choose color of list_of_colors (random from the internet)
list_of_colors = [[192,192,192],[128,128,128],[128,0,0],[128,128,0],[0,128,0],[128,0,128],[0,128,128],[0,0,128],[255,0,0],[0,255,0],[0,0,255],[255,255,0],[0,255,255],[255,0,255]]
new_color_dict = {idx:val for idx,val in enumerate(list_of_colors[:cluster_count])}
print('new_color_dict: ', new_color_dict,'\n')
#change arr with int to colors again
res = np.array([*new_color_dict.values()])[clusters]
#print('image array with new colors: \n', res)
cv2.imwrite("prov_test_output.png", res)
Output:
unique values: [(0, 255, 0), (255, 0, 0), (0, 0, 255), (0, 255, 255)]
mask dict: {(0, 255, 0): 0, (255, 0, 0): 1, (0, 0, 255): 2, (0, 255, 255): 3}
orig pic resized shape (100, 100, 3)
Mask int pic shape (100, 100)
Found 9 clusters
new_color_dict: {0: [192, 192, 192], 1: [128, 128, 128], 2: [128, 0, 0], 3: [128, 128, 0], 4: [0, 128, 0], 5: [128, 0, 128], 6: [0, 128, 128], 7: [0, 0, 128], 8: [255, 0, 0]}

PyTorch: Vectorizing patch selection from a batch of images

Suppose I have a batch of images as a tensor, for example:
images = torch.zeros(64, 3, 1024, 1024)
Now, I want to select a patch from each of those images. All the patches are of the same size, but have different starting positions for each image in the batch.
size_x = 100
size_y = 100
start_x = torch.zeros(64)
start_y = torch.zeros(64)
I can achieve the desired result like that:
result = []
for i in range(arr.shape[0]):
result.append(arr[i, :, start_x[i]:start_x[i]+size_x, start_y[i]:start_y[i]+size_y])
result = torch.stack(result, dim=0)
The question is -- is it possible to do the same thing faster, without a loop? Perhaps there is some form of advanced indexing, or a PyTorch function that can do this?
You can use torch.take to get rid of a for loop. But first, an array of indices should be created with this function
def convert_inds(img_a,img_b,patch_a,patch_b,start_x,start_y):
all_patches = np.zeros((len(start_x),3,patch_a,patch_b))
patch_src = np.zeros((patch_a,patch_b))
inds_src = np.arange(patch_b)
patch_src[:] = inds_src
for ind,info in enumerate(zip(start_x,start_y)):
x,y = info
if x + patch_a + 1 > img_a: return False
if y + patch_b + 1 > img_b: return False
start_ind = img_b * x + y
end_ind = img_b * (x + patch_a -1) + y
col_src = np.linspace(start_ind,end_ind,patch_b)[:,None]
all_patches[ind,:] = patch_src + col_src
return all_patches.astype(np.int)
As you can see, this function essentially creates the indices for each patch you want to slice. With this function, the problem can be easily solved by
size_x = 100
size_y = 100
start_x = torch.zeros(64)
start_y = torch.zeros(64)
images = torch.zeros(64, 3, 1024, 1024)
selected_inds = convert_inds(1024,1024,100,100,start_x,start_y)
selected_inds = torch.tensor(selected_inds)
res = torch.take(images,selected_inds)
UPDATE
OP's observation is correct, the approach above is not faster than a naive approach. In order to avoid building indices every time, here is another solution based on unfold
First, build a tensor of all the possible patches
# create all possible patches
all_patches = images.unfold(2,size_x,1).unfold(3,size_y,1)
Then, slice the desired patches from all_patches
img_ind = torch.arange(images.shape[0])
selected_patches = all_patches[img_ind,:,start_x,start_y,:,:]

Python list not appending & variable not defined

I am having one small problem with my program and as I am very new to Python am struggling to find a solution. I have googled about a bit but nothing seemed to work from what I tried. My code is the following:
from PIL import Image
import numpy as np
from scipy import misc
from pandas import *
def maze_gen():
##Opens the Image
im_file = Image.open('15x15.png')
##Reads the image pixel information
arr = misc.imread('15x15.png')
##Sets the width, height and maze size variables
width = im_file.size[0]
height = im_file.size[1]
size = width * height
##Defines the mapping, start and end points array
map = np.zeros([width, height], dtype=np.int)
start_pos = np.empty(shape=[1,2])
end_pos = np.empty(shape=[1,2])
##Search image replacing white pixels with 1's in the mapping array
for x in range(width):
for y in range(height):
if 255 in arr[x,y]:
map[x,y] = 1
##Find the start and end locations
for x in range(width):
if map[0,x] > 0:
start_pos = 0,x
for x in range(width):
if map[height -1 ,x] > 0:
end_pos = height -1 ,x
return width, height, size, map, start_pos, end_pos
def check_neighbours(col, row):
width, height, size, map, start_pos, end_pos=maze_gen()
neighbours = list()
##Debugging to check cell_current values are correct
print('col =', col)
print('row =', row)
if (col >= 0 and col < height and row >= 0 and row < width):
neighbours.append((col, row))
print('Neighbours', neighbours, '\n')
if (len(neighbours) > 0):
return neighbours
else:
return None
def solver():
width, height, size, map, start_pos, end_pos=maze_gen()
##Sets cell_current to the starting position
cell_current = start_pos
path = list()
path.append((cell_current))
check_neighbours(cell_current[0]-1, cell_current[1]) ##Top neighbour
check_neighbours(cell_current[0], cell_current[1]+1) ##Right neighbour
check_neighbours(cell_current[0]+1, cell_current[1]) ##Bottom neighbour
check_neighbours(cell_current[0], cell_current[1]-1) ##Left neighbour
print('Neighbours in Solver', neighbours, '\n')
def debug():
width, height, size, map, start_pos, end_pos=maze_gen()
##Prints maze information for debugging
print ('Maze width:', width)
print ('Maze height:', height)
print ('Maze size:', size, '\n')
##Print Start and End points
print ('Start Point')
print (start_pos, '\n')
print ('End Point')
print (end_pos, '\n')
##Prints mapping array for debugging
print ('Mapping Array')
print (DataFrame(map), '\n')
if (__name__ == "__main__"):
solver()
The issues I am running into are the list neighbours isn't being added to with each successful run through of the if statement. Instead of getting the following readout:
col = -1 row = 1 Neighbours []
col = 0 row = 2 Neighbours [(0, 2)]
col = 1 row = 1 Neighbours [(1, 1)]
col = 0 row = 0 Neighbours [(0, 0)]
Any help on this problem would be amazing. Please do bare in mind that I am very new to python so any stupid mistakes or things I could change to make the code better please do say!
You need to make two changes in your code:
Make neighbours variable global.
Replace neighbours.append((col, row)) with neighbours.append([col, row]). This is because you can only append a variable or a list to an existing list.
Hope this helps.

Total number of UNIQUE rotations in a 3D array around an axis

How many number of unique rotations are there in a 3D array around an axis. For example: a 3x3x3 array can be rotated 8 (rotation = single roll/shift in element - One rotation of the cube is rotating all the slices around one axis by 1 element. ) times around each axis. However, with all three axis, there are 8*8*8 rotations. But how many of them are unique?
In my calculation, there are 208. And in a 5x5x5 3D array, out of total of 13824 rotations (24*24*24) only 2048 are unique.
I would like if someone can confirm these or correct me if I am wrong.
I used the following script to generate these.
def rotateSlice(slice_data, num_elements=1, direction='c'):
if direction == 'ac':
num_elements *= -1
rotated_slice = slice_data
indexed_slice = numpy.arange(0, slice_data.shape[0]**2).reshape(slice_data.shape)
int_slice_idx = 0
for i in range(slice_data.shape[0], 1, -2):
clipped_array = slice_data[int_slice_idx:slice_data.shape[0]-int_slice_idx, int_slice_idx:slice_data.shape[0]-int_slice_idx]
clipped_indx_array = indexed_slice[int_slice_idx:slice_data.shape[0]-int_slice_idx, int_slice_idx:slice_data.shape[0]-int_slice_idx]
outer_border = list(range(i)) + list(range(range(i)[-1]+i, i*(i-2)+range(i)[-1]+i, i)) + list(range(i**2 - 1, i**2 - (i+1), -1)) + list(range(i*(i-2), 0, -i))
new_outer_border = numpy.roll(outer_border, num_elements)
rotated_slice.flat[clipped_indx_array.flat[outer_border]] = clipped_array.flat[new_outer_border]
int_slice_idx +=1
return rotated_slice
def rotateCube(cube, rotate_dim, num_elements=1, direction='c'):
out_cube = numpy.zeros(cube.shape, dtype='int')
for i in range(cube.shape[rotate_dim]):
slice = numpy.take(cube, i, rotate_dim)
rotated_slice = rotateSlice(slice, num_elements=num_elements, direction=direction)
#TODO - Its better if there is a less ugly way to do the following step. Similar to numpy.take
if rotate_dim == 0:
out_cube[i,:,:] = rotated_slice
elif rotate_dim == 1:
out_cube[:, i, :] = rotated_slice
elif rotate_dim == 2:
out_cube[:, :, i] = rotated_slice
return out_cube
def getAllCubeRotations(cube, step=1, direction='c'):
total_rotations_in_axis = cube.shape[0]**2 - 1
rotations = []
for i in range(int(total_rotations_in_axis)):
cube = rotateCube(cube, rotate_dim=0, num_elements=step, direction=direction)
for j in range(int(total_rotations_in_axis)):
cube = rotateCube(cube, rotate_dim=1, num_elements=step, direction=direction)
for k in range(int(total_rotations_in_axis)):
cube = rotateCube(cube, rotate_dim=2, num_elements=step, direction=direction)
rotations.append(cube)
return rotations
if __name__ == '__main__':
size = 5
a = numpy.arange(0, size*size*size).reshape((size,size,size))
rotations = getAllCubeRotations(a)
print(len(rotations))
uniques = []
for arr in rotations:
if not any(numpy.array_equal(arr, unique_arr) for unique_arr in uniques):
uniques.append(arr)
print(len(uniques))

Numpy convert list of 3D variable size volumes to 4D array

I'm working on a neural network where I am augmenting data via rotation and varying the size of each input volume.
Let me back up, the input to the network is a 3D volume. I generate variable size 3D volumes, and then pad each volume with zero's such that the input volume is constant. Check here for an issue I was having with padding (now resolved).
I generate a variable size 3D volume, append it to a list, and then convert the list into a numpy array. At this point, padding hasn't occured so converting it into a 4D tuple makes no sense...
input_augmented_matrix = []
label_augmented_matrix = []
for i in range(n_volumes):
if i % 50 == 0:
print ("Augmenting step #" + str(i))
slice_index = randint(0,n_input)
z_max = randint(5,n_input)
z_rand = randint(3,5)
z_min = z_max - z_rand
x_max = randint(75, n_input_x)
x_rand = randint(60, 75)
x_min = x_max - x_rand
y_max = randint(75, n_input_y)
y_rand = randint(60, 75)
y_min = y_max - y_rand
random_rotation = randint(1,4) * 90
for j in range(2):
temp_volume = np.empty((z_rand, x_rand, y_rand))
k = 0
for z in range(z_min, z_max):
l = 0
for x in range(x_min, x_max):
m = 0
for y in range(y_min, y_max):
if j == 0:
#input volume
try:
temp_volume[k][l][m] = input_matrix[z][x][y]
except:
pdb.set_trace()
else:
#ground truth volume
temp_volume[k][l][m] = label_matrix[z][x][y]
m = m + 1
l = l + 1
k = k + 1
temp_volume = np.asarray(temp_volume)
temp_volume = np.rot90(temp_volume,random_rotation)
if j == 0:
input_augmented_matrix.append(temp_volume)
else:
label_augmented_matrix.append(temp_volume)
input_augmented_matrix = np.asarray(input_augmented_matrix)
label_augmented_matrix = np.asarray(label_augmented_matrix)
The dimensions of input_augmented_matrix at this point is (N,)
Then I pad with the following code...
for i in range(n_volumes):
print("Padding volume #" + str(i))
input_augmented_matrix[i] = np.lib.pad(input_augmented_matrix[i], ((0,n_input_z - int(input_augmented_matrix[i][:,0,0].shape[0])),
(0,n_input_x - int(input_augmented_matrix[i][0,:,0].shape[0])),
(0,n_input_y - int(input_augmented_matrix[i][0,0,:].shape[0]))),
'constant', constant_values=0)
label_augmented_matrix[i] = np.lib.pad(label_augmented_matrix[i], ((0,n_input_z - int(label_augmented_matrix[i][:,0,0].shape[0])),
(0,n_input_x - int(label_augmented_matrix[i][0,:,0].shape[0])),
(0,n_input_y - int(label_augmented_matrix[i][0,0,:].shape[0]))),
'constant', constant_values=0)
At this point, the dimensions are still (N,) even though every element of the list is constant. For example input_augmented_matrix[0] = input_augmented_matrix[1]
Currently I just loop through and create a new array, but it takes too long and I would prefer some sort of method that automates this. I do it with the following code...
input_4d = np.empty((n_volumes, n_input_z, n_input_x, n_input_y))
label_4d = np.empty((n_volumes, n_input_z, n_input_x, n_input_y))
for i in range(n_volumes):
print("Converting to 4D tuple #" + str(i))
for j in range(n_input_z):
for k in range(n_input_x):
for l in range(n_input_y):
input_4d[i][j][k][l] = input_augmented_matrix[i][j][k][l]
label_4d[i][j][k][l] = label_augmented_matrix[i][j][k][l]
Is there a cleaner and faster way to do this?
As I understood this part
k = 0
for z in range(z_min, z_max):
l = 0
for x in range(x_min, x_max):
m = 0
for y in range(y_min, y_max):
if j == 0:
#input volume
try:
temp_volume[k][l][m] = input_matrix[z][x][y]
except:
pdb.set_trace()
else:
#ground truth volume
temp_volume[k][l][m] = label_matrix[z][x][y]
m = m + 1
l = l + 1
k = k + 1
You just want to do this
temp_input = input_matrix[z_min:z_max, x_min:x_max, y_min:y_max]
temp_label = label_matrix[z_min:z_max, x_min:x_max, y_min:y_max]
and then
temp_input = np.rot90(temp_input, random_rotation)
temp_label = np.rot90(temp_label, random_rotation)
input_augmented_matrix.append(temp_input)
label_augmented_matrix.append(temp_label)
Here
input_augmented_matrix[i] = np.lib.pad(
input_augmented_matrix[i],
((0,n_input_z - int(input_augmented_matrix[i][:,0,0].shape[0])),
(0,n_input_x - int(input_augmented_matrix[i][0,:,0].shape[0])),
(0,n_input_y - int(input_augmented_matrix[i][0,0,:].shape[0]))),
'constant', constant_values=0)
Better to do this, because shape property gives you size of array by all dimensions
ia_shape = input_augmented_matrix[i].shape
input_augmented_matrix[i] = np.lib.pad(
input_augmented_matrix[i],
((0, n_input_z - ia_shape[0]),
(0, n_input_x - ia_shape[1])),
(0, n_input_y - ia_shape[2]))),
'constant',
constant_values=0)
I guess now you're ready to refactor the last part of your code with magic indexing of NumPy.
My common suggestions:
use functions for repeated parts of code to avoid such indents like in your cascade of loops;
if you need so lot of nested loops, think about recursion, if you can't deal without them;
explore abilities of NumPy in official documentation: they're really exciting ;) For example, indexing is helpful for this task;
use PyLint and Flake8 packages to inspect quality of your code.
Do you want to write neural network by yourself, or you just want to solve some patterns recognition task? SciPy library may contain what you need and it's based on NumPy.

Categories

Resources