Can't write to Python data structure from image.load() - python

I'm working on class work and am having trouble with the data structure that is generated by using the load() function on an image in Python. What I am doing here is trying to convert an entire image to HSL from RGB. The problem is, when I try to write the new values back to the data structure, they don't get written. I have successfully written to this type of structure in other places in my code, however. If that were not the case, I would have just assumed that the structure is immutable, but it seems like it is not. If anyone can explain to me what is going on, and how this particular data structure works, I would be very grateful. I suspect that I somehow need to create a new structure to write to in the function, but I don't know how to, aside from creating a new image and getting the structure from that.
def toHSL(px): #reorders values to h, s, l from h, l, s
for x in xrange(0, dim[0]):
for y in xrange(0, dim[1]):
r, g, b = px[x,y][0], px[x,y][1], px[x,y][2] #I don't really understand how to create this type of data structure, even
r, g, b = [x/255.0 for x in r, g, b] #Python uses values from 0 to 1
h, l, s = colorsys.rgb_to_hls(r, g, b)
px[x,y] = (int(h*255), int(s*255), int(l*255)) #trying to save back to the original data structure
return px
img_name = "Mandrill"
try:
im = Image.open(img_name + ".bmp")
except:
print "Unable to load image."
sys.exit()
dim = im.size
pix = im.load()
new = Image.new("RGB", dim)
newpx = new.load()
hsl = toHSL(pix)
print hsl[0,0][0], hsl[0,0][1], hsl[0,0][2] #<< gives the original values

I stand corrected, apparently it is possible to read and write pixels using that (undocumented) indexing technique, at least with some image formats. Learn something new everyday... ;-)
Anyway what's wrong with your program is the line:
r, g, b = [x/255.0 for x in r, g, b]
which changes the value ofxwhich is already being used to iterate over the pixel coordinates. Simply changing it to another variable name as shown below will make your code do what you want:
r, g, b = [c/255.0 for c in r, g, b]
(Note, better yet, change it to the following and remove the line preceding it)
r, g, b = [c/255.0 for c in px[x,y]]
After doing that, here's the image that will result — displayed as RGB, not HSL, to show that the structure is indeed mutable:

Related

Having trouble with the Colorgram library in Python

I am using Python's colorgram library to extract color information from an image. One version of my code is as follows:
import colorgram
num_cols = 25
rgb_colors = []
colors = colorgram.extract('image.jpg', num_cols)
for color in colors:
r = color.rgb.r
g = color.rgb.g
b = color.rgb.b
new_color = (r, g, b)
rgb_colors.append(new_color)
print(rgb_colors)
print(len(rgb_colors))
The code works just fine, but if I give 'num_cols' a value of more than 36, the code only finds a maximum of 36 colors. I have tried different hi res images, and have tried different versions of the same code that I found online, but I always get this same limit of 36 colors; I just don't know where the problem can be, and I have not seen anyone else report the same issue. Can someone please tell me if I am doing something stupid here? Thanks.

Merge multiple images based on coordinates opencv

I have cropped an image in multiple parts and I have the coordinates of each frame (I have also saved on csv). I have modified the frames and now I want to merge them again.
How can I do it in opencv?
I have tried something like (I post only a portion of the code)
parameters = pd.read_csv('parameters.csv')
parameters
for ind in parameters.index:
x = parameters['x'][ind]
y = parameters['y'][ind]
w = parameters['w'][ind]
h = parameters['h'][ind]
frameMerge = imgScratches[y:y+h,x:x+w]
where imgScratches are the framecuts. However I only receive in output one framecut and not all of them merged.
thanks.
The problem seems to be that you assign frameMerge a new part of the image with every iteration. Instead you need to append/add to the already assigned value of frameMerge
# untested
parameters = pd.read_csv('parameters.csv')
frameMerge = "init whatever datatype frameMerge needs to be"
for ind in parameters.index:
x = parameters['x'][ind]
y = parameters['y'][ind]
w = parameters['w'][ind]
h = parameters['h'][ind]
# add/append to existing frameMerge
frameMerge += imgScratches[y:y+h,x:x+w]

Why isn't Python writing elements from one 3-D list to another?

I'm trying to create a program that creates an average out of an image by looping through each RGBA value in two images to average them out and create one composite image, but the right value isn't being written to my list comp_img, which contains all the new image data.
I'm using these 256x256 images for debugging.
But it just creates this as output:
While this is the composite color, the 64 gets wiped out entirely. Help is very much appreciated, thank you.
from PIL import Image
import numpy as np
from math import ceil
from time import sleep
red = Image.open("64red.png")
grn = Image.open("64green.png")
comp_img = []
temp = [0,0,0,0] #temp list used for appending
#temp is a blank pixel template
for i in range(red.width):
comp_img.append(temp)
temp = comp_img
#temp should now be a row template composed of pixel templates
#2d to 3d array code go here
comp_img = []
for i in range(red.height):
comp_img.append(temp)
reddata = np.asarray(red)
grndata = np.asarray(grn)
reddata = reddata.tolist() #its uncanny how easy it is
grndata = grndata.tolist()
for row, elm in enumerate(reddata):
for pxl, subelm in enumerate(elm):
for vlu_index, vlu in enumerate(subelm):
comp_img[row][pxl][vlu_index] = ceil((reddata[row][pxl][vlu_index] + grndata[row][pxl][vlu_index])/2)
#These print statements dramatically slowdown the otherwise remarkably quick program, and are solely for debugging/demonstration.
output = np.array(comp_img, dtype=np.uint8) #the ostensible troublemaker
outputImg = Image.fromarray(output)
outputImg.save("output.png")
You could simply do
comp_img = np.ceil((reddata + grndata) / 2)
This gives me
To get correct values it needs to work with 16bit values - because for uint8 it works only with values 0..255 and 255+255 gives 254 instead of 510 (it calculates it modulo 256 and (255+255) % 256 gives 254)
reddata = np.asarray(red, dtype=np.uint16)
grndata = np.asarray(grn, dtype=np.uint16)
and then it gives
from PIL import Image
import numpy as np
red = Image.open("64red.png")
grn = Image.open("64green.png")
reddata = np.asarray(red, dtype=np.uint16)
grndata = np.asarray(grn, dtype=np.uint16)
#print(reddata[128,128])
#print(grndata[128,128])
#comp_img = (reddata + grndata) // 2
comp_img = np.ceil((reddata + grndata) / 2)
#print(comp_img[128,128])
output = np.array(comp_img, dtype=np.uint8)
outputImg = Image.fromarray(output)
outputImg.save("output.png")
You really should work with just NumPy arrays and functions, but I'll explain the bug here. It's rooted in how you make the nested list. Let's look at the first level:
temp = [0,0,0,0] #temp list used for appending
#temp is a blank pixel template
for i in range(red.width):
comp_img.append(temp)
At this stage, comp_img is a list with size red.width whose every element/pixel references the same RGBA-list [0,0,0,0]. I don't just mean the values are the same, it's one RGBA-list object. When you edit the values of that one RGBA-list, you edit all the pixels' colors at once.
Just fixing that step isn't enough. You also make the same error in the next step of expanding comp_img to a 2D matrix of RGBA-lists; every row is the same list.
If you really want to make a blank comp_img first, you should just make a NumPy array of a numerical scalar dtype; there you can guarantee every element is independent:
comp_img = np.zeros((red.height, red.width, 4), dtype = np.uint8)
If you really want a nested list, you have to properly instantiate (make new) lists at every level for them to be independent. A list comprehension is easy to write:
comp_img = [[[0,0,0,0] for i in range(red.width)] for j in range(red.height)]

Music21 Analyze Key always returns c minor?

I've been trying to use the Python module Music21 to try and get the key from a set of chords, but no matter what I put in it always seems to return c minor. Any ideas what I'm doing wrong?
I've tried a variety of input strings, the print statement spits out all the right chord names but the resulting key is always c minor!
I'm using Python 3.7.4 on Windows with VSCode.
string = 'D, Em, F#m, G, A, Bm'
s = stream.Stream()
for c in string.split(','):
print(harmony.ChordSymbol(c).pitchedCommonName)
s.append(harmony.ChordSymbol(c))
key = s.analyze('key')
print(key)
It works if you give the ChordSymbol some length of time. The analysis weights the components by time, so a time of zero (default for ChordSymbols) will give you nonsense.
d = harmony.ChordSymbol('D')
d.quarterLength = 2
s = stream.Stream([d])
s.analyze('key')
It looks like music21 Analyze is not working ok with ChordSymbol.
As an alternative, you can manually set all the chord's notes, and analyze that. The code:
string = 'D, Em, F#m, G, A, Bm'
s = stream.Stream()
for d in string.split(','):
print(harmony.ChordSymbol(d).pitchedCommonName)
for p in harmony.ChordSymbol(d).pitches:
n = note.Note()
n.pitch = p
s.append(n)
key = s.analyze('key')
print(key)
returns a D major key, as expected.

Horizontal flip of Image on Python with pypng

I am trying to horizontally flip an image (from left to right) on Python using PyPNG, I have written the following codes but it does not seem to work, anybody have any idea what I'm doing wrong here?
def horizontal_flip(image):
rows = len(image)
cols = len(image[0])
new_image = []
for r in range(rows):
new_row = []
for c in range(0,cols,3):
if c != cols/2:
image[c:c+3], image[-c-3: -c] = image[-c-3: -c], image[c:c+3]
new_row.append(image[r][c])
new_image.append(new_row)
return new_image
new_row.append(image[r][c]) should be outside of the if.
Also, you're flipping the image horizontally... twice. Make your for loop use range(0,cols/2,3). (That may also eliminate the need for that if.)
You're also modifying the original image in-place; are you sure you want to do that?
Seems a simpler solution might be to loop through each row in reverse, appending to a row for the new image.
The inner loop logic is wrong but particularly, this line:
image[c:c+3], image[-c-3: -c] = image[-c-3: -c], image[c:c+3]
You are changing the image variable in-place, but you seemed to forget the row variable r. So right now, you are changing rows. And your negative slicing is a bit off. For c=0, you'll get image[-3:0] and this is not a valid slice and it will return [].
But judging from your code you don't mean to change image in-place, you rather want to create new_image. What you should be doing is inserting slices at the end of new_row:
def horizontal_flip(image):
rows = len(image)
cols = len(image[0])
new_image = []
for r in range(rows):
new_row = []
for c in range(0,cols,3):
new_row = image[r][c:c+3] + new_row
new_image.append(new_row)
return new_image
By the way, you can also change the image in-place, but be careful. As you are passing a list, you should copy it before changing so that original is unchanged. Here is that version:
def horizontal_flip(image):
cols = len(image[0])/3
#make a copy so that original image is not altered
image = [row[:] for row in image]
for row in image:
for c in range(int(cols/2)): # int() is not needed for Python 2.x, since integer division yields integer
# This also takes care of odd n cases, middle chunk is not changed.
row[3*c:3*c+3], row[3*(cols-c-1):3*(cols-c-1)+3] = row[3*(cols-c-1):3*(cols-c-1)+3], row[3*c:3*c+3]
return image
This can also be done with a list comprehension in single line, but it will be less readable. If you like, here is how you can do it:
from itertools import chain
flipped_image = [list(chain(*[row[3*i:3*i+3] for i in range(len(image[0])/3-1,-1,-1)])) for row in image]

Categories