My scrollregion height won't go any more than 4000. It doesn't change the height when I set it to 10000.
I've tried to change the Canvas height by adding 100+ to it
from tkinter import *
from tkinter import ttk
FrameU = Tk()
FrameNU=Frame(FrameU,width=540,height=800,bg="#A0522D")
FrameNU.place(x=0,y=0,relx=.2,rely=.2)
rx = .5
ry = .5
wCanvas, hCanvas = 550, 700 # size of canvas
w1, h1 = 0, 4000 # size of scrollable area
vBar = ttk.Scrollbar(FrameNU, orient = VERTICAL)
canvas_Main = Canvas(FrameNU,bg="#A0522D" ,scrollregion = (0,0,w1,h1), width = wCanvas, height = hCanvas, yscrollcommand = vBar.set)
vBar['command'] = canvas_Main.yview
vBar.pack(side=RIGHT,fill=Y)
canvas_Main.pack()
canvas_Main.create_line(10, 10, 100, 100) #Test if it works
#Buttons setup below over 160... Only added 1
MS = Button(canvas_Main,height=3,width=6,bg="blue")
def VscrollBarMove(event):
MS.place(relx = rx, rely = ry - vBar.get()[0])
#... More placements just added one
vBar.bind('<B1-Motion>', VscrollBarMove)
mainloop()
No errors. When I changed the height from 4000 to 10000 it didn't change anything but made the scrollbar look smaller as if it had more area going down but it doesn't.
Your code is correct in the sense of the Python (and the Tkinter) syntax. I modified it slightly to proof it's correctness. (Of course, all of new lines are not obligatory.)
1. Let's open the window to a full screen:
FrameU = Tk() # The existing string
windowHeight = FrameU.winfo_screenheight()
windowWidth = FrameU.winfo_screenwidth()
FrameU.geometry('{}x{}'.format(windowWidth, windowHeight))
FrameU.state('zoomed')
2. I changed the vertical position of the second frame for full view of the vertical scrollbar (on my current 1280 x 768 screen):
FrameNU.place(x=0,y=0,relx=.2,rely=.05) # the old rely was 0.2
3. New variable for the big canvas:
w1, h1 = 0, 4000 # size of scrollable area # the exisiting string
h2 = 10000 # the new variable
And we use this new variable instead h1:
canvas_Main = Canvas(FrameNU, ... ,scrollregion = (0,0,w1,h2), ... ) # Here h2 - the only change
Reference lines for the scrollbar correct behavior proof, as Bryan Oakley adviced.
canvas_Main.create_line(10, h1-5, 100, h1-5, fill = 'green')
canvas_Main.create_line(10, h2-5, 100, h2-5, fill = 'blue') # before VscrollBarMove function definition
That's all.
When I changed the height from 4000 to 10000 it didnt change anything but made the scrollbar look smaller as if it had more area going down but it doesn't.
That's precisely what changing the scrollregion does. It changes the part of the canvas that can be scrolled into view, which in turn affects how the thumb of the scrollbar is drawn. It doesn't matter whether you've drawn in that region or not.
You can see that it works by setting the height to 10000, and then drawing something at y coordinate 9000. When you scroll, that item will come into view.
Related
Given a DXF file (2D CAD drawing), is it somehow possible to rasterise only part of it? Preferably in Python's ezdxf. By the part of it, I mean the selected rectangular area, not a single layer.
Background: I'm struggling to rasterise quite a big DXF file with decent DPI in a reasonable time, so I thought that maybe there's a way to speed up the process by parallelising rasterising different parts of the drawing. I'm using ezdxf with matplotlib backend.
This solution renders the DXF file in 4 tiles including filtering the DXF entities outside the rendering area. But the calculation of the bounding boxes is also costly and the entities in the overlapping area are rendered multiple times, this means this solution takes longer as a single-pass rendering. But it shows the concept. The images fit perfect together the space is left to show that this are 4 images:
import matplotlib.pyplot as plt
import random
import ezdxf
from ezdxf.addons.drawing import RenderContext, Frontend
from ezdxf.addons.drawing.matplotlib import MatplotlibBackend
from ezdxf import bbox
from ezdxf.math import BoundingBox2d
COLORS = list(range(1, 7))
DPI = 300
WIDTH = 400
HEIGHT = 200
LEFT = 0
BOTTOM = 0
doc = ezdxf.new()
msp = doc.modelspace()
def random_points(count):
for _ in range(count):
yield WIDTH * random.random(), HEIGHT * random.random()
for s, e in zip(random_points(100), random_points(100)):
msp.add_line(s, e, dxfattribs={"color": random.choice(COLORS)})
# detecting the drawing extents by ezdxf can take along time for big files!
cache = bbox.Cache() # reuse bounding boxes for entity filtering
rect = bbox.extents(msp, cache=cache)
WIDTH = rect.size.x
HEIGHT = rect.size.y
LEFT = rect.extmin.x
BOTTOM = rect.extmin.y
VIEWPORT_X = [LEFT, LEFT + WIDTH / 2, LEFT, LEFT + WIDTH / 2]
VIEWPORT_Y = [BOTTOM, BOTTOM, BOTTOM + HEIGHT / 2, BOTTOM + HEIGHT / 2]
ctx = RenderContext(doc)
for quarter in [0, 1, 2, 3]:
# setup drawing add-on:
fig = plt.figure(dpi=300)
ax = fig.add_axes([0, 0, 1, 1])
out = MatplotlibBackend(ax)
# calculate and set render borders:
left = VIEWPORT_X[quarter]
bottom = VIEWPORT_Y[quarter]
ax.set_xlim(left, left + WIDTH / 2)
ax.set_ylim(bottom, bottom + HEIGHT / 2)
# set entities outside of the rendering area invisible:
# Bounding box calculation can be very costly, especially for deep nested
# block references! If you did the extents calculation and reuse the cache
# you already have paid the price:
render_area = BoundingBox2d(
[(left, bottom), (left + WIDTH / 2, bottom + HEIGHT / 2)])
for entity in msp:
entity_bbox = bbox.extents([entity], cache=cache)
if render_area.intersect(entity_bbox):
entity.dxf.invisible = 0
else:
entity.dxf.invisible = 1
# finalizing invokes auto-scaling!
Frontend(ctx, out).draw_layout(msp, finalize=False)
# set output size in inches
# width = 6 in x 300 dpi = 1800 px
# height = 3 in x 300 dpi = 900 px
fig.set_size_inches(6, 3, forward=True)
filename = f"lines{quarter}.png"
print(f'saving to "{filename}"')
fig.savefig(filename, dpi=300)
plt.close(fig)
The draw_layout() method has an argument filter_func to specify a function which accepts a DXF entity as argument and returns True or False to render or ignore this entity. This would be an alternative to filter the entities outside of the rendering area without altering the DXF content.
UPDATE: a refined example can be found at github
I have been working on a tkinter calculator in Python 3.8, with basic buttons and an entry field. I want the text in the buttons and entry field to increase (or decrease) size with the window automatically, in proportion with the buttons themselves -- have yet to work on the entry field font sizes -- and, despite trying for a while, have failed. At certain sizes, the font collapses to the minimum size (see code below) or starts phasing rapidly. It also collapses to minimum size when the window is moved.
# Default font size
fontsize = tkFont.Font(size=11)
def font_change(event):
# Base size
normal_width = 418
normal_height = 295
# Screen
screen_width = event.width
screen_height = event.height
# Get percentage of screen size from Base size
percentage_width = screen_width / (normal_width / 100)
percentage_height = screen_height / (normal_height / 100)
# Make a scaling factor
scale_factor = ((percentage_width + percentage_height) / 2) / 100
# Set the fontsize based on scale_factor,
# if the fontsize is less than minimum_size
# it is set to the minimum size
# font_size is the variable to store actual size
minimum_size = 8
if scale_factor > minimum_size/18:
font_size = int(18 * scale_factor)
else:
font_size = minimum_size
fontsize.configure(size = font_size)
I bind the function to an event:
root.bind("<Configure>", font_change)
Example of a button,
decimal = Button(
root,
text=".",
command=lambda: press("."),
font = fontsize,
height = 2,
width=7)
decimal.grid(row=6, column=2, sticky=NW + NE + SW + SE)
It would be appreciated if someone can help me out.
I constructed a test GUI with two Labels. root.bind( "<Configure>", func ) is fired each time root or any of it's children resize. Code below. When the Labels fire the configure event the font is minimised. Which fires the configure event again, even smaller etc.
import tkinter as tk
from tkinter import font
root = tk.Tk()
root.geometry( "200x100" )
fontsize = font.Font( size = 11 )
tk.Label( root, text = 'Test', font = fontsize ).grid( padx = 5, pady = 5 )
tk.Label( root, text = 'Another Test', font = fontsize ).grid( padx = 5, pady = 5 )
def font_change(event):
print( event.widget, event ) # See what is happening
# Base size
normal_width = 200
normal_height = 100
# Screen
screen_width = event.width
screen_height = event.height
print( event.widget, event )
# Get percentage of screen size from Base size
percentage_width = screen_width / (normal_width / 100)
percentage_height = screen_height / (normal_height / 100)
minimum_size = 8
# Make a scaling factor
scale_factor = ((percentage_width + percentage_height) / 2) / 100
# Set the fontsize based on scale_factor,
# if the fontsize is less than minimum_size
# it is set to the minimum size
# font_size is the variable to store actual size
if scale_factor > minimum_size/18:
font_size = int(18 * scale_factor)
else:
font_size = minimum_size
fontsize.configure( size = font_size )
root.bind( '<Configure>', font_change )
root.mainloop()
One option is to replace the print statement in the above code with
if event.widget != root:
return None # Jump out of the function if the widget firing configure isn't root
Another option would be to read the root width and height in the font_change function, not take the width and height from event.
I'm using this code to identify tops and bottoms of photographs:
( as of now I only have it working for tops. one thing at a time ;) )
def get_file(path):
client = vision.ImageAnnotatorClient()
for images in os.listdir(path):
# # Loads the image into memory
with io.open(images, "rb") as image_file:
content = image_file.read()
image = types.Image(content=content)
objects = client.object_localization(image=image).localized_object_annotations
im = Image.open(images)
width, height = im.size
print("Number of objects found: {}".format(len(objects)))
for object_ in objects:
if object_.name == "Top":
print("Top")
l1 = object_.bounding_poly.normalized_vertices[0].x
l2 = object_.bounding_poly.normalized_vertices[0].y
l3 = object_.bounding_poly.normalized_vertices[2].x
l4 = object_.bounding_poly.normalized_vertices[3].y
left = l1 * width
top = l2 * height
right = l3 * width
bottom = l4 * height
im = im.crop((left, top, right, bottom))
im.save('new_test_cropped.tif', 'tiff')
im.show()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Script to automatically crop images based on google vision predictions of 'tops' and 'bottoms'")
parser.add_argument('--path', help='Include the path to the images folder')
args = parser.parse_args()
get_file(args.path)
The images are opened, clothing is identified, and then the images are cropped and saved to a new file. (granted as of now they are being overwritten within the loop, but I'll fix that later)
What I cant figure out, is how to make the crop a 1:1 ratio. I need to save them out as square-cropped to be put on our website.
I'll be honest, the normalized_vertices make no sense to me. Hence why I'm having trouble.
Starting image:
Output:
Desired Output:
"Normalized" means the coordinates are divided by the width or height of the image, so normalized coordinates [1, 0.5] would indicate all the way (1) across the image and halfway down (0.5).
For a 1:1 aspect ratio you want right - left to be equal to top - bottom. So you want to find out which dimension (width or height) you need to increase, and by how much.
height = abs(top - bottom)
width = abs(right - left)
extrawidth = max(0, height - width)
extraheight = max(0, width - height)
If height > width, we want to increase width but not height. Since height - width > 0, the correct value will go into extrawidth. But because width - height < 0, extraheight will be 0.
Now let's say we want to increase the dimensions of our image symmetrically around the original crop rectangle.
top -= extraheight // 2
bottom += extraheight // 2
left -= extrawidth // 2
right += extrawidth // 2
And finally, do the crop:
im = im.crop((left, top, right, bottom))
For your image, let's say you get left = 93, right = 215, top = 49, and bottom = 205
Before:
After:
I have an image with image.shape=(20,10)and I want to resize this image so that new image size would be image.size = 90.
I want to use np.resize(image,(new_width, new_height)), but how can I calculate new_width and new_height, so that it maintains aspect_ratio as same as in original image.
Well, you choose which dimension you want to enforce and then you adjust the other one by calculating either new_width = new_height*aspect_ratio or new_height = new_width/aspect_ratio.
You might want to round those numbers and convert them to int too.
The height of your image is 20 and the width is 10, so the height is 2x the width, i.e.
h = 2 * w
You want your new image to have an area of 90 pixels, and the area (A) is:
A = h * w
90 = 2 * w * w
w = sqrt(45)
So the sides of your image need to be 6.7 and 13.4
I hope that helps, even if I doubt it will.
You can use this simple function for finding the new height of an image with width as an input
def findHeight(original_width, original_height, new_width):
area = original_width * original_height
new_height = area/new_width
return new_height
I am trying to draw two histograms alongside one another using tkinter canvas. Everything sort of works ( looks extremely scruffy at the moment) but the histograms are drawn downwards. I have tried making the y0 value negative, but then nothing at all is drawn.
I am using two lists of numerical data, the first with 50 observations and the other with eleven observations, the scales are not the same, but it is the qualitative effect I want at the moment.
The offending code is as follows:
root = Tk()
canvas = Canvas(root, width=620, height=400, background = "salmon")
canvas.grid()
# draw x-axis lines
canvas.create_line(0,2, 500, 0, width = 2, fill = "firebrick")
canvas.create_line(505,2, 610, 0, width = 2, fill = "dark slate blue")
# draw histograms
for idx in range(len(main_counts[0])):
canvas.create_rectangle(idx*10, main_counts[0][idx], 10 +(idx*10), 0, fill = "medium sea green", outline = "firebrick")
canvas.create_text(idx*10 + 8, 40, text = idx + 1, font = ("Comic sans MS",8), fill = "firebrick")
for idx in range(len(star_counts[2])):
canvas.create_rectangle((505 + idx*10), star_counts[2][idx], (515 + (idx*10)), 0, fill = "gold", outline = "dark slate blue")
canvas.create_text(505 + idx*10 + 8, 120, text = idx + 1, font = ("Comic sans MS", 8) , fill = "dark slate blue")
root.mainloop()
I know that I am missing something quite simple and obvious to all of you, but I just can't see it or the way to make my y0 negative which will presumably solve the problem. I can also not see my x-axes, but that may be because they are occluded by the histogram bars.
Many thanks for your patience and help! Any other suggestions about formatting the graphs will be welcomed including suggestions of best font to use for small digit screen display
The system coordinates start in the upper-left corner so you should write something like:
main_counts =[[10, 20, 30]]
for idx in range(len(main_counts[0])):
canvas.create_rectangle(idx*10, 200 -main_counts[0][idx], 10 +(idx*10), 200, fill = "medium sea green", outline = "firebrick")
canvas.create_text(idx*10 + 8, 210, text = idx + 1, font = ("Comic sans MS",8), fill = "firebrick")
You can use enumerate for more readable code:
for idx, val in enumerate(main_counts[0]):
canvas.create_rectangle(idx*10, 200 -val, 10 +(idx*10), 200, fill = "medium sea green", outline = "firebrick")
canvas.create_text(idx*10 + 8, 210, text = idx + 1, font = ("Comic sans MS",8), fill = "firebrick")
In simple terms - Try to create the histogram from a point on the coordinate according to your value as your second argument in rectangle_crete() function, and then go upto the point where you want your base of the histogram should,which will me constant for all your histograms. Because Tkinter coordinates starts from (0,0) and goes from up to down.
An example code is here -
from Tkinter import *
from random import randint # for testing histogram
master = Tk()
w = Canvas(master, width=1000, height=500)
w.pack()
start_point = 70
padding_y = 450
width = 50
height = 450
list = []
for i in range(1,10):
list.append(randint(1,4))
for i in range(1,10):
end_point = start_point+width
w.create_rectangle(start_point, list[i-1]*100, end_point, height, fill="blue")
start_point += width+20
mainloop()