def setFrame(fi): QGLViewer.timeline.frameStep = 1 drawing_skel2 = True global animDict dofs = animDict['dofData'][(fi - animDict['frameNumbers'][0]) % len(animDict['frameNumbers'])].copy() #dofs[[2,5]] = dofData[0,[2,5]] Character.pose_skeleton(skelDict['Gs'], skelDict, dofs) QGLViewer.skel.setPose(skelDict['Gs']) if drawing_skel2: dofs = skelDict2['dofData'][(fi - skelDict2['frameNumbers'][0]) % len(skelDict2['frameNumbers'])].copy() Character.pose_skeleton(skelDict2['Gs'], skelDict2, dofs) QGLViewer.skel2.setPose(skelDict['Gs']) QGLViewer.view.updateGL()
def scoreIK(skelDict, chanValues, effectorData, effectorTargets, rootMat=None): """ Args: skelDict (GskelDict): The Skeleton to process Returns: ? Requires: Character.pose_skeleton ISCV.score_effectors """ Character.pose_skeleton(skelDict['Gs'], skelDict, chanValues, rootMat) return ( ISCV.score_effectors(skelDict['Gs'], effectorData[0], effectorData[1], effectorData[2], effectorTargets) / np.sum(effectorData[1]))**0.5
def skeleton_marker_positions(skelDict, rootMat, chanValues, effectorLabels, effectorData, markerWeights=None): """ Based on the pose implied by the chanValues and rootMat, compute the 3D world-space positions of the markers. Multiple effectors may determine the position of the marker. effectorLabels provides this mapping. The weights for the markers, if any, are set by markerWeights. Args: skelDict (GskelDict): the skeleton rootMat (float[3][4]): reference frame of the Skeleton. chanValues (float[]) List of channel values to pose the skeleton effectorLabels : the marker that each effector determines effectorData : (effectorJoints, effectorOffsets, ...) markerWeights : the weight that each effector has on its marker Returns: int[]: Labels for the 3D positions of the markers. float[][3]: 3D positions of where the target would be in the pose. Requires: Character.pose_skeleton ISCV.marker_positions """ Character.pose_skeleton(skelDict['Gs'], skelDict, chanValues, rootMat) labels = np.unique(effectorLabels) els2 = np.int32([list(labels).index(x) for x in effectorLabels]) x3ds = ISCV.marker_positions(skelDict['Gs'], effectorData[0], effectorData[1], els2, markerWeights) return x3ds, labels
def loadVSS(fn): '''Decode a Vicon Skeleton file (VST format). VSK is labeling skeleton. VSS is solving skeleton.''' import xml.etree.cElementTree as ET import numpy as np dom = ET.parse(fn) parameters = dom.findall('Parameters')[0] params = dict([(p.get('NAME'),p.get('VALUE')) for p in parameters]) sticks = dom.findall('MarkerSet')[0].find('Sticks') sticksPairs = [(x.get('MARKER1'),x.get('MARKER2')) for x in sticks] sticksColour= [np.fromstring(x.get('RGB1', '255 255 255'), dtype=np.uint8, sep=' ') for x in sticks] hasTargetSet = True try: markers = dom.findall('TargetSet')[0].find('Targets') except: markers = dom.findall('MarkerSet')[0].find('Markers'); hasTargetSet = False markerOffsets = [x.get('POSITION').split() for x in markers] def ev(x,params): for k,v in params.items(): x = x.replace(k,v) return float(x) # eval(x) markerOffsets = [[ev(x,params) for x in mp] for mp in markerOffsets] markerColour= [np.fromstring(col, dtype=np.uint8, sep=' ') for col in \ [x.get('MARKER', x.get('RGB')) for x in dom.findall('MarkerSet')[0].find('Markers')]] colouredMarkers = [x.get('MARKER', x.get('NAME')) for x in dom.findall('MarkerSet')[0].find('Markers')] markerNames = [x.get('MARKER', x.get('NAME')) for x in markers] markerWeights = [float(x.get('WEIGHT')) if hasTargetSet else 1.0 for x in markers] markerParents = [x.get('SEGMENT') for x in markers] skeleton = dom.findall('Skeleton')[0] # skeleton is defined as a tree of Segments # Segment contains Joint and Segment # Joint is JointDummy(0)/JointHinge(1)/JointHardySpicer(2)/JointBall(3)/JointFree(6), containing JointTemplate def ap(skeleton, parent, skel): for seg in skeleton: if seg.tag == 'Segment': skel.append([seg.get('NAME'),parent,seg.attrib]) ap(seg, len(skel)-1, skel) else: skel[parent].extend([seg.tag,seg.attrib,{} if len(seg) == 0 else seg[0].attrib]) return skel # recursively parse the skeleton root = ap(skeleton, -1, []) assert(len(markerParents) == len(markerOffsets)) def cqToR(rs, R): '''Given a compressed quaternion, form a 3x3 rotation matrix.''' angle = np.dot(rs,rs)**0.5 scale = (np.sin(angle*0.5)/angle if angle > 1e-8 else 0.5) q = np.array([rs[0]*scale,rs[1]*scale,rs[2]*scale,np.cos(angle*0.5)], dtype=np.float32) q = np.outer(q, q)*2 R[:3,:3] = [ [1.0-q[1, 1]-q[2, 2], q[0, 1]-q[2, 3], q[0, 2]+q[1, 3]], [ q[0, 1]+q[2, 3], 1.0-q[0, 0]-q[2, 2], q[1, 2]-q[0, 3]], [ q[0, 2]-q[1, 3], q[1, 2]+q[0, 3], 1.0-q[0, 0]-q[1, 1]]] def float3(x): return np.array(map(lambda x:ev(x,params), x.split()),dtype=np.float32) def mats(x): preT = x.get('PRE-POSITION', '0 0 0') postT = x.get('POST-POSITION', '0 0 0') preR = x.get('PRE-ORIENTATION', '0 0 0') postR = x.get('POST-ORIENTATION', '0 0 0') pre = np.zeros((3,4),dtype=np.float32) post = np.zeros((3,4),dtype=np.float32) pre[:,3] = float3(preT) post[:,3] = float3(postT) cqToR(float3(preR), pre[:3,:3]) cqToR(float3(postR), post[:3,:3]) return pre,post name = fn.rpartition('/')[2].partition('.')[0] numBones = len(root) jointNames = [r[0] for r in root] markerParents = np.array([jointNames.index(mp) for mp in markerParents],dtype=np.int32) jointNames[0] = 'root' # !!!! WARNING !!!! jointParents = [r[1] for r in root] jointData = [mats(r[4]) for r in root] jointTypes = [r[3] for r in root] # JointDummy(0)/JointHinge(1)/JointHardySpicer(2)/JointBall(3)/JointFree(6) #jointTemplates = [mats(r[5]) for r in root] # JointTemplate ... contains the same data as jointTypes jointAxes = [r[4].get('AXIS',r[4].get('AXIS-PAIR',r[4].get('EULER-ORDER','XYZ'))) for r in root] # order jointTs = [r[4].get('T',None) for r in root] Gs = np.zeros((numBones,3,4),dtype=np.float32) # GLOBAL mats Ls = np.zeros((numBones,3,4),dtype=np.float32) # LOCAL mats Bs = np.zeros((numBones,3),dtype=np.float32) # BONES for ji,pi in enumerate(jointParents): if pi == -1: Ls[ji] = jointData[ji][0] else: np.dot(jointData[pi][1][:,:3],jointData[ji][0],out=Ls[ji]); Ls[ji,:,3] += jointData[pi][1][:,3] dofNames = [] jointChans = [] # tx=0,ty,tz,rx,ry,rz jointChanSplits = [0] # TODO: locked channels for ji,(jt,T) in enumerate(zip(jointTypes,jointTs)): jointChanSplits.append(len(jointChans)) if jt == 'JointDummy': assert(T is None) elif jt == 'JointHinge': assert(T == '* ') jointChans.append(jointAxes[ji].split().index('1')+3) elif jt == 'JointHardySpicer': assert(T == '* * ') ja = jointAxes[ji].split() jointChans.append(ja.index('1',3)) jointChans.append(ja.index('1')+3) elif jt == 'JointBall': assert(T == '* * * ') ja = jointAxes[ji] jointChans.append(ord(ja[0])-ord('X')+3) jointChans.append(ord(ja[1])-ord('X')+3) jointChans.append(ord(ja[2])-ord('X')+3) elif jt == 'JointFree': assert(T == '* * * * * * ' or T is None) # version 1 of the file apparently doesn't fill this! ja = jointAxes[ji] jointChans.append(0) jointChans.append(1) jointChans.append(2) jointChanSplits[-1] = len(jointChans) jointChans.append(ord(ja[0])-ord('X')+3) jointChans.append(ord(ja[1])-ord('X')+3) jointChans.append(ord(ja[2])-ord('X')+3) for jc in jointChans[jointChanSplits[-2]:]: dofNames.append(jointNames[ji]+':'+'tx ty tz rx ry rz'.split()[jc]) jointChanSplits.append(len(jointChans)) numDofs = len(dofNames) # fill Gs chanValues = np.zeros(numDofs,dtype=np.float32) rootMat = np.eye(3, 4, dtype=np.float32) # fill Bs; TODO add dummy joints to store the extra bones (where multiple joints have the same parent) for ji,pi in enumerate(jointParents): if pi != -1: Bs[pi] = Ls[ji,:,3] Bs[np.where(Bs*Bs<0.01)] = 0 # zero out bones < 0.1mm # TODO: compare skeleton with ASF exported version skel_dict = { 'markerOffsets' : np.array(markerOffsets, dtype=np.float32), 'markerParents' : markerParents, 'markerNames' : markerNames, 'markerNamesUnq' : colouredMarkers, 'markerColour' : markerColour, 'markerWeights' : np.array(markerWeights,dtype=np.float32), 'numMarkers' : len(markerNames), 'sticks' : sticksPairs, 'sticksColour' : sticksColour, 'name' : str(name), 'numJoints' : int(numBones), 'jointNames' : jointNames, # list of strings 'jointIndex' : dict([(k,v) for v,k in enumerate(jointNames)]), # dict of string:int 'jointParents' : np.array(jointParents,dtype=np.int32), 'jointChans' : np.array(jointChans,dtype=np.int32), # 0 to 5 : tx,ty,tz,rx,ry,rz 'jointChanSplits': np.array(jointChanSplits,dtype=np.int32), 'chanNames' : dofNames, # list of strings 'chanValues' : np.zeros(numDofs,dtype=np.float32), 'numChans' : int(numDofs), 'Bs' : np.array(Bs, dtype=np.float32), 'Ls' : np.array(Ls, dtype=np.float32), 'Gs' : np.array(Gs, dtype=np.float32), 'rootMat' : rootMat, } Character.pose_skeleton(skel_dict['Gs'], skel_dict) return skel_dict
def solveIK1Ray(skelDict, effectorData, x3ds, effectorIndices_3d, E, effectorIndices_2d, outerIts=10, rootMat=None): """ solveIK routine form Label.py - Has Single ray constraint equations enables Given effectors (joint, offset, weight) and constraints for those (3d and 2d), solve for the skeleton pose. Effector offsets, weights and targets are 3-vectors Args: skelDict (GskelDict): The Skeleton to process effectorData (big o'l structure!): effectorJoints, effectorOffsets, effectorWeights, usedChannels, usedChannelWeights, usedCAEs, usedCAEsSplits x3ds (float[][3]): 3D Reconstructions effectorIndices_3d (?): What's this? E (): Equations for 1-Ray constraints, or MDMA. effectorIndices_2d (?): What's this? outerIts (int): IK Iterations to solve the skeleton. Default = 10 rootMat (float[3][4]): reference frame of the Skeleton. Default = None Returns: None: The result is an update of the skelDict to the solution - chanValues, channelMats, and Gs. Requires: Character.pose_skeleton_with_chan_mats ISCV.derror_dchannel_single_ray ISCV.JTJ_single_ray """ if rootMat is None: rootMat = np.eye(3, 4, dtype=np.float32) effectorJoints, effectorOffsets, effectorWeightsOld, usedChannels, usedChannelWeights, usedCAEs, usedCAEsSplits = effectorData chanValues = skelDict['chanValues'] jointParents = skelDict['jointParents'] Gs = skelDict['Gs'] Ls = skelDict['Ls'] jointChans = skelDict['jointChans'] jointChanSplits = skelDict['jointChanSplits'] numChannels = jointChanSplits[-1] numEffectors = len(effectorJoints) num3ds = len(effectorIndices_3d) num2ds = len(effectorIndices_2d) effectorOffsets = np.copy(effectorOffsets[:, :, 3]) effectorWeights = np.zeros(numEffectors, dtype=np.float32) effectorWeights[ effectorIndices_3d] = 1 # TODO Why does this fail? effectorWeightsOld[effectorIndices_3d,0,3] effectorWeights[ effectorIndices_2d] = 1 # effectorWeightsOld[effectorIndices_2d,0,3] numUsedChannels = len(usedChannels) channelMats = np.zeros((numChannels, 3, 4), dtype=np.float32) effectors = np.zeros((numEffectors, 3), dtype=np.float32) residual = np.zeros((num3ds, 3), dtype=np.float32) residual2 = np.zeros((num2ds, 2), dtype=np.float32) derrors = np.zeros((numUsedChannels, numEffectors, 3), dtype=np.float32) delta = np.zeros((numUsedChannels), dtype=np.float32) JTJ = np.zeros((numUsedChannels, numUsedChannels), dtype=np.float32) JTB = np.zeros((numUsedChannels), dtype=np.float32) JT = derrors.reshape(numUsedChannels, -1) JTJdiag = np.diag_indices_from(JTJ) for it in xrange(outerIts): # TODO, only usedChannels are changing, only update the matrices that have changed after the first iteration. # updates the channelMats and Gs Character.pose_skeleton_with_chan_mats(channelMats, Gs, skelDict, chanValues, rootMat) bestScore = ISCV.pose_effectors_single_ray( effectors, residual, residual2, Gs, effectorJoints, effectorOffsets, effectorWeights, x3ds, effectorIndices_3d, E, effectorIndices_2d) if np.sum(residual * residual) + np.sum( residual2 * residual2) <= 1e-5 * (num3ds + num2ds): break # early termination ISCV.derror_dchannel_single_ray(derrors, channelMats, usedChannels, usedChannelWeights, usedCAEs, usedCAEsSplits, jointChans, effectors, effectorWeights) # J = d_effectors/dc # err(c) = x3ds - effectors[effectorIndices_3d], e0 + E effectors[effectorIndices_2d]; err(c+delta) = x3ds - effectors[effectorIndices_3d] - J[effectorIndices_3d] delta, e0 + E effectors[effectorIndices_2d] + E J[effectorIndices_2d] delta = 0 # J dc = B; (J[effectorIndices_3d] ; E J[effectorIndices_2d]) dc = B ; e0 # DLS method : solve (JTJ + k^2 I) delta = JTB ISCV.JTJ_single_ray( JTJ, JTB, JT, residual, effectorIndices_3d, E, effectorIndices_2d, residual2) #np.dot(JT, B, out=JTB); np.dot(JT, JT.T, out=JTJ) JTJ[JTJdiag] += 1 JTJ[JTJdiag] *= 1.1 # delta[:] = np.linalg.solve(JTJ, JTB) _, delta[:], _ = LAPACK.dposv(JTJ, JTB) # Use Positive Definite Solver chanValues[usedChannels] += delta # TODO: add channel limits # # J_transpose method, 3d only: scaling problems with translation #JT = derrors[:,effectorIndices_3d,:].reshape(numUsedChannels,-1) #np.dot(JT, B, out=delta) #np.dot(JT.T,delta,out=JJTB) #delta *= np.dot(B,JJTB)/(np.dot(JJTB,JJTB)+1) #delta[:3] *= 100000. #testScale = ISCV.Jtranspose_SR(delta, JJTB, JT, residual,effectorIndices_3d,residual2,effectorIndices_2d) Character.pose_skeleton(Gs, skelDict, chanValues, rootMat)
def solveIK(skelDict, chanValues, effectorData, effectorTargets, outerIts=10, rootMat=None): """ Given an initial skeleton pose (chanValues), effectors (ie constraints: joint, offset, weight, target), solve for the skeleton pose. Effector weights and targets are 3x4 matrices. * Setting 1 in the weight's 4th column makes a position constraint. * Setting 100 in the weight's first 3 columns makes an orientation constraint. Args: skelDict (GskelDict): The Skeleton to process chanValues (float[]): Initial pose of the skeleton as Translation and many rotations applied to joints in the skelDict. effectorData (big o'l structure!): effectorJoints, effectorOffsets, effectorWeights, usedChannels, usedChannelWeights, usedCAEs, usedCAEsSplits effectorTargets (?): What's this? outerIts (int): IK Iterations to solve the skeleton. Default = 10 rootMat (float[3][4]): reference frame of the Skeleton. Default = None Returns: None: The result is an update of the skelDict to the solution - chanValues, channelMats, and Gs. Requires: Character.pose_skeleton_with_chan_mats ISCV.pose_effectors ISCV.derror_dchannel ISCV.JTJ """ effectorJoints, effectorOffsets, effectorWeights, usedChannels, usedChannelWeights, usedCAEs, usedCAEsSplits = effectorData jointParents = skelDict['jointParents'] Gs = skelDict['Gs'] Ls = skelDict['Ls'] jointChans = skelDict['jointChans'] jointChanSplits = skelDict['jointChanSplits'] numChannels = jointChanSplits[-1] numEffectors = len(effectorJoints) numUsedChannels = len(usedChannels) channelMats = np.zeros((numChannels, 3, 4), dtype=np.float32) #usedEffectors = np.array(np.where(np.sum(effectorWeights,axis=(1,2)) != 0)[0], dtype=np.int32) usedEffectors = np.array(np.where(effectorWeights.reshape(-1) != 0)[0], dtype=np.int32) # numUsedEffectors= len(usedEffectors) effectors = np.zeros((numEffectors, 3, 4), dtype=np.float32) residual = np.zeros((numEffectors, 3, 4), dtype=np.float32) derrors = np.zeros((numUsedChannels, numEffectors, 3, 4), dtype=np.float32) # steps = np.ones((numUsedChannels),dtype=np.float32)*0.2 # steps[np.where(jointChans[usedChannels] < 3)[0]] = 30. # steps = 1.0/steps delta = np.zeros((numUsedChannels), dtype=np.float32) # JJTB = np.zeros((numEffectors*12),dtype=np.float32) JTJ = np.zeros((numUsedChannels, numUsedChannels), dtype=np.float32) JTB = np.zeros((numUsedChannels), dtype=np.float32) JT = derrors.reshape(numUsedChannels, -1) JTJdiag = np.diag_indices_from(JTJ) B = residual.reshape(-1) # TODO, calculate the exact requirements on the tolerance B_len = len(B) tolerance = 0.00001 it_eps = (B_len**0.5) * tolerance for it in xrange(outerIts): # TODO, only usedChannels are changing, only update the matrices that have changed after the first iteration. # TODO Look into damping, possibly clip residuals? # updates the channelMats and Gs Character.pose_skeleton_with_chan_mats(channelMats, Gs, skelDict, chanValues, rootMat) bestScore = ISCV.pose_effectors(effectors, residual, Gs, effectorJoints, effectorOffsets, effectorWeights, effectorTargets) if np.linalg.norm(B) < it_eps: break # early termination ISCV.derror_dchannel(derrors, channelMats, usedChannels, usedChannelWeights, usedCAEs, usedCAEsSplits, jointChans, effectors, effectorWeights) # if True: # DLS method : solve (JTJ + k^2 I) delta = JTB ISCV.JTJ( JTJ, JTB, JT, B, usedEffectors) #np.dot(JT, B, out=JTB); np.dot(JT, JT.T, out=JTJ) JTJ[JTJdiag] += 1 JTJ[JTJdiag] *= 1.1 _, delta[:], _ = LAPACK.dposv(JTJ, JTB) # Use Positive Definite Solver # Use General Solver # delta[:] = np.linalg.solve(JTJ, JTB) # elif it==0: # SVD method: solve J delta = B # delta[:] = np.linalg.lstsq(JT.T[usedEffectors], B[usedEffectors], rcond=0.0001)[0].reshape(-1) # else: # J transpose method # testScale = ISCV.J_transpose(delta, JJTB, JT, B) # #np.dot(JT, B, out=delta); np.dot(JT.T,delta,out=JJTB); delta *= np.dot(B,JJTB)/(np.dot(JJTB,JJTB)+1.0) #scale = np.max(np.abs(delta*steps)) #if scale > 1.0: delta *= 1.0/scale #np.clip(delta,-steps,steps,out=delta) chanValues[usedChannels] += delta # TODO: add channel limits #bestScore = ISCV.lineSearch(chanValues, usedChannels, delta, Gs, Ls, jointParents, jointChans, jointChanSplits, # rootMat, effectorJoints, effectorOffsets, effectorWeights, effectorTargets, innerIts, bestScore) #print np.mean(B*B) Character.pose_skeleton(Gs, skelDict, chanValues, rootMat)
def animateHead(newFrame): global ted_geom, ted_geom2, ted_shape, tony_geom, tony_shape, tony_geom2, tony_obj, ted_obj, diff_geom, c3d_frames, extract global tony_shape_vector, tony_shape_mat, ted_lo_rest, ted_lo_mat, c3d_points global md, movies tony_geom.image, tony_geom.bindImage, tony_geom.bindId = ted_geom.image, ted_geom.bindImage, ted_geom.bindId # reuse the texture! fo = 55 MovieReader.readFrame(md, seekFrame=((newFrame + fo) / 2)) view = QApp.view() for ci in range(0, 4): view.cameras[ci + 1].invalidateImageData() ci = view.cameras.index(view.camera) - 1 if ci >= 0: MovieReader.readFrame(movies[ci], seekFrame=(newFrame + fo)) # only update the visible camera frac = (newFrame % 200) / 100. if (frac > 1.0): frac = 2.0 - frac fi = newFrame % len(c3d_frames) if ted_skel: # move the skeleton dofs = ted_anim['dofData'][fi * 2 - 120] Character.pose_skeleton(ted_skel['Gs'], ted_skel, dofs) ted_glskel.setPose(ted_skel['Gs']) offset = ted_skel['Gs'][13] # ted_skel['jointNames'].index('VSS_Head') cams = QApp.app.getLayers()['cameras'] tmp = np.eye(4, 4, dtype=np.float32) tmp[:3, :] = offset cams.setTransform(tmp) if ci >= 0: # move the camera view to be correct camRT = mats[ci][1] RT = np.dot(camRT, np.linalg.inv(tmp)) view.cameras[ci + 1].setRT(RT) # update the face geometries to fit the skeleton ted_geom.setPose(offset.reshape(1, 3, 4)) tony_geom.setPose(offset.reshape(1, 3, 4)) #TODO head_points,c3d_points,surface_points,ted_geom2 frame = c3d_frames[fi][extract] which = np.where(frame[:, 3] == 0)[0] x3ds = frame[which, :3] #print which,x3ds.shape,ted_lo_rest.shape,ted_lo_mat.shape bnds = np.array([[0, 1]] * ted_lo_mat.shape[0], dtype=np.float32) tony_shape_vector[:] = OBJReader.fitLoResShapeMat(ted_lo_rest, ted_lo_mat, x3ds, Aoffset=10.0, Boffset=3.0, x_0=tony_shape_vector, indices=which, bounds=bnds) #global tony_shape_vectors; tony_shape_vector[:] = tony_shape_vectors[newFrame%len(tony_shape_vectors)] #tony_shape_vector *= 0. #tony_shape_vector += (np.random.random(len(tony_shape_vector)) - 0.5)*0.2 if 1: ted_shape_v = np.dot(ted_shape_mat_T, tony_shape_vector).reshape(-1, 3) else: ted_shape_v = np.zeros_like(ted_obj['v']) ISCV.dot(ted_shape_mat_T, tony_shape_vector, ted_shape_v.reshape(-1)) tony_shape_v = ted_shape_v #tony_shape_v = tony_shape['v']*frac ted_geom.setVs(ted_obj['v'] + ted_shape_v) #ted_shape['v'] * frac) tony_geom.setVs(tony_obj['v'] + tony_shape_v - np.array([200, 0, 0], dtype=np.float32)) ted_geom2.setVs(ted_obj['v'] * (1.0 - frac) + tony_tedtopo_obj['v'] * frac + np.array([200, 0, 0], dtype=np.float32)) #if len(ted_shape_v) == len(tony_shape_v): # tony_geom2.setVs(tony_obj['v'] + ted_shape_v - [400,0,0]) # diff_geom.setVs(ted_obj['v'] + tony_shape_v - ted_shape_v - [600,0,0]) #print [c3d_labels[i] for i in which] surface_points.vertices = np.dot(ted_lo_mat.T, tony_shape_vector).T + ted_lo_rest surface_points.colour = [0, 1, 0, 1] # green c3d_points.vertices = x3ds c3d_points.colour = [1, 0, 0, 1] # red QApp.app.refreshImageData() QApp.app.updateGL()