creating a text wheel QT/PYQT - c++

Hi I'm creating a network diagrame of nodes using networkX's circular layout.
It's been great laying out nodes in a circle however I want to add text to each node(a description) but the text must not overlap, so it remains readable
something like this textwheel. Notice how all the text juts outward from each node.
How can i do the same. I'm using pyqt/qt and I know of the painter.rotate method but I can't have a fixed rotation, what's the best way to workout the correct rotation for each node's description.
I'm drawing the nodes on a Qgraphicsscene, so I can get the boundingRect of the scene, and also get the center of the scene. I can also get the position of each node as output by NetworkX's cirular layout. Having the 2 points is a start. I'm not sure what the best way forward after that is.
If there are several articles that demonstrate a good solution please let me.
One additional wrinkle is that the nodes themselves can be moved by the user. But I imagine that once I've worked out how to draw the text, i can apply the same formula/method in the event that the node itself is moved by the user.
Just need a general way of working out the angle of the center of the node in relation to the center of the graphicssene.
If there are code samples that can point me in the right direction please share.
Thanks

Usually, the rotation of the text is the same as the rotation of the radius used to position the reference point on the text's rectangle.
The code below demonstrates how to use painter transforms to easily achieve the desired result. The text alignment trick is inspired by this answer.
import sys
from PyQt5.QtCore import QRect, QRectF, QSizeF, QPointF, Qt
from PyQt5.QtGui import QPainter, QPicture, QFont, QColor
from PyQt5.QtWidgets import QApplication, QLabel
def drawNode(painter, angle, radius, text):
size = 32767.0;
painter.save();
painter.rotate(-angle);
painter.translate(radius, 0);
painter.drawText(QRectF(0, -size/2.0, size, size), Qt.AlignVCenter, text);
painter.restore();
if __name__ == "__main__":
app = QApplication(sys.argv)
pic = QPicture()
pic.setBoundingRect(QRect(-100, -100, 200, 200))
p = QPainter(pic)
p.drawEllipse(0, 0, 3, 3)
p.setFont(QFont("Helvetica", 25))
for angle in range(0, 359, 30):
drawNode(p, angle, 50, str(angle))
p.end()
l = QLabel()
l.setPicture(pic);
l.show();
sys.exit(app.exec_())

Related

Find the width of an ink stroke in an image using OpenCV & C++

I have the following sample of handwriting taken with three different writing instruments:
Looking at the writing, I can tell that there is a distinct difference between the first two and the last one. My goal is to determine an approximation of the stroke thickness for each letter, allowing me to group them based on being thin or thick.
So far, I have tried looking into stroke width transform, but I have struggled to translate it to my example.
I am able to preprocess the image such that I am just left with just the contours of the test in question. For example, here is thick from the last line:
I suggest detecting contours with cv::findContours as you are doing and then compare bounding rectangle area and contour area. The thicker writing the greater coefficent (contourArea/boundingRectArea) will be.
This approach will help you. This will calcuate the stroke width.
from skimage.feature import peak_local_max
from skimage import img_as_float
def adaptive_thresholding(image):
output_image = cv2.adaptiveThreshold(image,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,21,2)
return output_image
def stroke_width(image):
dist = cv2.distanceTransform(cv2.subtract(255,image), cv2.DIST_L2, 5)
im = img_as_float(dist)
coordinates = peak_local_max(im, min_distance=15)
pixel_strength = []
for element in coordinates:
x = element[0]
y = element[1]
pixel_strength.append(np.asarray(dist)[x,y])
mean_pixel_strength = np.asarray(pixel_strength).mean()
return mean_pixel_strength
image = cv2.imread('Small3.JPG', 0)
process_image = adaptive_thresholding(image)
stroke_width(process_image)
A python implementation for this might go something like this, using Stroke Width Transform implementation of SWTloc.
Full Disclosure: I am the author of this library.
EDIT : Post v2.0.0
Transforming The Image
import swtloc as swt
imgpath = 'images/path_to_image.jpeg'
swtl = swt.SWTLocalizer(image_paths=imgpath)
swtImgObj = swtl.swtimages[0]
# Perform SWT Transformation with numba engine
swt_mat = swtImgObj.transformImage(auto_canny_sigma=1.0, gaussian_blurr=False,
minimum_stroke_width=3, maximum_stroke_width=50,
maximum_angle_deviation=np.pi/3)
Localize Letters
localized_letters = swtImgObj.localizeLetters()
Plot Histogram of Each Letters Strokes Widths
import seaborn as sns
import matplotlib.pyplot as plt
all_sws = []
for letter_label, letter in localized_letters.items():
all_sws.append(letter.stroke_widths_mean)
sns.displot(all_sws, bins=31)
From the distribution plot, it can be inferred that there might be three fontsize of the text available in the image - [3, 15, 27]

tkinter canvas create_arc method giving round edged arcs

I am drawing arcs in tkinter. They use the 'arc' method so only have an outer edge of a specified width and colour. Annoyingly some arcs seem to have randomly have a rounded edge. Does anyone know if this is a bug or if it can be fixed?
Example code:
w1.create_arc(xarc0,yarc0,xarc1,yarc1,start=arcstart,extent=arcextent,width=thick1,outline=mc,style="arc")
Pic of the problem attached.
After some experimenting, I believe this is a glitch
I ran a simulation to create 360 arcs, each with an extent from 1 - 360, I found that the only time when the arc has a curved end was when the extent of the arc was 90 or 271, I don't know why this happens but I don't think its supposed to.
The green arc is the simulation, the red and the blue arc are the arcs which have a curved end. I don't think there is a way to fix this problem, however you can change the extent from 90 to 91 as I've done for the orange arc, as you can see it has a straight end and there's not much difference.
Here's the code if you want to play around with it:
from tkinter import *
Window = Tk()
w1 = Canvas(Window)
w1.pack()
for x in range(360):
w1.after(5)
arc = w1.create_arc(20, 20, 150,100,start=0,extent=x,width=30,outline = "green", style="arc")
w1.update()
if x == 90 or x == 271:
print("Now!")
w1.create_arc(160,20, 300,100,start=0,extent=90,width=30,outline = "red", style="arc")
w1.create_arc(180,120, 320,200,start=0,extent=271,width=30,outline = "blue", style="arc")
w1.create_arc(0, 150, 140,230,start=0,extent=91,width=30,outline = "orange", style="arc")

Changing canvas scroll region on Python turtle canvas

Can I change the scrollregion on a Python turtle canvas? I want the drawing to move with it, not just the coordinates to shift. The appearance I'm going for is side-scroller like, where the screen's display region moves to center the turtle onscreen.
I've tried using turtle.setworldcoordinates(llx, lly, urx, ury), but, from the documentation, "This performs a screen.reset()". I've also looked at this SO question , but this involves scroll bars, will not center the turtle easily, and has a limited canvas space. What I'm looking for is something that:
Moves the display region to center the turtle
Also moves the drawing
Has an infinite scroll region
does not display scroll bars
can be called quickly with a function
My best guess would be to be able to have an infinite scrolled canvas somehow, then hide the scroll bars and set them according to turtle position.
Is this possible in Python 2.7? I don't mind if it uses tkinter as well.
EDIT: 6-3-15
I found the canvas.xview and canvas.yview functions, but they don't seem to work once I define screen = turtle.TurtleScreen(canvas), and TurtleScreen has no xview or yview functions. I can't seem to make this work.
Then I found turtle.ScrolledCanvas(). This seems ideal except it has no methods for setting scroll manually from the program. Can I set the scroll manually on a turtle.ScrolledCanvas()???
The position of a canvas can be changed without a reset using canvas.place() method. It will move the turtle and the drawings too, so the turtle needs to be relocated after every move.
The next code moves the canvas with Left and Right arrows and draws a circle with Space, while keeping the turtle in the center. No ScrolledCanvas needed, just a very large standard canvas:
import turtle
import Tkinter as tk
def keypress(event):
global xx, canvas, t, speed
ev = event.keysym
if ev == 'Left':
xx += speed
else:
xx -= speed
canvas.place(x=xx)
t.setposition((-canvas.winfo_width() / 4) - (xx + 250), 0)
return None
def drawCircle(_):
global t
t.pendown()
t.fillcolor(0, 0, 1.0)
t.fill(True)
t.circle(100)
t.fill(False)
t.fillcolor(0, 1, 0)
t.penup()
# Set the main window
window = tk.Tk()
window.geometry('500x500')
window.resizable(False, False)
# Create the canvas. Width is larger than window
canvas = turtle.Canvas(window, width=2000, height=500)
xx = -500
canvas.place(x=xx, y=0)
# Bring the turtle
t = turtle.RawTurtle(canvas)
t.shape('turtle') # nicer look
t.speed(0)
t.penup()
t.setposition((-canvas.winfo_width() / 4) - (xx + 250), 0)
# key binding
window.bind('<KeyPress-Left>', keypress)
window.bind('<KeyPress-Right>', keypress)
window.bind('<KeyPress-space>', drawCircle)
drawCircle(None)
speed = 3 # scrolling speed
window.mainloop()
Having a real infinite scrolling would require to redraw every item in the canvas every time with the required offset, instead of actually moving or scrolling the canvas. Functions like create_image() can give the illusion of movement with static backgrounds, but it resets the drawings.

Qt drawing a filled rounded rectangle with border

I want to draw a rectangle with rounded corners (border radius same for all 4 corners) with a specific color filling the entire rectangle, and a separate border color (say border is 1 px wide).
From my observation, Qt provides three methods - fillRect and drawRect and drawRoundedRect. I have tried them, they don't work like I want to. There is no method like fillRoundedRect. Which means that I can draw a rounded rectangle but it won't be filled with the color I want.
How do I do it? And also, I read that due to some aliasing problems, the corners are often rendered as unequal. How do I set it as equal for all four? Will painter.setRenderHint(QPainter::Antialiasing) suffice? Or do I have to do anything else?
You can create a QPainterPath, add the rounded rect to it, and then fill and stroke it:
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
QPainterPath path;
path.addRoundedRect(QRectF(10, 10, 100, 50), 10, 10);
QPen pen(Qt::black, 10);
p.setPen(pen);
p.fillPath(path, Qt::red);
p.drawPath(path);
Note that even with antialiasing, 1 px border will probably never really look good, especially on a low DPI desktop monitor, on a high DPI mobile device it will be almost invisible.
If you create the rectangle as QRectF(9.5, 9.5, 100, 50) it will look better with 1 px antialiased border, because it will "snap" on the right pixel:
The answer above (from #dtech) works great, but can sometimes end up with an uneven border around the roundedRect. Using QPainter.strokePath() instead of QPainter.drawPath() can fix this issue.
Here is a python implementation of QPushButton, with paintEvent reimplemented:
# I use PySide6, but whatever library should work.
from PySide6.QtWidgets import QPushButton
from PySide6.QtGui import QPainter, QPainterPath, QBrush, QPen
from PySide6.QtCore import Qt, QRectF
class RoundedButton(QPushButton):
def __init__(self, text, bordersize, outlineColor, fillColor):
super(RoundedButton, self).__init__()
self.bordersize = bordersize
self.outlineColor = outlineColor
self.fillColor = fillColor
self.setText(text)
def paintEvent(self, event):
# Create the painter
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# Create the path
path = QPainterPath()
# Set painter colors to given values.
pen = QPen(self.outlineColor, self.bordersize)
painter.setPen(pen)
brush = QBrush(self.fillColor)
painter.setBrush(brush)
rect = QRectF(event.rect())
# Slighly shrink dimensions to account for bordersize.
rect.adjust(self.bordersize/2, self.bordersize/2, -self.bordersize/2, -self.bordersize/2)
# Add the rect to path.
path.addRoundedRect(rect, 10, 10)
painter.setClipPath(path)
# Fill shape, draw the border and center the text.
painter.fillPath(path, painter.brush())
painter.strokePath(path, painter.pen())
painter.drawText(rect, Qt.AlignCenter, self.text())

Huge "Grid" of PushButtons lags awfully, buttons can't be enabled

In my project, I have 256 tiny PushButtons in one 16x16 grid layout. (Yeah that took forever.) Editing and running my program now is very laggy. Also, for some strange reason, Qt will not let me enable any of the buttons, but other buttons to the side work just fine?
Is there any easy way to determine which square of the grid was clicked without having a bunch of buttons? (Like following the cursor over an image maybe?)
Also, when each "square" of the grid is clicked, it becomes the "selection" and it needs to be the only "square" selected. (Think about it like a huge chess board)
Here is a pic: http://gyazo.com/988cdbb59b3d1f1873c41bf91b1408fd
Later on, I will need to do this again for a 54x54 size grid (2916 buttons) and I REALLY don't want to do it button by button.
Thanks for your time, I hope you understand my question :)
You can do this easy way, I've explained almost everything in code, but if you have any questions about it feel free to ask, and please, accept this answer if it solved your problem :)
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class DrawImage(QMainWindow):
def __init__(self, parent=None):
super(QMainWindow, self).__init__(parent)
self.setWindowTitle('Select Window')
#you can set grid size here ... 8x8, 16x16 , for bigger numbers (54x54) be sure your image is big enough, because QWidget can't be smaller then ~20 pixels
self.gridSize = 16
mainWidget = QWidget()
self.setCentralWidget(mainWidget)
self.scene = QGraphicsScene()
view = QGraphicsView(self.scene)
layout = QVBoxLayout()
layout.addWidget(view)
mainWidget.setLayout(layout)
self.image = QImage('image.JPG')# put your image name here, image (suppose to be grid) must be at the same folder or put full path
pixmapItem = QGraphicsPixmapItem(QPixmap(self.image), None, self.scene)
pixmapItem.mousePressEvent = self.pixelSelect
def pixelSelect( self, event ):
#add whatever you want to this widget,any functionality or you can add image for example, I've simply colored it
wdg = QWidget()
layout = QVBoxLayout()
palette = QPalette(wdg.palette())
palette.setBrush(QPalette.Background, QColor(200,255,255))
wdg.setPalette(palette)
wdg.setLayout(layout)
self.scene.addWidget(wdg)
#calculate size and position for added widget
imageSize = self.image.size()
width = imageSize.width()
height = imageSize.height()
#size
wgWidth = float(width)/self.gridSize
wgHeight = float(height)/self.gridSize
wdg.setFixedSize(wgWidth,wgHeight)
#position
wgXpos = int(event.pos().x()/wgWidth) * wgWidth
wgYpos = int(event.pos().y()/wgHeight) * wgHeight
wdg.move(wgXpos, wgYpos)
#which square is clicked?
print "square at row ", int(event.pos().y()/wgHeight)+1,", column ",int(event.pos().x()/wgWidth)+1, "is clicked"
def main():
app = QtGui.QApplication(sys.argv)
form = DrawImage()
form.show()
app.exec_()
if __name__ == '__main__':
main()
Also, if you want to display simple square image over your grid image, look at question/solution I had: QGraphicsPixmapItem won't show over the other QGraphicsPixmapItem
If you don't really need the appearance of buttons, I would create a QWidget subclass that implements a custom paintEvent and renders a grid of needed size (taking the widget size into account). Then, in the mouse events (up,down,move etc.) you can calculate which grid item was clicked with a simple formula. You can render cells with different colors to indicate selection or highlighting.
P.S.:I would really like to post some code from my implementations (i have done this two or three times) but the source codes are at my old company :)
You just create your own QGridLayout in order to be able to add the buttons easily.
I posted an answer to another question, showing you how to fill a custom made QGridLayout with a bunch of widgets sequentially. The buttons are added according to the maximum count of columns you specified. (Note: It's just a very rough example but enough to start from)
In your example you would create the custom grid layout with 16 columns and simply add your buttons.
To find out which button has been pressed (and to make connecting easier) you can use QSignalMapper.
For investigating the lag you could check the amount of (GDI-/User-) handles of your application (using ProcessExplorer for example). The handle count shouldn't be above 10.000.
I don't know why you can't enable the push buttons.