def testEulers2(self): for i in range(100): y, p, r = randnums(3, -math.pi * 2, math.pi * 2) r1 = rotator(y, p, r) self.assertEqual( r1, rotator(0, 0, r) * rotator(y, 0, 0) * rotator(0, p, 0))
def testFromTo2(self): for i in range(100): x, y, z = randnums(3, -5, 5) v = vec3(x, y, z) r = rotator(v, -v) self.assertEqual(r, rotator( vec3(1, 0, 0).cross(v), 0)) # TODO: wanted to actually test 180 degree rotators
def testMatrix1(self): for i in range(100): a = randangle() m = [ math.cos(a), 0, math.sin(a), 0, 1, 0, -math.sin(a), 0, math.cos(a) ] r = rotator(*m) self.assertEqual(r, rotator(vec3(0, 1, 0), a))
def testMatrix3(self): for i in range(100): y, p, r = randnums(3, -math.pi * 2, math.pi * 2) r1 = rotator(y, p, r) y, p, r = randnums(3, -math.pi * 2, math.pi * 2) r2 = rotator(y, p, r) m1 = r1.toMatrix() m2 = r2.toMatrix() m = np.asarray(m1).dot(np.asarray(m2)).flat eqas_(listSum((r1 * r2).toMatrix()), m)
def testXis1(self): '''Test the xi values of points on the plane of a SharedImage, calls SharedImage.getPlaneXi().''' si = SharedImage('', vec3(), rotator(), (10, 10)) self.assertEqual(vec3(), si.getPlaneXi(vec3())) self.assertEqual(vec3(1, 1), si.getPlaneXi(vec3(10, -10))) self.assertEqual(vec3(0.5, 0.5), si.getPlaneXi(vec3(5, -5))) self.assertEqual(vec3(0.5, 0.5), si.getPlaneXi(vec3(5, -5)))
def _loadAnalyzeFile(filename, name, imgObj, task): with f: filename = Future.get(filename) name = name or self.mgr.getUniqueObjName( splitPathExt(filename)[1]) img = imgObj or nibabel.load(filename) dat = dat = np.asanyarray(img.dataobj) hdr = dict(img.get_header()) hdr['filename'] = filename pixdim = hdr['pixdim'] interval = float(pixdim[4]) if interval == 0.0 and len( img.shape) == 4 and img.shape[-1] > 1: interval = 1.0 spacing = vec3(pixdim[1], pixdim[2], pixdim[3]) dat = eidolon.transposeRowsColsNP( dat) # transpose from row-column to column-row obj = self.createObjectFromArray(name, dat, interval, 0, vec3(), rotator(), spacing, task=task) obj.source = hdr f.setObject(obj)
def testIdent1(self): '''Test the default rotator which should represent the identity transformation.''' for i in range(100): x, y, z = randnums(3, -5, 5) v = vec3(x, y, z) r = rotator() self.assertEqual(r * v, v)
def testInv1(self): for i in range(100): x, y, z = randnums(3, -5, 5) v = vec3(x, y, z) y, p, r = randnums(3, -math.pi * 2, math.pi * 2) r1 = rotator(y, p, r) self.assertEqual(r1 * (r1 / v), v)
def testMatrix2(self): for i in range(100): a = randangle() r = rotator(vec3(1, 0, 0), a) eqas_(listSum(r.toMatrix()), (1, 0, 0, 0, 0, math.cos(a), -math.sin(a), 0, 0, math.sin(a), math.cos(a), 0, 0, 0, 0, 1))
def testXis2(self): '''Test the xi values of corners of a SharedImage, calls getPlaneXi().''' si = SharedImage('', vec3(1, -2, 3), rotator(0.1, -0.2, 0.3), (10, 10), (0.678, 0.789)) corners = si.getCorners() self.assertEqual(vec3(), si.getPlaneXi(corners[0])) self.assertEqual(vec3(1, 1), si.getPlaneXi(corners[-1])) self.assertEqual(vec3(0.5, 0.5), si.getPlaneXi(si.center))
def testXis3(self): '''Test the xi values of points on the plane of a SharedImage, calls SharedImage.getPlaneXi().''' pos = vec3(5, -6, 7) si = SharedImage('', pos, rotator(), (10, 10)) self.assertEqual(vec3(), si.getPlaneXi(pos)) self.assertEqual(vec3(1, 1), si.getPlaneXi(pos + vec3(10, -10))) self.assertEqual(vec3(0.5, 0.5, 5), si.getPlaneXi(pos + vec3(5, -5, 5)))
def testXis4(self): '''Test the xi values of points on the plane of a SharedImage, calls SharedImage.getPlaneXi().''' pos = vec3(5, -6, 7) dim = (0.678, 0.789) si = SharedImage('', pos, rotator(), (10, 10), dim) self.assertEqual(vec3(), si.getPlaneXi(pos)) self.assertEqual(vec3(1, 1), si.getPlaneXi(pos + vec3(10 * dim[0], -10 * dim[1]))) self.assertEqual(vec3(0.5, 0.5, 5), si.getPlaneXi(pos + vec3(5 * dim[0], -5 * dim[1], 5)))
def setUp(self): self.tempdir = tempfile.mkdtemp() self.plugin = eidolon.getSceneMgr().getPlugin('Nifti') self.vfunc = lambda x, y, z, t: (x + 1) * 1000 + (y + 1) * 100 + ( z + 1) * 10 + t + 1 self.volarr = np.fromfunction(self.vfunc, (21, 33, 46, 17)) self.vpos = vec3(-10, 20, -15) self.vrot = rotator(0.1, -0.2, 0.13) self.vol = self.plugin.createObjectFromArray('TestVolume', self.volarr, pos=self.vpos, rot=self.vrot)
def importImages(x4, plugin): ''' Import images from the X4DF object `x4', returning a list of ImageSceneObject instances. The `plugin' must be an ImageScenePlugin instance used to create the objects. ''' arrs = {a.name: a for a in x4.arrays} results = [] for im in x4.images: name, timescheme, trans, imagedata, _ = im images = [] filenames = [] tstart, tstep = timescheme or (0, 0) trans = trans or idTransform for i, imgdat in enumerate(imagedata): src, timestep, imgtrans, _ = imgdat arr = arrs[src].data imgtrans = imgtrans or trans filenames.append(arrs[src].filename) if timestep is None: offset, interval = tstart, tstep else: offset, interval = tstart + i * tstep, 0 arr = reverseAxes( arr ) # array is stored in inverse index order from what is expected pos = vec3(*imgtrans.position) rot = rotator(*imgtrans.rmatrix.flatten()) spacing = vec3(*imgtrans.scale) * vec3( arr.shape[1], arr.shape[0], arr.shape[2] if len(arr.shape) > 2 else 0).inv() obj = plugin.createObjectFromArray('tmp', arr, interval, offset, pos, rot, spacing) images += obj.images results.append( eidolon.ImageSceneObject(name, None, images, filenames=list(filter(bool, filenames)))) return results
def loadImageStackObject(self,name,filenames,pos=vec3(),rot=rotator(),spacing=(1.0,1.0),imgsep=1.0,sortIndex=None,regex=None,reverse=False,task=None): ''' Loads a stack of images (or a sequence of stacks), ordered bottom up, into a ImageSceneObject. If `sortIndex' is not None, this is the sorting index in the file names used to sort the stack. The start position `pos' is intepreted as the top left position of the bottom-most image. If `filenames' is a list of filenames only, the series is not timed, however if it's a list of lists of filenames then each sublist is (optionally) sorted and then loaded into a time series object. ''' isTimed=eidolon.isIterable(filenames[0]) and not isinstance(filenames[0],str) if isTimed: if sortIndex!=None: filenames=[sortFilenameList(fn,sortIndex,regex) for fn in filenames] if reverse: for f in filenames: f.reverse() positions=[pos+(rot*vec3(0,0,imgsep*i)) for i in range(len(filenames[0]))] imagesteps=[loadImageStack(fn,self.mgr.scene.loadImageFile,positions,rot,spacing,task) for fn in filenames] for i,imgs in enumerate(imagesteps): for img in imgs: img.timestep=i images=listSum(imagesteps) filenames=listSum(filenames) else: if sortIndex!=None: filenames=sortFilenameList(filenames,sortIndex,regex) if reverse: filenames.reverse() positions=[pos+(rot*vec3(0,0,imgsep*i)) for i in range(len(filenames))] images=loadImageStack(filenames,self.mgr.scene.loadImageFile,positions,rot,spacing,task) return self.createSceneObject(name,images,filenames,isTimed)
def getTransformFromInfo(offcenter, angulation, sliceorient, spacing, dimensions): ''' Returns a (vec3,rotator) pair for the position and orientation of an image given the ParRec parameters for offcenter position, angulation in degrees, slice orientation value from SliceOrientations, pixel spacing, and image dimensions. ''' cy, cz, cx = offcenter theta, phi, rho = map(math.radians, angulation) refmat = np.array([[-1, 0, 0], [0, 0, 1], [0, -1, 0]]) AFRtoLPS = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) torient = np.eye(3) # get the slice orientation transform matrix if sliceorient == SliceOrientations.Transverse: torient = np.array([[0, 1, 0], [-1, 0, 0], [0, 0, -1]]) elif sliceorient == SliceOrientations.Sagittal: torient = np.array([[-1, 0, 0], [0, 0, -1], [0, 1, 0]]) elif sliceorient == SliceOrientations.Coronal: torient = np.array([[0, 0, -1], [1, 0, 0], [0, 1, 0]]) # convert angulation values to rotation matrices tap = np.array([[1, 0, 0], [0, math.cos(theta), -math.sin(theta)], [0, math.sin(theta), math.cos(theta)]]) tfh = np.array([[math.cos(phi), 0, math.sin(phi)], [0, 1, 0], [-math.sin(phi), 0, math.cos(phi)]]) trl = np.array([[math.cos(rho), -math.sin(rho), 0], [math.sin(rho), math.cos(rho), 0], [0, 0, 1]]) # compose transformations and convert to a rotator object dirmat = AFRtoLPS.dot(trl).dot(tap).dot(tfh).dot(refmat).dot(torient) rot = rotator(*dirmat.flat) # Since rotation is defined at the center of the image, need to add a rotated mid vector to the # position which is instead defined at the top left corner. midoffset = ((spacing * vec3(1, -1, 1)) * (dimensions - vec3(1))) * 0.5 - spacing * vec3(0.5, -0.5, 0) pos = vec3(cx, cy, cz) - (rot * midoffset) return pos, rot
def testEulers5(self): for i in range(100): a = randangle() self.assertEqual(rotator(0, 0, a), rotator(vec3(0, 1, 0), a))
def testIdent2(self): '''Test a rotator specified as the angle around the zero vector is an identity transformation.''' r = rotator(vec3(), randangle()) self.assertEqual(r, rotator())
def _saveFile(path, obj, kwargs, task): with f: assert isinstance(obj, ImageSceneObject) if os.path.isdir(path): path = os.path.join(path, obj.getName()) if not overwrite and os.path.exists(path): raise IOError('File already exists: %r' % path) if not eidolon.hasExtension(path, 'nii', 'nii.gz'): path += '.nii' if 'datatype' in kwargs: datatype = kwargs.pop('datatype') elif isinstance(obj.source, dict) and 'datatype' in obj.source: datatype = data_type_codes.dtype[int( obj.source['datatype'])] else: datatype = np.float32 mat = self.getImageObjectArray(obj, datatype) dat = mat['array'] pos = mat['pos'] spacex, spacey, spacez = mat['spacing'] rot = rotator(vec3(0, 0, 1), math.pi) * mat['rot'] * rotator( vec3(0, 0, 1), -halfpi) toffset = mat['toffset'] interval = mat['interval'] affine = np.array(rot.toMatrix()) affine[:, 3] = -pos.x(), -pos.y(), pos.z(), 1.0 dat = eidolon.transposeRowsColsNP( dat) # transpose from row-column to column-row imgobj = nibabel.nifti1.Nifti1Image(dat, affine) # header info: http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h hdr = { 'pixdim': np.array([ 1.0, spacex, spacey, spacez if spacez != 0.0 else 1.0, interval, 1.0, 1.0, 1.0 ], np.float32), 'toffset': toffset, 'slice_duration': interval, 'xyzt_units': unit_codes['mm'] | unit_codes['msec'], 'qform_code': xform_codes['aligned'], 'sform_code': xform_codes['scanner'], 'datatype': data_type_codes.code[datatype] } hdr.update(kwargs) for k, v in hdr.items(): if k in imgobj.header: imgobj.header[k] = v nibabel.save(imgobj, path) if setFilenames: obj.plugin.removeObject(obj) obj.plugin = self obj.source = dict(nibabel.load(path).get_header()) obj.source['filename'] = path elif isinstance(obj.source, dict) and 'filename' in obj.source: obj.source['filename'] = path f.setObject(imgobj)
def testPlaneXiFunc(self): '''Tests the getPlaneXi function directly.''' v = vec3(*randnums(3, -5, 5)) r = rotator(*randnums(4, -1, 1)) self.assertEqual(vec3(0), getPlaneXi(v, v, r, vec3(1)))
def _loadNiftiFile(filename, name, imgObj, task): with f: filename = Future.get(filename) name = name or self.mgr.getUniqueObjName( splitPathExt(filename)[1]) img = imgObj or nibabel.load(filename) hdr = dict(img.header) hdr['filename'] = filename pixdim = hdr['pixdim'] xyzt_units = hdr['xyzt_units'] x = float(hdr['qoffset_x']) y = float(hdr['qoffset_y']) z = float(hdr['qoffset_z']) b = float(hdr['quatern_b']) c = float(hdr['quatern_c']) d = float(hdr['quatern_d']) toffset = float(hdr['toffset']) interval = float(pixdim[4]) if interval == 0.0 and len( img.shape) == 4 and img.shape[-1] > 1: interval = 1.0 qfac = float(pixdim[0]) or 1.0 spacing = vec3(pixdim[1], pixdim[2], qfac * pixdim[3]) if int(hdr['qform_code']) > 0: position = vec3(-x, -y, z) rot = rotator( -c, b, math.sqrt(max(0, 1.0 - (b * b + c * c + d * d))), -d) * rotator(vec3.Z(), halfpi) else: affine = img.get_affine() position = vec3(-affine[0, 3], -affine[1, 3], affine[2, 3]) rmat = np.asarray([ affine[0, :3] / -spacing.x(), affine[1, :3] / -spacing.y(), affine[2, :3] / spacing.z() ]) rot = rotator(*rmat.flatten().tolist()) * rotator( vec3.Z(), halfpi) xyzunit = xyzt_units & 0x07 # isolate space units with a bitmask of 7 tunit = xyzt_units & 0x38 # isolate time units with a bitmask of 56 if tunit == 0: # if no tunit provided, try to guess if interval < 1.0: tunit = unit_codes['sec'] elif interval > 1000.0: tunit = unit_codes['usec'] # convert to millimeters if xyzunit == unit_codes['meter']: position *= 1000.0 spacing *= 1000.0 elif xyzunit == unit_codes['micron']: position /= 1000.0 spacing /= 1000.0 # convert to milliseconds if tunit == unit_codes['sec']: toffset *= 1000.0 interval *= 1000.0 elif tunit == unit_codes['usec']: toffset /= 1000.0 interval /= 1000.0 dobj = img.dataobj datshape = tuple( d or 1 for d in dobj.shape ) # dimensions are sometimes given as 0 for some reason? # reading file data directly is expected to be faster than using nibabel, specifically by using memmap if filename.endswith('.gz'): dat = img.get_data() #dat=np.asanyarray(dobj) # same as the above # with gzip.open(filename) as o: # TODO: not sure if this is any faster than the above # o.seek(dobj.offset) # seek beyond the header # dat=np.frombuffer(o.read(),dobj.dtype).reshape(datshape,order=dobj.order) else: # mmap the image data below the header in the file dat = np.memmap(dobj.file_like, dobj.dtype, 'r', dobj.offset, datshape, dobj.order) dat = eidolon.transposeRowsColsNP( dat) # transpose from row-column to column-row obj = self.createObjectFromArray(name, dat, interval, toffset, position, rot, spacing, task=task) obj.source = hdr # apply slope since this isn't done automatically when using memmap/gzip if not filename.endswith('.gz'): eidolon.applySlopeIntercept(obj, *img.header.get_slope_inter()) f.setObject(obj)
def testRot1(self): '''Tests the creation of a SharedImage with the default vec3 and rotator value, calls SharedImage.getPlaneXi().''' si = SharedImage('', vec3(), rotator(), (1, 1)) self.assertEqual(vec3(1), si.getPlaneXi(vec3(1, -1, 1)))
def testPlaneDef3(self): r = rotator(vec3(1, 0, 0), vec3(0, 0, 1), vec3(1, 0, 0), vec3(0, 1, 0)) eqas_(r.getEulers(), (0, halfpi, 0))
def testAxis2(self): r = rotator(vec3(0, 0, -1), halfpi) eqas_(r.getEulers(), (-halfpi, 0, 0))
def testFromTo1(self): r = rotator(vec3(1, 0, 0), vec3(0, 1, 0)) self.assertEqual(r, rotator(vec3(0, 0, 1), halfpi))
def testEulers1(self): for i in range(100): y, p, r = randnums(3, -math.pi * 2, math.pi * 2) r1 = rotator(y, p, r) self.assertEqual(r1, rotator(*r1.getEulers()))
# it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Eidolon is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program (LICENSE.txt). If not, see <http://www.gnu.org/licenses/> from eidolon import ReprType,vec3, rotator,generateArrow,halfpi, MeshSceneObject,TriDataSet,AxesType pos=vec3(-10,20,-15) rot=rotator(0.1,-0.2,0.13) w,h,d=31,42,53 nodesz,indsz=generateArrow(5) nodesz=[(n+vec3.Z())*vec3(w,d,h)*vec3(0.1,0.1,0.5) for n in nodesz] nodesx=[rotator(vec3(0,1,0),halfpi)*n for n in nodesz] nodesy=[rotator(vec3(1,0,0),-halfpi)*n for n in nodesz] nodes=[(rot*n)+pos for n in (nodesx+nodesy+nodesz)] nlen=len(nodesz) indices=indsz+[(i+nlen,j+nlen,k+nlen) for i,j,k in indsz]+[(i+nlen*2,j+nlen*2,k+nlen*2) for i,j,k in indsz] field=[2.0]*nlen+[1.0]*nlen+[0.0]*nlen axes=MeshSceneObject('Axes',TriDataSet('tris',nodes,indices,[('col',field)])) mgr.addSceneObject(axes)
def testFullCircle1(self): r = rotator(vec3(1, 0, 0), math.pi * 2) self.assertEqual(r, rotator())