This is my code for creating a 22x16 grid in pygame:
import pygame
pygame.init()
screen = pygame.display.set_mode((440, 320))
class Grid:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
pygame.draw.rect(screen, GREY, [self.x * WIDTH, self.y * HEIGHT, WIDTH, HEIGHT], 1)
pygame.display.update()
cols = 22
rows = 16
WIDTH = 20
HEIGHT = 20
BLACK = (0, 0, 0)
GREY = (127,127,127)
grid = [0 for i in range(cols)]
for node in range(cols):
grid[node] = [0 for node in range(rows)]
for x in range(cols):
for y in range(rows):
grid[x][y] = Grid(x, y)
for x in range(cols):
for y in range(rows):
grid[x][y].draw()
while True:
event = pygame.event.get()
if event == pygame.QUIT:
pygame.quit()
How do I find the verices of each square in this grid and draw them?
Drawing the verices is not that important but would be nice to see them to get a better understanding for later.
The corner points (vertices) of the tile with the column index column and the row index row can be computed as follows:
tl = column * WIDTH, row * HEIGHT
tr = (column+1) * WIDTH, row * HEIGHT
bl = column * WIDTH, (row+1) * HEIGHT
br = (column+1) * WIDTH, (row+1) * HEIGHT
However, this can be simplified by using a pygame.Rect object:
tile_rect = pygame.Rect(column * WIDTH, row * HEIGHT, WIDTH, HEIGHT)
tl = tile_rect.topleft
tr = tile_rect.topright
bl = tile_rect.bottomleft
tt = tile_rect.bottomright
This is a follow-up to my previous question here
I've been trying to convert the color data in a heatmap to RGB values.
source image
In the below image, to the left is a subplot present in panel D of the source image. This has 6 x 6 cells (6 rows and 6 columns). On the right, we see the binarized image, with white color highlighted in the cell that is clicked after running the code below. The input for running the code is the below image. The ouput is(mean = [ 27.72 26.83 144.17])is the mean of BGR color in the cell that is highlighted in white on the right image below.
A really nice solution that was provided as an answer to my previous question is the following (ref)
import cv2
import numpy as np
# print pixel value on click
def mouse_callback(event, x, y, flags, params):
if event == cv2.EVENT_LBUTTONDOWN:
# get specified color
row = y
column = x
color = image[row, column]
print('color = ', color)
# calculate range
thr = 20 # ± color range
up_thr = color + thr
up_thr[up_thr < color] = 255
down_thr = color - thr
down_thr[down_thr > color] = 0
# find points in range
img_thr = cv2.inRange(image, down_thr, up_thr) # accepted range
height, width, _ = image.shape
left_bound = x - (x % round(width/6))
right_bound = left_bound + round(width/6)
up_bound = y - (y % round(height/6))
down_bound = up_bound + round(height/6)
img_rect = np.zeros((height, width), np.uint8) # bounded by rectangle
cv2.rectangle(img_rect, (left_bound, up_bound), (right_bound, down_bound), (255,255,255), -1)
img_thr = cv2.bitwise_and(img_thr, img_rect)
# get points around specified point
img_spec = np.zeros((height, width), np.uint8) # specified mask
last_img_spec = np.copy(img_spec)
img_spec[row, column] = 255
kernel = np.ones((3,3), np.uint8) # dilation structuring element
while cv2.bitwise_xor(img_spec, last_img_spec).any():
last_img_spec = np.copy(img_spec)
img_spec = cv2.dilate(img_spec, kernel)
img_spec = cv2.bitwise_and(img_spec, img_thr)
cv2.imshow('mask', img_spec)
cv2.waitKey(10)
avg = cv2.mean(image, img_spec)[:3]
mean.append(np.around(np.array(avg), 2))
print('mean = ', np.around(np.array(avg), 2))
# print(mean) # appends data to variable mean
if __name__ == '__main__':
mean = [] #np.zeros((6, 6))
# create window and callback
winname = 'img'
cv2.namedWindow(winname)
cv2.setMouseCallback(winname, mouse_callback)
# read & display image
image = cv2.imread('ip2.png', 1)
#image = image[3:62, 2:118] # crop the image to 6x6 cells
#---- resize image--------------------------------------------------
# appended this to the original code
print('Original Dimensions : ', image.shape)
scale_percent = 220 # percent of original size
width = int(image.shape[1] * scale_percent / 100)
height = int(image.shape[0] * scale_percent / 100)
dim = (width, height)
# resize image
image = cv2.resize(image, dim, interpolation=cv2.INTER_AREA)
# ----------------------------------------------------------------------
cv2.imshow(winname, image)
cv2.waitKey() # press any key to exit
cv2.destroyAllWindows()
What do I want to do next?
The mean of the RGB values thus obtained has to be mapped to the values in the following legend provided in the source image,
I would like to ask for suggestions on how to map the RGB data to the values in the legend.
Note: In my previous post it has been suggested that one could
fit the RGB values into an equation which gives continuous results.
Any suggestions in this direction will also be helpful.
EDIT:
Answering the comment below
I did the following to measure the RGB values of legend
Input image:
This image has 8 cells in columns width and 1 cell in rows height
Changed these lines of code:
left_bound = x - (x % round(width/8)) # 6 replaced with 8
right_bound = left_bound + round(width/8) # 6 replaced with 8
up_bound = y - (y % round(height/1)) # 6 replaced with 1
down_bound = up_bound + round(height/1) # 6 replaced with 1
Mean obtained for each cell/ each color in legend from left to right:
mean = [ 82.15 174.95 33.66]
mean = [45.55 87.01 17.51]
mean = [8.88 8.61 5.97]
mean = [16.79 17.96 74.46]
mean = [ 35.59 30.53 167.14]
mean = [ 37.9 32.39 233.74]
mean = [120.29 118. 240.34]
mean = [238.33 239.56 248.04]
You can try to apply piece wise approach, make pair wise transitions between colors:
c[i->i+1](t)=t*(R[i+1],G[i+1],B[i+1])+(1-t)*(R[i],G[i],B[i])
Do the same for these values:
val[i->i+1](t)=t*val[i+1]+(1-t)*val[i]
Where i - index of color in legend scale, t - parameter in [0:1] range.
So, you have continuous mapping of 2 values, and just need to find color parameters i and t closest to sample and find value from mapping.
Update:
To find the color parameters you can think about every pair of neighbour legend colors as a pair of 3d points, and your queried color as external 3d point. Now you just meed to find a length of perpendicular from the external point to a line, then, iterating over legend color pairs, find the shortest perpendicular (now you have i).
Then find intersection point of the perpendicular and the line. This point will be located at the distance A from line start and if line length is L then parameter value t=A/L.
Update2:
Simple brutforce solution to illustrate piece wise approach:
#include "opencv2/opencv.hpp"
#include <string>
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
Mat Image=cv::Mat::zeros(100,250,CV_32FC3);
std::vector<cv::Scalar> Legend;
Legend.push_back(cv::Scalar(82.15,174.95,33.66));
Legend.push_back(cv::Scalar(45.55, 87.01, 17.51));
Legend.push_back(cv::Scalar(8.88, 8.61, 5.97));
Legend.push_back(cv::Scalar(16.79, 17.96, 74.46));
Legend.push_back(cv::Scalar(35.59, 30.53, 167.14));
Legend.push_back(cv::Scalar(37.9, 32.39, 233.74));
Legend.push_back(cv::Scalar(120.29, 118., 240.34));
Legend.push_back(cv::Scalar(238.33, 239.56, 248.04));
std::vector<float> Values;
Values.push_back(-4);
Values.push_back(-2);
Values.push_back(0);
Values.push_back(2);
Values.push_back(4);
Values.push_back(8);
Values.push_back(16);
Values.push_back(32);
int w = 30;
int h = 10;
for (int i = 0; i < Legend.size(); ++i)
{
cv::rectangle(Image, Rect(i * w, 0, w, h), Legend[i]/255, -1);
}
std::vector<cv::Scalar> Smooth_Legend;
std::vector<float> Smooth_Values;
for (int i = 0; i < Legend.size()-1; ++i)
{
cv::Scalar c1 = Legend[i];
cv::Scalar c2 = Legend[i + 1];
float v1 = Values[i];
float v2 = Values[i+1];
for (int j = 0; j < w; ++j)
{
float t = (float)j / (float)w;
Scalar c = c2 * t + c1 * (1 - t);
float v = v2 * t + v1 * (1 - t);
float x = i * w + j;
line(Image, Point(x, h), Point(x, h + h), c/255, 1);
Smooth_Values.push_back(v);
Smooth_Legend.push_back(c);
}
}
Scalar qp = cv::Scalar(5, 0, 200);
float d_min = FLT_MAX;
int ind = -1;
for (int i = 0; i < Smooth_Legend.size(); ++i)
{
float d = cv::norm(qp- Smooth_Legend[i]);
if (d < d_min)
{
ind = i;
d_min = d;
}
}
std::cout << Smooth_Values[ind] << std::endl;
line(Image, Point(ind, 3 * h), Point(ind, 4 * h), Scalar::all(255), 2);
circle(Image, Point(ind, 4 * h), 3, qp/255,-1);
putText(Image, std::to_string(Smooth_Values[ind]), Point(ind, 70), FONT_HERSHEY_DUPLEX, 1, Scalar(0, 0.5, 0.5), 0.002);
cv::imshow("Legend", Image);
cv::imwrite("result.png", Image*255);
cv::waitKey();
}
The result:
Python:
import cv2
import numpy as np
height=100
width=250
Image = np.zeros((height, width,3), np.float)
legend = np.array([ (82.15,174.95,33.66),
(45.55,87.01,17.51),
(8.88,8.61,5.97),
(16.79,17.96,74.46),
( 35.59,0.53,167.14),
( 37.9,32.39,233.74),
(120.29,118.,240.34),
(238.33,239.56,248.04)], np.float)
values = np.array([-4,-2,0,2,4,8,16,32], np.float)
# width of cell, also defines number
# of one segment transituin subdivisions.
# Larger values will give more accuracy, but will woek slower.
w = 30
# Only fo displaying purpose. Height of bars in result image.
h = 10
# Plot legend cells ( to check correcrness only )
for i in range(len(legend)):
col=legend[i]
cv2.rectangle(Image, (i * w, 0, w, h), col/255, -1)
# Start form smoorhed scales for color and according values
Smooth_Legend=[]
Smooth_Values=[]
for i in range(len(legend)-1): # iterate known knots
c1 = legend[i] # start color point
c2 = legend[i + 1] # end color point
v1 = values[i] # start value
v2 = values[i+1] # emd va;ie
for j in range(w): # slide inside [start:end] interval.
t = float(j) / float(w) # map it to [0:1] interval
c = c2 * t + c1 * (1 - t) # transition between c1 and c2
v = v2 * t + v1 * (1 - t) # transition between v1 and v2
x = i * w + j # global scale coordinate (for drawing)
cv2.line(Image, (x, h), (x, h + h), c/255, 1) # draw one tick of smoothed scale
Smooth_Values.append(v) # append smoothed values for next step
Smooth_Legend.append(c) # append smoothed color for next step
# queried color
qp = np.array([5, 0, 200])
# initial value for minimal distance set to large value
d_min = 1e7
# index for clolor search
ind = -1
# search for minimal distance from queried color to smoothed scale color
for i in range(len(Smooth_Legend)):
# distance
d = cv2.norm(qp-Smooth_Legend[i])
if (d < d_min):
ind = i
d_min = d
# ind contains index of the closest color in smoothed scale
# and now we can extract according value from smoothed values scale
print(Smooth_Values[ind]) # value mapped to queried color.
# plot pointer (to check ourself)
cv2.line(Image, (ind, 3 * h), (ind, 4 * h), (255,255,255), 2);
cv2.circle(Image, (ind, 4 * h), 3, qp/255,-1);
cv2.putText(Image, str(Smooth_Values[ind]), (ind, 70), cv2.FONT_HERSHEY_DUPLEX, 1, (0, 0.5, 0.5), 1);
# show window
cv2.imshow("Legend", Image)
# save to file
cv2.imwrite("result.png", Image*255)
cv2.waitKey()
Okay, I've been at this all day and haven't a clue. I need to get my turtle object to draw random lines outside of a circle.
I've made code that restricts the random lines within the boundaries before, so I thought all I had to do was change the sign, but that didn't work. I'm not allowed to use coordinate geometry - it has to be something more basic...
Here's my code in it's current format:
import turtle, random
mRoshi = turtle.Turtle()
def draw_any_shape(myTurtle, sideLength, numSides):
turnAng = 360/numSides
for i in range(numSides):
myTurtle.forward(sideLength)
myTurtle.right(turnAng)
def drawCircle(myTurtle, radius, startX, startY):
circumference = 2*3.1415*radius
sideLength = circumference/360
myTurtle.penup()
myTurtle.goto(startX, startY)
#myTurtle.dot()
myTurtle.goto(startX, startY+radius)
myTurtle.pendown()
draw_any_shape(myTurtle, sideLength, 360)
def stumblingTurtle(myTurtle, radius, startX, startY, paramN5):
circumference = 2*3.1415*radius
myTurtle.speed(6)
drawCircle(myTurtle, radius, startX, startY)
myTurtle.penup()
for i in range(paramN5):
drx = random.randint(-800, 800)
drw = random.randint(-800, 800)
if (drx**2 + drw**2) > radius**2:
myTurtle.goto(drx,drw)
crx = random.randint(-800, 800)
crw = random.randint(-800, 800)
xdif = crx-drx
ydif = crw-drw
for j in range(drx, crx):
for k in range(drw, crw):
if (xdif**2 + ydif**2) > radius**2:
myTurtle.goto(crx,crw)
Does this do what you want? It's also based on code that originally kept the turtle within a circle. It uses Python3 turtle's undo capability to allow the turtle to accidentally wander into the circle and then undo that accident as if it never happened:
import turtle
import random
RADIUS = 50
MAXIMUM_TURN = 45
STEP_SIZE = 10
BORDER = 20
def bounded_random_move():
yertle.forward(STEP_SIZE)
x, y = yertle.position()
if (x * x + y * y) < RADIUS * RADIUS or x < -window_width/2 or x > window_width/2 or y < -window_height/2 or y > window_height/2:
yertle.undo() # undo misstep
turn = random.randint(180 - MAXIMUM_TURN, 180 + MAXIMUM_TURN)
yertle.left(turn)
turtle.ontimer(bounded_random_move, 100)
turtle.setup(RADIUS * 10, RADIUS * 10)
window_width = turtle.window_width() - BORDER
window_height = turtle.window_height() - BORDER
magic_marker = turtle.Turtle(visible=False)
magic_marker.penup()
magic_marker.color("red")
magic_marker.sety(-RADIUS)
magic_marker.pendown()
magic_marker.circle(RADIUS)
yertle = turtle.Turtle(shape="turtle", visible=False)
yertle.speed("fastest")
yertle.penup()
yertle.goto(RADIUS * 2, RADIUS * 2) # start outside circle
yertle.pendown()
yertle.showturtle()
turtle.ontimer(bounded_random_move, 100)
turtle.exitonclick()
My undo trick might not be rigorous enough for everyone, however.
I'm really hoping for some help on this as it has me absolutely stumped. I have the code working on its own as per below:
from tkinter import *
from PIL import ImageTk
dam_level = [75]
c = Canvas(width = 200, height = 235, relief = "sunken", borderwidth = 2)
c.grid(row = 11, rowspan = 8, column = 4, columnspan = 2)
c_width = 200
c_height = 250
y_stretch = 1.9
y_gap = 35
x_stretch = 15
x_width = 90
x_gap = 30
for x, y in enumerate(dam_level):
x0 = x * x_stretch + x * x_width + x_gap
y0 = c_height - (y * y_stretch + y_gap)
x1 = x * x_stretch + x * x_width + x_width + x_gap
y1 = c_height - y_gap
c.create_rectangle(x0, y0, x1, y1, fill = "#008ae8")
y = (str(y))
c.create_text(x0 + 10, y0, anchor = SW, text = (y, "%"))
c.create_text(x0 + 60, y1 + 5, anchor = N, text = "Catchment")
photo = ImageTk.PhotoImage(file =
"/Users/Name/Desktop/python3.4/water.png")
c.create_image(10, 10, image = photo, anchor = NW)
mainloop()
However when I put it in my main application in its own function (with the rest of my code), the image won't display. The graph and canvas displays, just not the water.png image. There's no error log or anything. The only change I make when I put this in my app is adding 'self' to this line. (And I remove the 'mainloop()' of course).
c = Canvas(self, width = 200, height = 235, relief = "sunken", borderwidth = 2)
Any suggestions would be greatly appreciated.
Thanks Bryan, for pointing me in the right direction.
Fixed with:
c.image = photo
I would like to draw a scale which ranges from red to green. I managed to do a scale which ranges from green to yellow. Now I have 2 possible solutions :
Either drawing 2 gradients : one from red to yellow and one from yellow to green. Then I can link the 2 drawings.
Or the better solution I think : Drawing one gradient from red to green with a checkpoint on the yellow.
How to implement this second solution ?
Here is my code from green to yellow. You can test it.
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
f = GradientFrame(root)
f.pack(fill="both", expand=True)
class GradientFrame(tk.Canvas):
'''A gradient frame which uses a canvas to draw the background'''
def __init__(self, parent, borderwidth=1, relief="sunken"):
tk.Canvas.__init__(self, parent, borderwidth=borderwidth, relief=relief)
self._color1 = "green"
self._color2 = "yellow"
self._color3 = "red"
self.bind("<Configure>", self._draw_gradient)
def _draw_gradient(self, event=None):
'''Draw the gradient'''
self.delete("gradient")
width = 200
height = 50
limit = width
(r1,g1,b1) = self.winfo_rgb(self._color1)
(r2,g2,b2) = self.winfo_rgb(self._color2)
(r3,g3,b3) = self.winfo_rgb(self._color3)
r_ratio = float((r2-r1)) / limit
g_ratio = float((g2-g1)) / limit
b_ratio = float((b2-b1)) / limit
for i in range(limit):
nr = int(r1 + (r_ratio * i))
ng = int(g1 + (g_ratio * i))
nb = int(b1 + (b_ratio * i))
color = "#%4.4x%4.4x%4.4x" % (nr,ng,nb)
self.create_line(0,i,height,i, tags=("gradient",), fill=color)
self.lower("gradient")
for i in range(limit):
nr = int(r1 + (r_ratio * i))
ng = int(g1 + (g_ratio * i))
nb = int(b1 + (b_ratio * i))
color = "#%4.4x%4.4x%4.4x" % (nr,ng,nb)
self.create_line(0,i,height,i, tags=("gradient",), fill=color)
self.lower("gradient")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Without the generalization or general aesthetics of the above code, I offer you a simple solution (implemented against the red to green gradient) here:
import tkinter
def rgb(r, g, b):
return "#%s%s%s" % tuple([hex(c)[2:].rjust(2, "0") for c in (r, g, b)])
root = tkinter.Tk()
root.title("Gradient")
gradient1 = tkinter.Canvas(root, width=255*2, height=50)
gradient1.pack()
gradient2 = tkinter.Canvas(root, width=255*2, height=50)
gradient2.pack()
for x in range(0, 256):
r = x
g = 255-x
gradient1.create_rectangle(x*2, 0, x*2 + 2, 50, fill=rgb(r, g, 0),
outline=rgb(r, g, 0))
for x in range(0, 256):
r = x*2 if x < 128 else 255
g = 255 if x < 128 else 255 - (x-128)*2
gradient2.create_rectangle(x*2, 0, x*2 + 2, 50, fill=rgb(r, g, 0),
outline=rgb(r, g, 0))
I believe it's what you meant by "yellow checkpoints". Thanks for the challenge! :^)