Skip to content

Commit 54e78a4

Browse files
authored
Merge pull request #1 from alhazacod/adding_interactive_functions
Now this is usable lol
2 parents e146596 + ed32856 commit 54e78a4

File tree

5 files changed

+256
-36
lines changed

5 files changed

+256
-36
lines changed

.pylintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
extension-pkg-whitelist=PyQt5

MatrixButtons.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import PyQt5.QtWidgets as QtWidgets
2+
from matplotlib.figure import Figure
3+
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
4+
from sympy import Matrix
5+
6+
class MatrixButtons(QtWidgets.QWidget):
7+
"""Widget for PyQt5 with the Matrix input and the buttons
8+
9+
...
10+
11+
Attributes
12+
----------
13+
layout: QtWidgets.QGridLayout
14+
"""
15+
def __init__(self):
16+
super().__init__()
17+
self.layout = QtWidgets.QGridLayout(self) # Set the layout
18+
19+
#First row
20+
self.matrix00 = QtWidgets.QLineEdit()
21+
self.matrix10 = QtWidgets.QLineEdit()
22+
self.matrix20 = QtWidgets.QLineEdit()
23+
self.addToLayout(self.matrix00,[0,0],[1,1])
24+
self.addToLayout(self.matrix10,[1,0],[1,1])
25+
self.addToLayout(self.matrix20,[2,0],[1,1])
26+
27+
# Second row
28+
self.matrix01 = QtWidgets.QLineEdit()
29+
self.matrix11 = QtWidgets.QLineEdit()
30+
self.matrix21 = QtWidgets.QLineEdit()
31+
self.addToLayout(self.matrix01,[0,1],[1,1])
32+
self.addToLayout(self.matrix11,[1,1],[1,1])
33+
self.addToLayout(self.matrix21,[2,1],[1,1])
34+
35+
# Third row
36+
self.matrix02 = QtWidgets.QLineEdit()
37+
self.matrix12 = QtWidgets.QLineEdit()
38+
self.matrix22 = QtWidgets.QLineEdit()
39+
self.addToLayout(self.matrix02,[0,2],[1,1])
40+
self.addToLayout(self.matrix12,[1,2],[1,1])
41+
self.addToLayout(self.matrix22,[2,2],[1,1])
42+
43+
# Button for plot the grid transformed by the matrix
44+
self.plotButton = QtWidgets.QPushButton('Plot')
45+
self.addToLayout(self.plotButton,[1,3],[1,1])
46+
47+
48+
def addToLayout(self, widget, position:list, size:list):
49+
self.layout.addWidget(widget,position[1],position[0],size[1],size[0])
50+
51+
def setMatrix(self,matrix):
52+
"""Add widget to layout
53+
"""
54+
# First row
55+
self.matrix00.setText(str(matrix[0,0]))
56+
self.matrix10.setText(str(matrix[0,1]))
57+
self.matrix20.setText(str(matrix[0,2]))
58+
59+
# Second row
60+
self.matrix01.setText(str(matrix[1,0]))
61+
self.matrix11.setText(str(matrix[1,1]))
62+
self.matrix21.setText(str(matrix[1,2]))
63+
64+
# Third row
65+
self.matrix02.setText(str(matrix[2,0]))
66+
self.matrix12.setText(str(matrix[2,1]))
67+
self.matrix22.setText(str(matrix[2,2]))

Plotting.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44

55
"""
66
TODO:
7-
- I should add the option of default colors and alpha but i couldn't, it's something to fix
7+
-
88
"""
99
class PlotWidget(QtWidgets.QWidget):
10-
"""
11-
12-
Widget for PyQt5 with the plot
10+
"""Widget for PyQt5 with the plot
1311
1412
...
1513
@@ -36,13 +34,37 @@ class PlotWidget(QtWidgets.QWidget):
3634
3735
canvas: matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg
3836
"""
39-
def __init__(self, x: list, y: list, z: list, colors: tuple, alpha: float):
37+
def __init__(self):
4038
super().__init__()
41-
self.fig = Figure()
42-
self.canvas = FigureCanvas(self.fig)
43-
self.axes = self.fig.add_subplot(111,projection='3d')
39+
self.fig = Figure() # Create a matplotlib figure
40+
self.canvas = FigureCanvas(self.fig) # Get the plot image
41+
self.axes = self.fig.add_subplot(111,projection='3d') # Create matpotlib axes
42+
43+
self.setWindowTitle('Linear transformations')
44+
45+
self.layout = QtWidgets.QGridLayout(self) # Set the layout
46+
47+
self.addToLayout(self.canvas, [0,0],[10,10]) # add the plot to the layout
48+
49+
def scatter(self,xyzgrid:list,colors:list):
50+
"""Scatter the data
51+
52+
...
4453
45-
layout = QtWidgets.QVBoxLayout(self)
46-
layout.addWidget(self.canvas)
54+
Parameters
55+
----------
56+
xyzgrid:list
57+
data for plotting
58+
xyzgrid[0] the x data
59+
xyzgrid[1] the y data
60+
xyzgrid[2] the z
61+
colors:list
62+
colors
63+
"""
64+
self.axes.clear()
65+
self.axes.scatter(xyzgrid[0],xyzgrid[1],xyzgrid[2],c = colors,alpha=0.7)
4766

48-
self.axes.scatter(x, y, z, c = colors, alpha = alpha)
67+
def addToLayout(self, widget, position:list = [0,0], size:list = [1,1]):
68+
"""Add widget to layout
69+
"""
70+
self.layout.addWidget(widget,position[1],position[0],size[1],size[0])

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Linear transformation visualization in 3D
2+
3+
## Why?
4+
5+
A linear transformation is a function from one vector space to another that respects the linear structure of each vector space ([Learn more](https://brilliant.org/wiki/linear-transformations/)). For visualizing this we can transform a vector as we can see in the Fig. 1.
6+
7+
<figure>
8+
<img src="vector_linearTransformation.png" alt="vector transformed" style="width:40%;
9+
display: block;
10+
margin-left: auto;
11+
margin-right: auto;">
12+
<figcaption style="text-align: center;">Fig.1 - Vector transformed with a transformation matrix</figcaption>
13+
</figure>
14+
15+
But it has an obvious problem, because the linear transformation transform the whole space, not just the vector, in other words it transforms the basis vectors thus every vector in the space gets transformed too. That's why i created this app so everyone can see how a transformation matrix change the whole space.
16+
17+
## Installation guide
18+
19+
### With the executable file
20+
21+
1. Install Python from the [python web](https://www.python.org/).
22+
2. Download the executable file for your OS (Linux or Windows).
23+
3. Execute the file.
24+
4. Start using.
25+
26+
The executable was created with [PyInstaller](http://www.pyinstaller.org/).
27+
28+
### Manual installation
29+
30+
1. Install Python from the [python web](https://www.python.org/).
31+
2. Install pip from the [pip web](https://pip.pypa.io/en/stable/installing/)
32+
3. Install the dependences using pip.
33+
- pip install sympy
34+
- pip install pyqt5
35+
- pip install numpy
36+
4. Download the main.py, Plotting.py and Matrix.py files.
37+
5. In the console go to the files location
38+
6. Run the main file with <code>python3.8 main.py</code>
39+
40+
## How to use
41+

main.py

Lines changed: 114 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,18 @@
44
Date: January 27 2021
55
"""
66
from sympy.matrices import Matrix #It's easier work with matrices in sympy than numpy
7-
from sympy import sin,cos,pi #Trigonometric functions
7+
from sympy import sin,cos,pi,N #Trigonometric functions
88
import matplotlib.pyplot as plt #Plotting
99
from numpy import linspace,column_stack,array #For create the linspace for the plotting
1010

1111
import PyQt5.QtWidgets as QtWidgets #Python GUI
12+
from PyQt5.QtGui import QFont #Pyqt5 fonts
1213

1314
from Plotting import PlotWidget #The plot object
1415

15-
"""Transformation matrix"""
16-
transformationMatrix = Matrix([[1, 0, 0],
17-
[0, 0.5, 0],
18-
[0, 0, 1]])
16+
from MatrixButtons import MatrixButtons # Interface class
1917

20-
def transformPoint(x,y,z):
18+
def transformPoint(x,y,z,transformationMatrix):
2119
"""Return the vector transformed
2220
2321
"""
@@ -28,11 +26,10 @@ def colorizer(x, y,z):
2826
2927
"""
3028
r = min(1, 1-y/3)
31-
g = min(1, 1+y/3)
29+
g = min(1, 1+x/3)
3230
b = 1/4 + x/16
3331
return (r, g, b)
3432

35-
3633
def get_grid(min,max):
3734
"""Return a grid in x, y and z
3835
@@ -51,28 +48,120 @@ def get_grid(min,max):
5148
xyzgrid = column_stack([[x, y,z] for x in xvals for y in yvals for z in zvals])
5249
return xyzgrid
5350

51+
def get_transformedGrid(originalGrid, transformationMatrix):
52+
"""Return a grid in x, y and z transformed
53+
54+
...
55+
56+
Parameters
57+
----------
58+
originalGrid
59+
60+
transformationMatrix
61+
62+
"""
63+
transformedGrid = transformPoint(originalGrid[0],originalGrid[1],originalGrid[2],transformationMatrix)
64+
65+
return array([transformedGrid[0,:][0,:], #
66+
transformedGrid[1,:][0,:], # Save the transformed grid as a numpy array
67+
transformedGrid[2,:][0,:]]) #
68+
69+
def adjust_plot(axes):
70+
axes.set_xlim([-2, 2]) #
71+
axes.set_ylim([-2, 2]) # Set the limits of the plot
72+
axes.set_zlim([-2, 2]) #
73+
axes.axis('on') # Show the axis
74+
axes.set_xlabel('x') #
75+
axes.set_ylabel('y') # Axes names
76+
axes.set_zlabel('z') #
77+
axes.grid(True) # Don't show the grid
78+
79+
def exceptionDialog(msg):
80+
dlg = QtWidgets.QErrorMessage()
81+
dlg.showMessage(msg)
82+
dlg.exec_()
83+
84+
def transformAndPlot(plot,matrix):
85+
86+
try:
87+
matrix = array(matrix)
88+
print(matrix)
89+
interface.setMatrix(matrix)
90+
xyzgrid = get_transformedGrid(originalGrid, Matrix([[N(matrix[0,0]),N(matrix[0,1]),N(matrix[0,2])],
91+
[N(matrix[1,0]),N(matrix[1,1]),N(matrix[1,2])],
92+
[N(matrix[2,0]),N(matrix[2,1]),N(matrix[2,2])]])) # get a transformed grid
93+
plot.scatter(xyzgrid,colors)
94+
adjust_plot(plot.axes)
95+
plot.fig.canvas.draw()
96+
plot.fig.canvas.flush_events()
97+
except:
98+
exceptionDialog('An error has ocurred. Remember that you only can write operations between numbers or trigonometric functions in the matrix boxes. ex. sin(3*pi/2) or sqrt(2)/2+5')
99+
100+
def examplesComboBox(window,comboBox):
101+
examples = {
102+
'Original Grid' :[[1, 0, 0],[0, 1, 0],[0, 0, 1]],
103+
'Personalized matrix' :[[1, 0, 0],[0, 1, 0],[0, 0, 1]],
104+
'Reflect in X' :[[-1, 0, 0],[0, 1, 0],[0, 0, 1]],
105+
'Reflect in Y' :[[1, 0, 0],[0, -1, 0],[0, 0, 1]],
106+
'Reflect in Z' :[[1, 0, 0],[0, 1, 0],[0, 0, -1]],
107+
'Stretch in Y' :[[1, 0, 0],[0, 2, 0],[0, 0, 1]],
108+
'Compact in Y' :[[1, 0, 0],[0, '1/2', 0],[0, 0, 1]],
109+
'X rotation 45º' :[[1, 0, 0],[0, 'cos(45*pi/180)', '-sin(45*pi/180)'],[0, 'sin(45*pi/180)', 'cos(45*pi/180)']],
110+
'Y rotation 45º' :[['cos(45*pi/180)', '-sin(45*pi/180)', 0],['sin(45*pi/180)', 'cos(45*pi/180)', 0],[0, 0, 1]],
111+
'Z rotation 45º' :[['cos(45*pi/180)', 0, '-sin(45*pi/180)'],[0, 1, 0],['sin(45*pi/180)', 0, 'cos(45*pi/180)']],
112+
'X-Y rotation 45º-10º':[['cos(45*pi/180)*cos(10*pi/180)','cos(45*pi/180)*sin(10*pi/180)-sin(45*pi/180)','cos(45*pi/180)*sin(10*pi/180)+sin(45*pi/180)*sin(10*pi/180)'],['sin(45*pi/180)*cos(10*pi/180)','sin(45*pi/180)*sin(10*pi/180)+cos(45*pi/180)','sin(45*pi/180)*sin(10*pi/180)-cos(45*pi/180)'],['-sin(10*pi/180)','cos(10*pi/180)','cos(10*pi/180)']],
113+
'Only XY dimension' :[[1, 0, 0],[0, 1, 0],[0, 0, 0]],
114+
'Only YZ dimension' :[[0, 0, 0],[0, 1, 0],[0, 0, 1]],
115+
'Only XZ dimension' :[[1, 0, 0],[0, 0, 0],[0, 0, 1]]
116+
}
117+
for key in examples:
118+
examplesWidget.addItem(key)
119+
120+
examplesWidget.activated[str].connect(lambda str: transformAndPlot(window,examples.get(str)))
121+
122+
window.addToLayout(examplesWidget, [13,2],[3,1])
123+
124+
def plotButton(plot,matrix):
125+
transformAndPlot(plot,matrix)
126+
examplesWidget.setCurrentIndex(1)
127+
54128
if __name__ == "__main__":
129+
"""Transformation matrix"""
130+
transformationMatrix = [[1, 0, 0],
131+
[0, 1, 0],
132+
[0, 0, 1]]
55133

56-
originalGrid = get_grid(-2,2)
57-
transformedGrid = transformPoint(originalGrid[0],originalGrid[1],originalGrid[2])
58-
xyzgrid = array([transformedGrid[0,:][0,:], #
59-
transformedGrid[1,:][0,:], # Save the transformed grid as a numpy array
60-
transformedGrid[2,:][0,:]]) #
134+
originalGrid = get_grid(-2,2) # Create a straigh grid
135+
xyzgrid = get_transformedGrid(originalGrid, Matrix(transformationMatrix)) # get a transformed grid
61136

62137
colors = list(map(colorizer, originalGrid[0], originalGrid[1], originalGrid[2])) # Asign the colors to the points
63138

64-
app = QtWidgets.QApplication([])
65-
plot = PlotWidget(xyzgrid[0], xyzgrid[1], xyzgrid[2], colors = colors, alpha = 0.7)
66-
67-
plot.axes.set_xlim([-2, 2]) #
68-
plot.axes.set_ylim([-2, 2]) # Set the limits of the plot
69-
plot.axes.set_zlim([-2, 2]) #
70-
plot.axes.axis('on') # Show the axis
71-
plot.axes.set_xlabel('x') #
72-
plot.axes.set_ylabel('y') # Axes names
73-
plot.axes.set_zlabel('z') #
74-
plot.axes.grid(False) # Don't show the grid
139+
app = QtWidgets.QApplication([]) # Create the app
140+
plot = PlotWidget() # Get the widget where the plot is
141+
142+
plot.scatter(xyzgrid,colors) # Scatter
143+
144+
adjust_plot(plot.axes) # Set the limits of the axes
75145
plot.axes.view_init(10,5) # View init at 15 and 5 degrees
146+
147+
examples_label = QtWidgets.QLabel('Examples')
148+
examples_label.setFont(QFont('Sans Serif', 20))
149+
plot.addToLayout(examples_label,[12,2],[1,1])
150+
examplesWidget = QtWidgets.QComboBox()
151+
examplesComboBox(plot,examplesWidget)
152+
153+
matrix_label = QtWidgets.QLabel('Transformation Matrix')
154+
matrix_label.setFont(QFont('Sans Serif', 20))
155+
plot.addToLayout(matrix_label,[12,4],[2,1])
156+
interface = MatrixButtons() # Get the interface
157+
plot.addToLayout(interface,[11,5],[10,2]) # add the interface to the plot layout
158+
159+
interface.setMatrix(array(transformationMatrix))
160+
161+
interface.plotButton.clicked.connect(
162+
lambda: plotButton(plot,[[interface.matrix00.text(), interface.matrix10.text(), interface.matrix20.text()],
163+
[interface.matrix01.text(), interface.matrix11.text(), interface.matrix21.text()],
164+
[interface.matrix02.text(), interface.matrix12.text(), interface.matrix22.text()]]))
76165

77-
plot.show()
166+
plot.show()
78167
app.exec()

0 commit comments

Comments
 (0)