We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Segfault when iterating on a transform-mapped Vector in a thread
<!-- In the following, please describe your issue in detail! -->
<!-- If some sections do not apply, just remove them. -->

### Short description
Take a Vector, map it with a transform, put it in a _threading_ Thread, iterate, and BOOM.

### Code to reproduce
<!-- Please provide a minimal working example that reproduces the issue in the code block below.
     Ideally, this should be a full example someone else could run without additional setup. -->

```python
import pyqtgraph as pg
import threading


def do_it():
    xform = pg.SRTTransform3D()
    v = xform.map(pg.Vector((0, 0, 0)))
    tuple(v)


pg.mkQApp()
threading.Thread(target=do_it).start()
```

### Expected behavior
<!-- What should happen? -->

### Real behavior
<!-- What happens? -->
`SIGSEGV`

### Tested environment(s)

 * PyQtGraph version: 0.13.8dev0
 * Qt Python binding: PyQt5 5.15.10 Qt 5.15.2
 * Python version: 3.11
 * NumPy version: 1.26.3
 * Operating system: linux
 * Installation method: git

### Additional context

Workaround: use a `QThread`, and this isn't an issue.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pyqtgraph/SRTTransform3D.py`
Content:
```
1 from math import atan2, degrees
2 
3 import numpy as np
4 
5 from . import SRTTransform
6 from .Qt import QtGui
7 from .Transform3D import Transform3D
8 from .Vector import Vector
9 
10 
11 class SRTTransform3D(Transform3D):
12     """4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate
13     This transform has no shear; angles are always preserved.
14     """
15     def __init__(self, init=None):
16         Transform3D.__init__(self)
17         self.reset()
18         if init is None:
19             return
20         if init.__class__ is QtGui.QTransform:
21             init = SRTTransform.SRTTransform(init)
22         
23         if isinstance(init, dict):
24             self.restoreState(init)
25         elif isinstance(init, SRTTransform3D):
26             self._state = {
27                 'pos': Vector(init._state['pos']),
28                 'scale': Vector(init._state['scale']),
29                 'angle': init._state['angle'],
30                 'axis': Vector(init._state['axis']),
31             }
32             self.update()
33         elif isinstance(init, SRTTransform.SRTTransform):
34             self._state = {
35                 'pos': Vector(init._state['pos']),
36                 'scale': Vector(init._state['scale']),
37                 'angle': init._state['angle'],
38                 'axis': Vector(0, 0, 1),
39             }
40             self._state['scale'][2] = 1.0
41             self.update()
42         elif isinstance(init, QtGui.QMatrix4x4):
43             self.setFromMatrix(init)
44         else:
45             raise Exception("Cannot build SRTTransform3D from argument type:", type(init))
46 
47         
48     def getScale(self):
49         return Vector(self._state['scale'])
50         
51     def getRotation(self):
52         """Return (angle, axis) of rotation"""
53         return self._state['angle'], Vector(self._state['axis'])
54         
55     def getTranslation(self):
56         return Vector(self._state['pos'])
57     
58     def reset(self):
59         self._state = {
60             'pos': Vector(0,0,0),
61             'scale': Vector(1,1,1),
62             'angle': 0.0,  ## in degrees
63             'axis': (0, 0, 1)
64         }
65         self.update()
66         
67     def translate(self, *args):
68         """Adjust the translation of this transform"""
69         t = Vector(*args)
70         self.setTranslate(self._state['pos']+t)
71         
72     def setTranslate(self, *args):
73         """Set the translation of this transform"""
74         self._state['pos'] = Vector(*args)
75         self.update()
76         
77     def scale(self, *args):
78         """adjust the scale of this transform"""
79         ## try to prevent accidentally setting 0 scale on z axis
80         if len(args) == 1 and hasattr(args[0], '__len__'):
81             args = args[0]
82         if len(args) == 2:
83             args = args + (1,)
84             
85         s = Vector(*args)
86         self.setScale(self._state['scale'] * s)
87         
88     def setScale(self, *args):
89         """Set the scale of this transform"""
90         if len(args) == 1 and hasattr(args[0], '__len__'):
91             args = args[0]
92         if len(args) == 2:
93             args = args + (1,)
94         self._state['scale'] = Vector(*args)
95         self.update()
96         
97     def rotate(self, angle, axis=(0,0,1)):
98         """Adjust the rotation of this transform"""
99         origAxis = self._state['axis']
100         if axis[0] == origAxis[0] and axis[1] == origAxis[1] and axis[2] == origAxis[2]:
101             self.setRotate(self._state['angle'] + angle)
102         else:
103             m = QtGui.QMatrix4x4()
104             m.translate(*self._state['pos'])
105             m.rotate(self._state['angle'], *self._state['axis'])
106             m.rotate(angle, *axis)
107             m.scale(*self._state['scale'])
108             self.setFromMatrix(m)
109         
110     def setRotate(self, angle, axis=(0,0,1)):
111         """Set the transformation rotation to angle (in degrees)"""
112         
113         self._state['angle'] = angle
114         self._state['axis'] = Vector(axis)
115         self.update()
116     
117     def setFromMatrix(self, m):
118         """
119         Set this transform based on the elements of *m*
120         The input matrix must be affine AND have no shear,
121         otherwise the conversion will most likely fail.
122         """
123         import numpy.linalg
124         for i in range(4):
125             self.setRow(i, m.row(i))
126         m = self.matrix().reshape(4,4)
127         ## translation is 4th column
128         self._state['pos'] = m[:3,3]
129         
130         ## scale is vector-length of first three columns
131         scale = (m[:3,:3]**2).sum(axis=0)**0.5
132         ## see whether there is an inversion
133         z = np.cross(m[0, :3], m[1, :3])
134         if np.dot(z, m[2, :3]) < 0:
135             scale[1] *= -1  ## doesn't really matter which axis we invert
136         self._state['scale'] = scale
137         
138         ## rotation axis is the eigenvector with eigenvalue=1
139         r = m[:3, :3] / scale[np.newaxis, :]
140         try:
141             evals, evecs = numpy.linalg.eig(r)
142         except:
143             print("Rotation matrix: %s" % str(r))
144             print("Scale: %s" % str(scale))
145             print("Original matrix: %s" % str(m))
146             raise
147         eigIndex = np.argwhere(np.abs(evals-1) < 1e-6)
148         if len(eigIndex) < 1:
149             print("eigenvalues: %s" % str(evals))
150             print("eigenvectors: %s" % str(evecs))
151             print("index: %s, %s" % (str(eigIndex), str(evals-1)))
152             raise Exception("Could not determine rotation axis.")
153         axis = evecs[:,eigIndex[0,0]].real
154         axis /= ((axis**2).sum())**0.5
155         self._state['axis'] = axis
156         
157         ## trace(r) == 2 cos(angle) + 1, so:
158         cos = (r.trace()-1)*0.5  ## this only gets us abs(angle)
159         
160         ## The off-diagonal values can be used to correct the angle ambiguity, 
161         ## but we need to figure out which element to use:
162         axisInd = np.argmax(np.abs(axis))
163         rInd,sign = [((1,2), -1), ((0,2), 1), ((0,1), -1)][axisInd]
164         
165         ## Then we have r-r.T = sin(angle) * 2 * sign * axis[axisInd];
166         ## solve for sin(angle)
167         sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd])
168         
169         ## finally, we get the complete angle from arctan(sin/cos)
170         self._state['angle'] = degrees(atan2(sin, cos))
171         if self._state['angle'] == 0:
172             self._state['axis'] = (0,0,1)
173         
174     def as2D(self):
175         """Return a QTransform representing the x,y portion of this transform (if possible)"""
176         return SRTTransform.SRTTransform(self)
177 
178     #def __div__(self, t):
179         #"""A / B  ==  B^-1 * A"""
180         #dt = t.inverted()[0] * self
181         #return SRTTransform.SRTTransform(dt)
182         
183     #def __mul__(self, t):
184         #return SRTTransform.SRTTransform(QtGui.QTransform.__mul__(self, t))
185 
186     def saveState(self):
187         p = self._state['pos']
188         s = self._state['scale']
189         ax = self._state['axis']
190         #if s[0] == 0:
191             #raise Exception('Invalid scale: %s' % str(s))
192         return {
193             'pos': (p[0], p[1], p[2]), 
194             'scale': (s[0], s[1], s[2]), 
195             'angle': self._state['angle'], 
196             'axis': (ax[0], ax[1], ax[2])
197         }
198 
199     def restoreState(self, state):
200         self._state['pos'] = Vector(state.get('pos', (0.,0.,0.)))
201         scale = state.get('scale', (1.,1.,1.))
202         scale = tuple(scale) + (1.,) * (3-len(scale))
203         self._state['scale'] = Vector(scale)
204         self._state['angle'] = state.get('angle', 0.)
205         self._state['axis'] = state.get('axis', (0, 0, 1))
206         self.update()
207 
208     def update(self):
209         Transform3D.setToIdentity(self)
210         ## modifications to the transform are multiplied on the right, so we need to reverse order here.
211         Transform3D.translate(self, *self._state['pos'])
212         Transform3D.rotate(self, self._state['angle'], *self._state['axis'])
213         Transform3D.scale(self, *self._state['scale'])
214 
215     def __repr__(self):
216         return str(self.saveState())
217         
218     def matrix(self, nd=3):
219         if nd == 3:
220             return np.array(self.copyDataTo()).reshape(4,4)
221         elif nd == 2:
222             m = np.array(self.copyDataTo()).reshape(4,4)
223             m[2] = m[3]
224             m[:,2] = m[:,3]
225             return m[:3,:3]
226         else:
227             raise Exception("Argument 'nd' must be 2 or 3")
228 
229     def __reduce__(self):
230         return SRTTransform3D, (self.saveState(),)
231
```

Path: `pyqtgraph/Transform3D.py`
Content:
```
1 import numpy as np
2 
3 from . import functions as fn
4 from .Qt import QtGui
5 from .Vector import Vector
6 
7 
8 class Transform3D(QtGui.QMatrix4x4):
9     """
10     Extension of QMatrix4x4 with some helpful methods added.
11     """
12     def __init__(self, *args):
13         if len(args) == 1:
14             if isinstance(args[0], (list, tuple, np.ndarray)):
15                 args = [x for y in args[0] for x in y]
16                 if len(args) != 16:
17                     raise TypeError("Single argument to Transform3D must have 16 elements.")
18             elif isinstance(args[0], QtGui.QMatrix4x4):
19                 args = list(args[0].copyDataTo())
20         
21         QtGui.QMatrix4x4.__init__(self, *args)
22         
23     def matrix(self, nd=3):
24         if nd == 3:
25             return np.array(self.copyDataTo()).reshape(4,4)
26         elif nd == 2:
27             m = np.array(self.copyDataTo()).reshape(4,4)
28             m[2] = m[3]
29             m[:,2] = m[:,3]
30             return m[:3,:3]
31         else:
32             raise Exception("Argument 'nd' must be 2 or 3")
33         
34     def map(self, obj):
35         """
36         Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates
37         """
38         if isinstance(obj, np.ndarray) and obj.shape[0] in (2,3):
39             if obj.ndim >= 2:
40                 return fn.transformCoordinates(self, obj)
41             elif obj.ndim == 1:
42                 v = QtGui.QMatrix4x4.map(self, Vector(obj))
43                 return np.array([v.x(), v.y(), v.z()])[:obj.shape[0]]
44         elif isinstance(obj, (list, tuple)):
45             v = QtGui.QMatrix4x4.map(self, Vector(obj))
46             return type(obj)([v.x(), v.y(), v.z()])[:len(obj)]
47         else:
48             return QtGui.QMatrix4x4.map(self, obj)
49             
50     def inverted(self):
51         inv, b = QtGui.QMatrix4x4.inverted(self)
52         return Transform3D(inv), b
53
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pyqtgraph/SRTTransform3D.py b/pyqtgraph/SRTTransform3D.py
--- a/pyqtgraph/SRTTransform3D.py
+++ b/pyqtgraph/SRTTransform3D.py
@@ -214,17 +214,6 @@
 
     def __repr__(self):
         return str(self.saveState())
-        
-    def matrix(self, nd=3):
-        if nd == 3:
-            return np.array(self.copyDataTo()).reshape(4,4)
-        elif nd == 2:
-            m = np.array(self.copyDataTo()).reshape(4,4)
-            m[2] = m[3]
-            m[:,2] = m[:,3]
-            return m[:3,:3]
-        else:
-            raise Exception("Argument 'nd' must be 2 or 3")
 
     def __reduce__(self):
         return SRTTransform3D, (self.saveState(),)
diff --git a/pyqtgraph/Transform3D.py b/pyqtgraph/Transform3D.py
--- a/pyqtgraph/Transform3D.py
+++ b/pyqtgraph/Transform3D.py
@@ -30,7 +30,7 @@
             return m[:3,:3]
         else:
             raise Exception("Argument 'nd' must be 2 or 3")
-        
+
     def map(self, obj):
         """
         Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates
@@ -45,8 +45,11 @@
             v = QtGui.QMatrix4x4.map(self, Vector(obj))
             return type(obj)([v.x(), v.y(), v.z()])[:len(obj)]
         else:
-            return QtGui.QMatrix4x4.map(self, obj)
-            
+            retval = QtGui.QMatrix4x4.map(self, obj)
+            if not isinstance(retval, type(obj)):
+                return type(obj)(retval)
+            return retval
+
     def inverted(self):
         inv, b = QtGui.QMatrix4x4.inverted(self)
         return Transform3D(inv), b

{"golden_diff": "diff --git a/pyqtgraph/SRTTransform3D.py b/pyqtgraph/SRTTransform3D.py\n--- a/pyqtgraph/SRTTransform3D.py\n+++ b/pyqtgraph/SRTTransform3D.py\n@@ -214,17 +214,6 @@\n \n     def __repr__(self):\n         return str(self.saveState())\n-        \n-    def matrix(self, nd=3):\n-        if nd == 3:\n-            return np.array(self.copyDataTo()).reshape(4,4)\n-        elif nd == 2:\n-            m = np.array(self.copyDataTo()).reshape(4,4)\n-            m[2] = m[3]\n-            m[:,2] = m[:,3]\n-            return m[:3,:3]\n-        else:\n-            raise Exception(\"Argument 'nd' must be 2 or 3\")\n \n     def __reduce__(self):\n         return SRTTransform3D, (self.saveState(),)\ndiff --git a/pyqtgraph/Transform3D.py b/pyqtgraph/Transform3D.py\n--- a/pyqtgraph/Transform3D.py\n+++ b/pyqtgraph/Transform3D.py\n@@ -30,7 +30,7 @@\n             return m[:3,:3]\n         else:\n             raise Exception(\"Argument 'nd' must be 2 or 3\")\n-        \n+\n     def map(self, obj):\n         \"\"\"\n         Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates\n@@ -45,8 +45,11 @@\n             v = QtGui.QMatrix4x4.map(self, Vector(obj))\n             return type(obj)([v.x(), v.y(), v.z()])[:len(obj)]\n         else:\n-            return QtGui.QMatrix4x4.map(self, obj)\n-            \n+            retval = QtGui.QMatrix4x4.map(self, obj)\n+            if not isinstance(retval, type(obj)):\n+                return type(obj)(retval)\n+            return retval\n+\n     def inverted(self):\n         inv, b = QtGui.QMatrix4x4.inverted(self)\n         return Transform3D(inv), b\n", "issue": "Segfault when iterating on a transform-mapped Vector in a thread\n<!-- In the following, please describe your issue in detail! -->\r\n<!-- If some sections do not apply, just remove them. -->\r\n\r\n### Short description\r\nTake a Vector, map it with a transform, put it in a _threading_ Thread, iterate, and BOOM.\r\n\r\n### Code to reproduce\r\n<!-- Please provide a minimal working example that reproduces the issue in the code block below.\r\n     Ideally, this should be a full example someone else could run without additional setup. -->\r\n\r\n```python\r\nimport pyqtgraph as pg\r\nimport threading\r\n\r\n\r\ndef do_it():\r\n    xform = pg.SRTTransform3D()\r\n    v = xform.map(pg.Vector((0, 0, 0)))\r\n    tuple(v)\r\n\r\n\r\npg.mkQApp()\r\nthreading.Thread(target=do_it).start()\r\n```\r\n\r\n### Expected behavior\r\n<!-- What should happen? -->\r\n\r\n### Real behavior\r\n<!-- What happens? -->\r\n`SIGSEGV`\r\n\r\n### Tested environment(s)\r\n\r\n * PyQtGraph version: 0.13.8dev0\r\n * Qt Python binding: PyQt5 5.15.10 Qt 5.15.2\r\n * Python version: 3.11\r\n * NumPy version: 1.26.3\r\n * Operating system: linux\r\n * Installation method: git\r\n\r\n### Additional context\r\n\r\nWorkaround: use a `QThread`, and this isn't an issue.\n", "before_files": [{"content": "from math import atan2, degrees\n\nimport numpy as np\n\nfrom . import SRTTransform\nfrom .Qt import QtGui\nfrom .Transform3D import Transform3D\nfrom .Vector import Vector\n\n\nclass SRTTransform3D(Transform3D):\n    \"\"\"4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate\n    This transform has no shear; angles are always preserved.\n    \"\"\"\n    def __init__(self, init=None):\n        Transform3D.__init__(self)\n        self.reset()\n        if init is None:\n            return\n        if init.__class__ is QtGui.QTransform:\n            init = SRTTransform.SRTTransform(init)\n        \n        if isinstance(init, dict):\n            self.restoreState(init)\n        elif isinstance(init, SRTTransform3D):\n            self._state = {\n                'pos': Vector(init._state['pos']),\n                'scale': Vector(init._state['scale']),\n                'angle': init._state['angle'],\n                'axis': Vector(init._state['axis']),\n            }\n            self.update()\n        elif isinstance(init, SRTTransform.SRTTransform):\n            self._state = {\n                'pos': Vector(init._state['pos']),\n                'scale': Vector(init._state['scale']),\n                'angle': init._state['angle'],\n                'axis': Vector(0, 0, 1),\n            }\n            self._state['scale'][2] = 1.0\n            self.update()\n        elif isinstance(init, QtGui.QMatrix4x4):\n            self.setFromMatrix(init)\n        else:\n            raise Exception(\"Cannot build SRTTransform3D from argument type:\", type(init))\n\n        \n    def getScale(self):\n        return Vector(self._state['scale'])\n        \n    def getRotation(self):\n        \"\"\"Return (angle, axis) of rotation\"\"\"\n        return self._state['angle'], Vector(self._state['axis'])\n        \n    def getTranslation(self):\n        return Vector(self._state['pos'])\n    \n    def reset(self):\n        self._state = {\n            'pos': Vector(0,0,0),\n            'scale': Vector(1,1,1),\n            'angle': 0.0,  ## in degrees\n            'axis': (0, 0, 1)\n        }\n        self.update()\n        \n    def translate(self, *args):\n        \"\"\"Adjust the translation of this transform\"\"\"\n        t = Vector(*args)\n        self.setTranslate(self._state['pos']+t)\n        \n    def setTranslate(self, *args):\n        \"\"\"Set the translation of this transform\"\"\"\n        self._state['pos'] = Vector(*args)\n        self.update()\n        \n    def scale(self, *args):\n        \"\"\"adjust the scale of this transform\"\"\"\n        ## try to prevent accidentally setting 0 scale on z axis\n        if len(args) == 1 and hasattr(args[0], '__len__'):\n            args = args[0]\n        if len(args) == 2:\n            args = args + (1,)\n            \n        s = Vector(*args)\n        self.setScale(self._state['scale'] * s)\n        \n    def setScale(self, *args):\n        \"\"\"Set the scale of this transform\"\"\"\n        if len(args) == 1 and hasattr(args[0], '__len__'):\n            args = args[0]\n        if len(args) == 2:\n            args = args + (1,)\n        self._state['scale'] = Vector(*args)\n        self.update()\n        \n    def rotate(self, angle, axis=(0,0,1)):\n        \"\"\"Adjust the rotation of this transform\"\"\"\n        origAxis = self._state['axis']\n        if axis[0] == origAxis[0] and axis[1] == origAxis[1] and axis[2] == origAxis[2]:\n            self.setRotate(self._state['angle'] + angle)\n        else:\n            m = QtGui.QMatrix4x4()\n            m.translate(*self._state['pos'])\n            m.rotate(self._state['angle'], *self._state['axis'])\n            m.rotate(angle, *axis)\n            m.scale(*self._state['scale'])\n            self.setFromMatrix(m)\n        \n    def setRotate(self, angle, axis=(0,0,1)):\n        \"\"\"Set the transformation rotation to angle (in degrees)\"\"\"\n        \n        self._state['angle'] = angle\n        self._state['axis'] = Vector(axis)\n        self.update()\n    \n    def setFromMatrix(self, m):\n        \"\"\"\n        Set this transform based on the elements of *m*\n        The input matrix must be affine AND have no shear,\n        otherwise the conversion will most likely fail.\n        \"\"\"\n        import numpy.linalg\n        for i in range(4):\n            self.setRow(i, m.row(i))\n        m = self.matrix().reshape(4,4)\n        ## translation is 4th column\n        self._state['pos'] = m[:3,3]\n        \n        ## scale is vector-length of first three columns\n        scale = (m[:3,:3]**2).sum(axis=0)**0.5\n        ## see whether there is an inversion\n        z = np.cross(m[0, :3], m[1, :3])\n        if np.dot(z, m[2, :3]) < 0:\n            scale[1] *= -1  ## doesn't really matter which axis we invert\n        self._state['scale'] = scale\n        \n        ## rotation axis is the eigenvector with eigenvalue=1\n        r = m[:3, :3] / scale[np.newaxis, :]\n        try:\n            evals, evecs = numpy.linalg.eig(r)\n        except:\n            print(\"Rotation matrix: %s\" % str(r))\n            print(\"Scale: %s\" % str(scale))\n            print(\"Original matrix: %s\" % str(m))\n            raise\n        eigIndex = np.argwhere(np.abs(evals-1) < 1e-6)\n        if len(eigIndex) < 1:\n            print(\"eigenvalues: %s\" % str(evals))\n            print(\"eigenvectors: %s\" % str(evecs))\n            print(\"index: %s, %s\" % (str(eigIndex), str(evals-1)))\n            raise Exception(\"Could not determine rotation axis.\")\n        axis = evecs[:,eigIndex[0,0]].real\n        axis /= ((axis**2).sum())**0.5\n        self._state['axis'] = axis\n        \n        ## trace(r) == 2 cos(angle) + 1, so:\n        cos = (r.trace()-1)*0.5  ## this only gets us abs(angle)\n        \n        ## The off-diagonal values can be used to correct the angle ambiguity, \n        ## but we need to figure out which element to use:\n        axisInd = np.argmax(np.abs(axis))\n        rInd,sign = [((1,2), -1), ((0,2), 1), ((0,1), -1)][axisInd]\n        \n        ## Then we have r-r.T = sin(angle) * 2 * sign * axis[axisInd];\n        ## solve for sin(angle)\n        sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd])\n        \n        ## finally, we get the complete angle from arctan(sin/cos)\n        self._state['angle'] = degrees(atan2(sin, cos))\n        if self._state['angle'] == 0:\n            self._state['axis'] = (0,0,1)\n        \n    def as2D(self):\n        \"\"\"Return a QTransform representing the x,y portion of this transform (if possible)\"\"\"\n        return SRTTransform.SRTTransform(self)\n\n    #def __div__(self, t):\n        #\"\"\"A / B  ==  B^-1 * A\"\"\"\n        #dt = t.inverted()[0] * self\n        #return SRTTransform.SRTTransform(dt)\n        \n    #def __mul__(self, t):\n        #return SRTTransform.SRTTransform(QtGui.QTransform.__mul__(self, t))\n\n    def saveState(self):\n        p = self._state['pos']\n        s = self._state['scale']\n        ax = self._state['axis']\n        #if s[0] == 0:\n            #raise Exception('Invalid scale: %s' % str(s))\n        return {\n            'pos': (p[0], p[1], p[2]), \n            'scale': (s[0], s[1], s[2]), \n            'angle': self._state['angle'], \n            'axis': (ax[0], ax[1], ax[2])\n        }\n\n    def restoreState(self, state):\n        self._state['pos'] = Vector(state.get('pos', (0.,0.,0.)))\n        scale = state.get('scale', (1.,1.,1.))\n        scale = tuple(scale) + (1.,) * (3-len(scale))\n        self._state['scale'] = Vector(scale)\n        self._state['angle'] = state.get('angle', 0.)\n        self._state['axis'] = state.get('axis', (0, 0, 1))\n        self.update()\n\n    def update(self):\n        Transform3D.setToIdentity(self)\n        ## modifications to the transform are multiplied on the right, so we need to reverse order here.\n        Transform3D.translate(self, *self._state['pos'])\n        Transform3D.rotate(self, self._state['angle'], *self._state['axis'])\n        Transform3D.scale(self, *self._state['scale'])\n\n    def __repr__(self):\n        return str(self.saveState())\n        \n    def matrix(self, nd=3):\n        if nd == 3:\n            return np.array(self.copyDataTo()).reshape(4,4)\n        elif nd == 2:\n            m = np.array(self.copyDataTo()).reshape(4,4)\n            m[2] = m[3]\n            m[:,2] = m[:,3]\n            return m[:3,:3]\n        else:\n            raise Exception(\"Argument 'nd' must be 2 or 3\")\n\n    def __reduce__(self):\n        return SRTTransform3D, (self.saveState(),)\n", "path": "pyqtgraph/SRTTransform3D.py"}, {"content": "import numpy as np\n\nfrom . import functions as fn\nfrom .Qt import QtGui\nfrom .Vector import Vector\n\n\nclass Transform3D(QtGui.QMatrix4x4):\n    \"\"\"\n    Extension of QMatrix4x4 with some helpful methods added.\n    \"\"\"\n    def __init__(self, *args):\n        if len(args) == 1:\n            if isinstance(args[0], (list, tuple, np.ndarray)):\n                args = [x for y in args[0] for x in y]\n                if len(args) != 16:\n                    raise TypeError(\"Single argument to Transform3D must have 16 elements.\")\n            elif isinstance(args[0], QtGui.QMatrix4x4):\n                args = list(args[0].copyDataTo())\n        \n        QtGui.QMatrix4x4.__init__(self, *args)\n        \n    def matrix(self, nd=3):\n        if nd == 3:\n            return np.array(self.copyDataTo()).reshape(4,4)\n        elif nd == 2:\n            m = np.array(self.copyDataTo()).reshape(4,4)\n            m[2] = m[3]\n            m[:,2] = m[:,3]\n            return m[:3,:3]\n        else:\n            raise Exception(\"Argument 'nd' must be 2 or 3\")\n        \n    def map(self, obj):\n        \"\"\"\n        Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates\n        \"\"\"\n        if isinstance(obj, np.ndarray) and obj.shape[0] in (2,3):\n            if obj.ndim >= 2:\n                return fn.transformCoordinates(self, obj)\n            elif obj.ndim == 1:\n                v = QtGui.QMatrix4x4.map(self, Vector(obj))\n                return np.array([v.x(), v.y(), v.z()])[:obj.shape[0]]\n        elif isinstance(obj, (list, tuple)):\n            v = QtGui.QMatrix4x4.map(self, Vector(obj))\n            return type(obj)([v.x(), v.y(), v.z()])[:len(obj)]\n        else:\n            return QtGui.QMatrix4x4.map(self, obj)\n            \n    def inverted(self):\n        inv, b = QtGui.QMatrix4x4.inverted(self)\n        return Transform3D(inv), b\n", "path": "pyqtgraph/Transform3D.py"}], "after_files": [{"content": "from math import atan2, degrees\n\nimport numpy as np\n\nfrom . import SRTTransform\nfrom .Qt import QtGui\nfrom .Transform3D import Transform3D\nfrom .Vector import Vector\n\n\nclass SRTTransform3D(Transform3D):\n    \"\"\"4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate\n    This transform has no shear; angles are always preserved.\n    \"\"\"\n    def __init__(self, init=None):\n        Transform3D.__init__(self)\n        self.reset()\n        if init is None:\n            return\n        if init.__class__ is QtGui.QTransform:\n            init = SRTTransform.SRTTransform(init)\n        \n        if isinstance(init, dict):\n            self.restoreState(init)\n        elif isinstance(init, SRTTransform3D):\n            self._state = {\n                'pos': Vector(init._state['pos']),\n                'scale': Vector(init._state['scale']),\n                'angle': init._state['angle'],\n                'axis': Vector(init._state['axis']),\n            }\n            self.update()\n        elif isinstance(init, SRTTransform.SRTTransform):\n            self._state = {\n                'pos': Vector(init._state['pos']),\n                'scale': Vector(init._state['scale']),\n                'angle': init._state['angle'],\n                'axis': Vector(0, 0, 1),\n            }\n            self._state['scale'][2] = 1.0\n            self.update()\n        elif isinstance(init, QtGui.QMatrix4x4):\n            self.setFromMatrix(init)\n        else:\n            raise Exception(\"Cannot build SRTTransform3D from argument type:\", type(init))\n\n        \n    def getScale(self):\n        return Vector(self._state['scale'])\n        \n    def getRotation(self):\n        \"\"\"Return (angle, axis) of rotation\"\"\"\n        return self._state['angle'], Vector(self._state['axis'])\n        \n    def getTranslation(self):\n        return Vector(self._state['pos'])\n    \n    def reset(self):\n        self._state = {\n            'pos': Vector(0,0,0),\n            'scale': Vector(1,1,1),\n            'angle': 0.0,  ## in degrees\n            'axis': (0, 0, 1)\n        }\n        self.update()\n        \n    def translate(self, *args):\n        \"\"\"Adjust the translation of this transform\"\"\"\n        t = Vector(*args)\n        self.setTranslate(self._state['pos']+t)\n        \n    def setTranslate(self, *args):\n        \"\"\"Set the translation of this transform\"\"\"\n        self._state['pos'] = Vector(*args)\n        self.update()\n        \n    def scale(self, *args):\n        \"\"\"adjust the scale of this transform\"\"\"\n        ## try to prevent accidentally setting 0 scale on z axis\n        if len(args) == 1 and hasattr(args[0], '__len__'):\n            args = args[0]\n        if len(args) == 2:\n            args = args + (1,)\n            \n        s = Vector(*args)\n        self.setScale(self._state['scale'] * s)\n        \n    def setScale(self, *args):\n        \"\"\"Set the scale of this transform\"\"\"\n        if len(args) == 1 and hasattr(args[0], '__len__'):\n            args = args[0]\n        if len(args) == 2:\n            args = args + (1,)\n        self._state['scale'] = Vector(*args)\n        self.update()\n        \n    def rotate(self, angle, axis=(0,0,1)):\n        \"\"\"Adjust the rotation of this transform\"\"\"\n        origAxis = self._state['axis']\n        if axis[0] == origAxis[0] and axis[1] == origAxis[1] and axis[2] == origAxis[2]:\n            self.setRotate(self._state['angle'] + angle)\n        else:\n            m = QtGui.QMatrix4x4()\n            m.translate(*self._state['pos'])\n            m.rotate(self._state['angle'], *self._state['axis'])\n            m.rotate(angle, *axis)\n            m.scale(*self._state['scale'])\n            self.setFromMatrix(m)\n        \n    def setRotate(self, angle, axis=(0,0,1)):\n        \"\"\"Set the transformation rotation to angle (in degrees)\"\"\"\n        \n        self._state['angle'] = angle\n        self._state['axis'] = Vector(axis)\n        self.update()\n    \n    def setFromMatrix(self, m):\n        \"\"\"\n        Set this transform based on the elements of *m*\n        The input matrix must be affine AND have no shear,\n        otherwise the conversion will most likely fail.\n        \"\"\"\n        import numpy.linalg\n        for i in range(4):\n            self.setRow(i, m.row(i))\n        m = self.matrix().reshape(4,4)\n        ## translation is 4th column\n        self._state['pos'] = m[:3,3]\n        \n        ## scale is vector-length of first three columns\n        scale = (m[:3,:3]**2).sum(axis=0)**0.5\n        ## see whether there is an inversion\n        z = np.cross(m[0, :3], m[1, :3])\n        if np.dot(z, m[2, :3]) < 0:\n            scale[1] *= -1  ## doesn't really matter which axis we invert\n        self._state['scale'] = scale\n        \n        ## rotation axis is the eigenvector with eigenvalue=1\n        r = m[:3, :3] / scale[np.newaxis, :]\n        try:\n            evals, evecs = numpy.linalg.eig(r)\n        except:\n            print(\"Rotation matrix: %s\" % str(r))\n            print(\"Scale: %s\" % str(scale))\n            print(\"Original matrix: %s\" % str(m))\n            raise\n        eigIndex = np.argwhere(np.abs(evals-1) < 1e-6)\n        if len(eigIndex) < 1:\n            print(\"eigenvalues: %s\" % str(evals))\n            print(\"eigenvectors: %s\" % str(evecs))\n            print(\"index: %s, %s\" % (str(eigIndex), str(evals-1)))\n            raise Exception(\"Could not determine rotation axis.\")\n        axis = evecs[:,eigIndex[0,0]].real\n        axis /= ((axis**2).sum())**0.5\n        self._state['axis'] = axis\n        \n        ## trace(r) == 2 cos(angle) + 1, so:\n        cos = (r.trace()-1)*0.5  ## this only gets us abs(angle)\n        \n        ## The off-diagonal values can be used to correct the angle ambiguity, \n        ## but we need to figure out which element to use:\n        axisInd = np.argmax(np.abs(axis))\n        rInd,sign = [((1,2), -1), ((0,2), 1), ((0,1), -1)][axisInd]\n        \n        ## Then we have r-r.T = sin(angle) * 2 * sign * axis[axisInd];\n        ## solve for sin(angle)\n        sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd])\n        \n        ## finally, we get the complete angle from arctan(sin/cos)\n        self._state['angle'] = degrees(atan2(sin, cos))\n        if self._state['angle'] == 0:\n            self._state['axis'] = (0,0,1)\n        \n    def as2D(self):\n        \"\"\"Return a QTransform representing the x,y portion of this transform (if possible)\"\"\"\n        return SRTTransform.SRTTransform(self)\n\n    #def __div__(self, t):\n        #\"\"\"A / B  ==  B^-1 * A\"\"\"\n        #dt = t.inverted()[0] * self\n        #return SRTTransform.SRTTransform(dt)\n        \n    #def __mul__(self, t):\n        #return SRTTransform.SRTTransform(QtGui.QTransform.__mul__(self, t))\n\n    def saveState(self):\n        p = self._state['pos']\n        s = self._state['scale']\n        ax = self._state['axis']\n        #if s[0] == 0:\n            #raise Exception('Invalid scale: %s' % str(s))\n        return {\n            'pos': (p[0], p[1], p[2]), \n            'scale': (s[0], s[1], s[2]), \n            'angle': self._state['angle'], \n            'axis': (ax[0], ax[1], ax[2])\n        }\n\n    def restoreState(self, state):\n        self._state['pos'] = Vector(state.get('pos', (0.,0.,0.)))\n        scale = state.get('scale', (1.,1.,1.))\n        scale = tuple(scale) + (1.,) * (3-len(scale))\n        self._state['scale'] = Vector(scale)\n        self._state['angle'] = state.get('angle', 0.)\n        self._state['axis'] = state.get('axis', (0, 0, 1))\n        self.update()\n\n    def update(self):\n        Transform3D.setToIdentity(self)\n        ## modifications to the transform are multiplied on the right, so we need to reverse order here.\n        Transform3D.translate(self, *self._state['pos'])\n        Transform3D.rotate(self, self._state['angle'], *self._state['axis'])\n        Transform3D.scale(self, *self._state['scale'])\n\n    def __repr__(self):\n        return str(self.saveState())\n\n    def __reduce__(self):\n        return SRTTransform3D, (self.saveState(),)\n", "path": "pyqtgraph/SRTTransform3D.py"}, {"content": "import numpy as np\n\nfrom . import functions as fn\nfrom .Qt import QtGui\nfrom .Vector import Vector\n\n\nclass Transform3D(QtGui.QMatrix4x4):\n    \"\"\"\n    Extension of QMatrix4x4 with some helpful methods added.\n    \"\"\"\n    def __init__(self, *args):\n        if len(args) == 1:\n            if isinstance(args[0], (list, tuple, np.ndarray)):\n                args = [x for y in args[0] for x in y]\n                if len(args) != 16:\n                    raise TypeError(\"Single argument to Transform3D must have 16 elements.\")\n            elif isinstance(args[0], QtGui.QMatrix4x4):\n                args = list(args[0].copyDataTo())\n        \n        QtGui.QMatrix4x4.__init__(self, *args)\n        \n    def matrix(self, nd=3):\n        if nd == 3:\n            return np.array(self.copyDataTo()).reshape(4,4)\n        elif nd == 2:\n            m = np.array(self.copyDataTo()).reshape(4,4)\n            m[2] = m[3]\n            m[:,2] = m[:,3]\n            return m[:3,:3]\n        else:\n            raise Exception(\"Argument 'nd' must be 2 or 3\")\n\n    def map(self, obj):\n        \"\"\"\n        Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates\n        \"\"\"\n        if isinstance(obj, np.ndarray) and obj.shape[0] in (2,3):\n            if obj.ndim >= 2:\n                return fn.transformCoordinates(self, obj)\n            elif obj.ndim == 1:\n                v = QtGui.QMatrix4x4.map(self, Vector(obj))\n                return np.array([v.x(), v.y(), v.z()])[:obj.shape[0]]\n        elif isinstance(obj, (list, tuple)):\n            v = QtGui.QMatrix4x4.map(self, Vector(obj))\n            return type(obj)([v.x(), v.y(), v.z()])[:len(obj)]\n        else:\n            retval = QtGui.QMatrix4x4.map(self, obj)\n            if not isinstance(retval, type(obj)):\n                return type(obj)(retval)\n            return retval\n\n    def inverted(self):\n        inv, b = QtGui.QMatrix4x4.inverted(self)\n        return Transform3D(inv), b\n", "path": "pyqtgraph/Transform3D.py"}]}