CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
SRTTransform3D.py220 linesDownload Raw Back to pyqtgraph
1from math import atan2, degrees2 3import numpy as np4 5from . import SRTTransform6from .Qt import QtGui7from .Transform3D import Transform3D8from .Vector import Vector9 10 11class SRTTransform3D(Transform3D):12    """4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate13    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            return20        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.041            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 degrees63            '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 axis80        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'] = angle114        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.linalg124        for i in range(4):125            self.setRow(i, m.row(i))126        m = self.matrix().reshape(4,4)127        ## translation is 4th column128        self._state['pos'] = m[:3,3]129        130        ## scale is vector-length of first three columns131        scale = (m[:3,:3]**2).sum(axis=0)**0.5132        ## see whether there is an inversion133        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 invert136        self._state['scale'] = scale137        138        ## rotation axis is the eigenvector with eigenvalue=1139        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            raise147        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]].real154        axis /= ((axis**2).sum())**0.5155        self._state['axis'] = axis156        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] * self181        #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 __reduce__(self):219        return SRTTransform3D, (self.saveState(),)220 
Aluode/PerceptionLabPortable · CoolFace