Attachment 'MultisMeshMorphing.py'

Download

   1 import numpy as np
   2 import sys
   3 from tvtk.api import tvtk
   4 from tvtk.api import colors
   5 from lxml import etree as et
   6 import os
   7 import subprocess
   8 import warnings
   9 warnings.simplefilter(action='ignore', category=FutureWarning)
  10 
  11 sys.path.append('/home/landisb/PycharmProjects/PythonScripts/Project')
  12 import myVTK
  13 import RunPygem
  14 
  15 
  16 def CreateNestedDictionary():
  17 	"""Create a nested dictionary structure for the ultrasound data
  18 		this iterates "nicely" for these purposes as
  19 		for place, slice in nestedDict.items():
  20 			for orient, strata in slice.items():
  21 				for layer, point in strata.items():"""
  22 	from copy import deepcopy
  23 	import numpy as np
  24 	orient = {	'bone'  : np.zeros(3),
  25 				# 'muscle': np.zeros(3),
  26 				# 'fat'   : np.zeros(3),
  27 				'skin'	: np.zeros(3)	}
  28 	place = {	'Anterior' : deepcopy(orient),
  29 				'Posterior': deepcopy(orient),
  30 				'Medial'   : deepcopy(orient),
  31 				'Lateral'  : deepcopy(orient)	}
  32 	nestedDict={'Proximal' : deepcopy(place),
  33 				'Central'  : deepcopy(place),
  34 				'Distal'   : deepcopy(place)	}
  35 	return nestedDict
  36 
  37 
  38 def GetSubjectData(filename):
  39 	"""Reads in-vivo subject file MULTIS00X-1.xml
  40 		returns subjectData in a dictionary"""
  41 	subjectData = {}
  42 	tree = et.ElementTree(file=filename)
  43 	root = tree.getroot()
  44 	subject = root.find('Subject_Data')
  45 	measurements = subject.find('Anatomical_Measurements')
  46 	limbs = []
  47 	limbs.append(measurements.find('Lower_Leg') )
  48 	limbs.append(measurements.find('Lower_Arm'))
  49 	limbs.append(measurements.find('Upper_Leg'))
  50 	limbs.append(measurements.find('Upper_Arm'))
  51 	#
  52 	for limb in limbs:
  53 		subjectData[limb.tag]={}
  54 		for cluster in limb:
  55 			children = list(cluster)
  56 			descriptor, value = children[0].text, children[1].text
  57 			descriptor = descriptor.replace(' ','')
  58 			annoyingDescriptions =('Hip', 'Knee', 'Elbow', 'Shoulder')
  59 			for annoy in annoyingDescriptions:
  60 				descriptor=descriptor.replace(annoy,'')
  61 			subjectData[limb.tag][descriptor] = float(value)
  62 	return subjectData
  63 def GetCadaverData(filename):
  64 	"""Reads in-vitro cadaver subject file CMULTIS00X-1.xml
  65 		this file is structured slightly differently than in-vivo
  66 		returns subjectData in a dictionary"""
  67 	subjectData = {}
  68 	tree = et.ElementTree(file=filename)
  69 	root = tree.getroot()
  70 	subject = root.find('Subject_Data')
  71 	limbs = ['Arm', 'Leg']
  72 	segments = ['Upper', 'Lower']
  73 	measurements = []
  74 	for limb in limbs:
  75 		temp = subject.find('{}_-_Anatomical_Measurements'.format(limb))
  76 		for segment in segments:
  77 			measurements.append(temp.find('{}_{}'.format(segment,limb)))
  78 	for measure in measurements:
  79 		subjectData[measure.tag]={}
  80 		for cluster in measure:
  81 			children = list(cluster)
  82 			descriptor, value = children[0].text, children[1].text
  83 			descriptor = descriptor.replace(' ','')
  84 			annoyingDescriptions =('Hip', 'Knee', 'Elbow', 'Shoulder')
  85 			for annoy in annoyingDescriptions:
  86 				descriptor=descriptor.replace(annoy,'')
  87 			subjectData[measure.tag][descriptor] = float(value)
  88 	return subjectData
  89 
  90 def ReadUltrasoundThickness(limbSegment, xmlFile):
  91 	"""Read the xml to find thicknesses"""
  92 	import numpy as np
  93 	import os
  94 	import re
  95 	from lxml import etree as et
  96 	directory = os.path.dirname(xmlFile)
  97 	doc = et.parse(xmlFile)
  98 	root = doc.getroot()
  99 	bodyPart = root.find(limbSegment.replace('_', ''))
 100 	AllThicknesses = CreateNestedDictionary()
 101 	for loc in list(bodyPart):
 102 		place, orient = re.findall('[A-Z][a-z]*', loc.tag)  # Split on capitalization
 103 		thicknessFile = loc.get('Anatomical')
 104 		subDoc = et.parse(os.path.join(directory, thicknessFile))
 105 		subRoot = subDoc.getroot()
 106 		subject = subRoot.find("Subject").find("Source")
 107 		Thickness = {}
 108 		for frame in list(subject):
 109 			readings = frame.find('Thickness')
 110 			for value in list(readings):
 111 				try:				Thickness[value.tag.lower()].append(float(value.text))
 112 				except KeyError:	Thickness[value.tag.lower()] = [float(value.text)]
 113 		for t, v in Thickness.iteritems():
 114 			if np.all(np.isnan(v)):		# no data, use CT thickness evaluation instead
 115 				# except I don't have this for in-vivo data, only my test case...
 116 				pass
 117 				# v = []			# empty the list as it is only NaNs
 118 				# # note the follwing code is similar but NOT the same as the subDoc section above.
 119 				# subDoc = et.parse(os.path.join(directory, '../CTManual_zProbe',thicknessFile))	# adjust path to CT thickness directory
 120 				# subRoot = subDoc.getroot()
 121 				# subject = subRoot.find("Subject").find("Source")
 122 				# for frame in list(subject):
 123 				# 	value = frame.find('Thickness').find(t.capitalize())
 124 				# 	v.append(float(value.text))
 125 			elif  np.any(np.isnan(v)):		v = v[~np.isnan(v)]		# just use the good values
 126 			Thickness[t] = sum(v) / len(v)
 127 		AllThicknesses[place][orient] = Thickness
 128 	return AllThicknesses
 129 def ReadInVitroPositions(limbSegment, xmlFile):
 130 	"""Read the xml to find the in vitro ultrasound positions"""
 131 	import re
 132 	from lxml import etree as et
 133 	basePoints = CreateNestedDictionary()
 134 	baseDirections = CreateNestedDictionary()
 135 	newFile = xmlFile.replace('/','\\')
 136 	print newFile, os.path.isfile(newFile)
 137 	doc = et.parse(xmlFile)
 138 	root = doc.getroot()
 139 	bodyPart = root.find(limbSegment.replace('_',''))
 140 	for loc in list(bodyPart):
 141 		place = loc.tag
 142 		place, orient = re.findall('[A-Z][a-z]*', place)	# Split on capitalization
 143 		pos = loc.find("Anatomical").find("USPosition")
 144 		x = float( pos.find("x").get('value') ) * 1000
 145 		y = float( pos.find("y").get('value') ) * 1000
 146 		z = float( pos.find("z").get('value') ) * 1000
 147 		roll = float( pos.find("roll" ).get('value') )
 148 		pitch= float( pos.find("pitch").get('value') )
 149 		yaw  = float( pos.find("yaw"  ).get('value') )
 150 		basePoints[place][orient]['skin'] = [x, y, z]
 151 		baseDirections[place][orient] = findDirectionFromRPY(roll, pitch, yaw)
 152 	return basePoints, baseDirections
 153 
 154 def findDirectionFromRPY(roll, pitch, yaw, reverse=False):
 155 	from numpy import sin, cos, dot, array
 156 	r = roll
 157 	p = pitch
 158 	y = yaw
 159 	transformMatrix = array(	[[ cos(p)*cos(y), 	sin(r)*sin(p)*cos(y) - cos(r)*sin(y),	cos(r)*sin(p)*cos(y) + sin(r)*sin(y)],
 160 								 [ cos(p)*sin(y),	sin(r)*sin(p)*sin(y) + cos(r)*cos(y),	cos(r)*sin(p)*sin(y) - sin(r)*cos(y)],
 161 								 [-sin(p), 			sin(r)*cos(p), 							cos(r)*cos(p)						]	])
 162 	if reverse:		dirVector = dot(transformMatrix, [0, 0,-1])
 163 	else:			dirVector = dot(transformMatrix, [0, 0, 1])  # ultrasound probe points in +Z dir
 164 	return dirVector
 165 
 166 def SimpleMorphing(renderer, subjectDir, cadaverDir, meshes, limb, activeMorphing=True, zPlane=False):
 167 	"""Morph a model (cadaver) into a shape matching an in-vivo subject"""
 168 	# draw meshes
 169 	myVTK.MakeStl(ren, meshes['bone'], color=colors.blanched_almond, opacity=0.1)
 170 	myVTK.MakeStl(ren, meshes['skin'], color=colors.cyan, opacity=0.1)
 171 	# Create handle for morphed meshes
 172 	morphs = {layer: mesh.replace('.','_morphed_{}.'.format(subjectDir.rsplit(os.sep,1)[1])) for layer,mesh in meshes.items() if layer is not 'bone'}
 173 	#
 174 	# Find files from positions relative to given directories
 175 	subjectFile =			os.path.join(subjectDir, 'Configuration', '{}.xml'.format(subjectDir.rsplit(os.sep, 1)[1]))
 176 	subjectUltrasound =		os.path.join(subjectDir, 'TissueThickness', 'UltrasoundManual', '{}_TA_inclusion.xml'.format(subjectDir.rsplit(os.sep, 1)[1]))
 177 	cadaverFile =			os.path.join(cadaverDir, 'Configuration', '{}.xml'.format(cadaverDir.rsplit(os.sep, 1)[1]))
 178 	cadaverUltrasound =		os.path.join(cadaverDir, 'TissueThickness', 'UltrasoundManual', '{}_TA_inclusion.xml'.format(cadaverDir.rsplit(os.sep, 1)[1]))
 179 	inVitroPositionsFile =	os.path.join(cadaverDir, 'Registration', '{}_{}_US_CT.xml'.format(cadaverDir.rsplit(os.sep, 1)[1], ''.join(L for L in limb if L.isupper())))
 180 	#
 181 	# Read cadaver subject file to get in-vitro experimentally meased values
 182 	cadaverMeasurements = GetCadaverData(cadaverFile)
 183 	cadaverLength  = cadaverMeasurements[limb]['Length'] * 10
 184 	cadaverCircum = {	'Central' : cadaverMeasurements[limb][ 'CentralCircumference']*10,
 185 						'Distal'  : cadaverMeasurements[limb][  'DistalCircumference']*10,
 186 						'Proximal': cadaverMeasurements[limb]['ProximalCircumference']*10	}
 187 	# Read subject file xml to find in-vivo experimentally measured sizes
 188 	subjectMeasurements = GetSubjectData(subjectFile)
 189 	subjectLength = subjectMeasurements[limb]['Length']*10
 190 	subjectCircum = {	'Central' : subjectMeasurements[limb][ 'CentralCircumference']*10,
 191 						'Distal'  : subjectMeasurements[limb][  'DistalCircumference']*10,
 192 						'Proximal': subjectMeasurements[limb]['ProximalCircumference']*10	}
 193 	#
 194 	# distance from central plane, file is distance from hip...so math is done now
 195 	subjectDistances= {	'Hip'     : subjectMeasurements[limb]['landmarktoCentralCircumference']*10,
 196 						'Proximal': subjectMeasurements[limb]['landmarktoCentralCircumference']*10-subjectMeasurements[limb]['landmarktoProximalCircumference']*10,
 197 						'Central' : 0.0,
 198 						'Distal'  : subjectMeasurements[limb]['landmarktoCentralCircumference']*10-subjectMeasurements[limb]['landmarktoDistalCircumference']*10,
 199 						'Knee'	  :	subjectMeasurements[limb]['landmarktoCentralCircumference']*10-subjectLength
 200 						}
 201 	#
 202 	# Read ultrasound thicknesses produces layer thickness
 203 	cadaverThicknesses = ReadUltrasoundThickness(limb, cadaverUltrasound)
 204 	subjectThickness = ReadUltrasoundThickness(limb, subjectUltrasound)
 205 	# sum layers for total thickness
 206 	cadaverThicknesses= {place:{orient:sum(strata.values()) for orient, strata in slice.items() } for place,slice in cadaverThicknesses.iteritems()}
 207 	subjectThickness  = {place:{orient:sum(strata.values()) for orient, strata in slice.items() } for place,slice in   subjectThickness.iteritems()}
 208 	#
 209 	# Read probe positions
 210 	probePoints, probeDirections = ReadInVitroPositions(limb, inVitroPositionsFile)
 211 	probeDirections = {place:{orient:strata for orient, strata in slice.items()} for place, slice in probeDirections.iteritems()}
 212 	probePoints = {place:{orient:[point for layer,point in strata.items()if layer is 'skin'][0] for orient,strata in slice.items()}for place,slice in probePoints.items()}
 213 	#
 214 	if zPlane:	# if true forces probePoints into the same z plane
 215 		for place, slice in probePoints.iteritems():
 216 			for orient, strata in slice.items():
 217 				strata[2] = probePoints[place]['Anterior'][2]
 218 	#
 219 	# Find control point for morph
 220 	controlPoints = CreateNestedDictionary()
 221 	modelThicknesses = CreateNestedDictionary()
 222 	norms = {	'Anterior'	: np.array([ 0,-1, 0]),
 223 				'Lateral'	: np.array([-1, 0, 0]),
 224 				'Medial'	: np.array([ 1, 0, 0]),
 225 				'Posterior'	: np.array([ 0, 1, 0])	}
 226 	for place, slice in probePoints.iteritems():
 227 		# if place is 'Central':
 228 			for orient, strata in slice.items():
 229 				p1 = probePoints[place][orient]
 230 				p2 = p1 + norms[orient] * cadaverThicknesses[place][orient]
 231 				p2 = p1 + probeDirections[place][orient] * cadaverThicknesses[place][orient]
 232 				#
 233 				controlPoints[place][orient]['bone'] = myVTK.FindIntersectionOfStlAndLine(ren, meshes['bone'], p1, p2, colors.red  , visualize=True, embellish=True, extend=True)
 234 				controlPoints[place][orient]['skin'] = myVTK.FindIntersectionOfStlAndLine(ren, meshes['skin'], p1, p2, colors.green, visualize=False, embellish=True, extend=True)
 235 				modelThicknesses[place][orient] = myVTK.dist(controlPoints[place][orient]['bone'], controlPoints[place][orient]['skin'])
 236 	#
 237 	# myVTK.Show(ren, mouseHighlightActor=False)
 238 	#
 239 	morphedPoints = CreateNestedDictionary()
 240 	for place, slice in controlPoints.items():
 241 		for orient, strata in slice.items():
 242 			for layer, point in strata.items():
 243 				if layer is 'bone':		morphedPoints[place][orient][layer] = point
 244 				else:
 245 					if np.isnan(subjectThickness[place][orient]):	morphedPoints[place][orient][layer] = controlPoints[place][orient][layer]
 246 					else:	morphedPoints[place][orient][layer] = controlPoints[place][orient]['bone']-norms[orient]*subjectThickness[place][orient]
 247 					print place, orient, layer, '	',morphedPoints[place][orient][layer]
 248 	#
 249 	controlArray = np.array( [point for place,slice in controlPoints.items() for orient,strata in slice.items() for layer,point in strata.items() if layer is not 'bone'] )
 250 	morphedArray = np.array( [point for place,slice in morphedPoints.items() for orient,strata in slice.items() for layer,point in strata.items() if layer is not 'bone'] )
 251 	#
 252 	# Morph meshes with subprocess to speed things up.  Morphing of seperate layers will run concurrently.
 253 	if activeMorphing:		# This was used for testing purposes, it is left in because the user may want the output again, without recalculating the morph
 254 		print 'Morphing...',
 255 		processes = []
 256 		for layer, mesh in meshes.items():
 257 			if layer is not 'bone':
 258 				RunPygem.WritePygemParameterFile('{}.dat'.format(layer), controlArray,morphedArray)
 259 				processes.append( subprocess.Popen(['python', 'RunPygem.py', mesh, '{}.dat'.format(layer), morphs[layer]]) )
 260 		print 'this takes a while...',
 261 		for proc in processes:	proc.wait()
 262 		print 'finished.'
 263 	#
 264 	myVTK.MakeStl(ren, morphs['skin'], color=colors.orange, opacity=0.1)
 265 	#
 266 	morphThicknesses = CreateNestedDictionary()
 267 	for place, slice in morphedPoints.iteritems():
 268 		if place is 'Central':
 269 			for orient, strata in slice.items():
 270 				p1 = morphedPoints[place][orient]['skin']
 271 				p2 = p1 + norms[orient]*(modelThicknesses[place][orient]+subjectThickness[place][orient])/2
 272 				#
 273 				intersectBone = myVTK.FindIntersectionOfStlAndLine(ren, meshes['bone'], p1, p2, colors.red  , embellish=True, extend=True)	# no longer morphing bone
 274 				intersectSkin = myVTK.FindIntersectionOfStlAndLine(ren, morphs['skin'], p1, p2, colors.blue, embellish=True, extend=True, size=1)
 275 				morphThicknesses[place][orient] = myVTK.dist(intersectBone, intersectSkin)
 276 				diff = morphThicknesses[place][orient]-subjectThickness[place][orient]
 277 				
 278 	# myVTK.Show(ren, mouseHighlightActor=False)
 279 
 280 	#
 281 	planeNormal = (0,0,1)
 282 	planeSize = 250
 283 	inVitroColors = {'Proximal':colors.pink, 'Central':colors.green_pale, 'Distal':colors.cyan, 'Hip':colors.slate_grey, 'Knee':colors.yellow}
 284 	inVivo_Colors = {'Proximal':colors.red,  'Central':colors.green_dark, 'Distal':colors.blue, 'Hip':colors.black,		 'Knee':colors.orange}
 285 	#
 286 	modelCircum = {}
 287 	morphCircum = {}
 288 	for place, slice in controlPoints.items():
 289 		if place is 'Central':
 290 			cP = controlPoints[place]['Anterior']['skin']	# makes following lines somewhat more readable
 291 			mP = morphedPoints[place]['Anterior']['skin']
 292 			# myVTK.DrawPlane(ren, cP, planeNormal, size=planeSize, opacity=0.05, color=inVitroColors[place])  # for visualization only
 293 			plane = tvtk.Plane(origin=cP, normal=planeNormal)  # Create the plane as a function, that will be used to cut the stl.
 294 			modelCircum[place],c = myVTK.MakeStlCut(ren, meshes['skin'], plane, inVitroColors[place], opacity=0)
 295 			#
 296 			# myVTK.DrawPlane(ren, mP, normal, size=planeSize, opacity=0.05, color=inVivo_Colors[place])  # for visualization only
 297 			plane = tvtk.Plane(origin=mP, normal=planeNormal)  # Create the plane as a function, that will be used to cut the stl.
 298 			morphCircum[place],c = myVTK.MakeStlCut(ren, morphs['skin'], plane, inVivo_Colors[place], opacity=0)
 299 			# MBoneCircum[place],c = myVTK.MakeStlCut(ren, morphs['bone'], plane, colors.black, opacity=1)
 300 			# print '{:8s} Bone Morph circum {:6.2f}, Bone circum {:6.2f}'.format(place, MBoneCircum[place], bone_Circum[place])
 301 			print place
 302 			# print '	Model circum {: 6.2f}	thickness {: 6.2f}'.format(modelCircum[place], modelThicknesses[place])
 303 			print '	Morph	circum {: 6.2f}'.format(morphCircum[place]),
 304 			print '	Thickness:',
 305 			print ' Anterior {: 6.2f}'.format(morphThicknesses[place]['Anterior']),
 306 			print ' Posterior {: 6.2f}'.format(morphThicknesses[place]['Posterior']),
 307 			print ' Lateral {: 6.2f}'.format(morphThicknesses[place]['Lateral']),
 308 			print ' Medial {: 6.2f}'.format(morphThicknesses[place]['Medial'])
 309 			#
 310 			print '	Subj	circum {: 6.2f}'.format(subjectCircum[place]),
 311 			print '	Thickness:',
 312 			print ' Anterior {: 6.2f}'.format(subjectThickness[place]['Anterior'] ),
 313 			print ' Posterior {: 6.2f}'.format(subjectThickness[place]['Posterior']),
 314 			print ' Lateral {: 6.2f}'.format(subjectThickness[place]['Lateral']),
 315 			print ' Medial {: 6.2f}'.format(subjectThickness[place]['Medial'])
 316 			#
 317 			print '	Cadaver	circum {: 6.2f}'.format(cadaverCircum[place]),
 318 			print '	Thickness:',
 319 			print ' Anterior {: 6.2f}'.format(cadaverThicknesses[place]['Anterior'] ),
 320 			print ' Posterior {: 6.2f}'.format(cadaverThicknesses[place]['Posterior']),
 321 			print ' Lateral {: 6.2f}'.format(cadaverThicknesses[place]['Lateral']),
 322 			print ' Medial {: 6.2f}'.format(cadaverThicknesses[place]['Medial'])
 323 			#
 324 			print '	Model	circum {: 6.2f}'.format(modelCircum[place]),
 325 			print '	Thickness:',
 326 			print ' Anterior {: 6.2f}'.format(modelThicknesses[place]['Anterior'] ),
 327 			print ' Posterior {: 6.2f}'.format(modelThicknesses[place]['Posterior']),
 328 			print ' Lateral {: 6.2f}'.format(modelThicknesses[place]['Lateral']),
 329 			print ' Medial {: 6.2f}'.format(modelThicknesses[place]['Medial'])
 330 
 331 if __name__ == '__main__':
 332 	#
 333 	gender = 'female'
 334 	limb = 'Lower_Leg'	# choose from Upper_Arm, Upper_Leg, Lower_Arm, Lower_Leg
 335 	#
 336 	# This is a coded reminder of the model and invivo parings
 337 	if gender is 'female':
 338 		subjectDir = 'C:\Users\landisb\Workspace\InVivoSubjects\MULTIS037-1'
 339 		cadaverDir = 'C:\Users\landisb\Workspace\CadaverModels\CMULTIS008-1'
 340 	elif gender is 'male':
 341 		subjectDir = 'C:\Users\landisb\Workspace\InVivoSubjects\MULTIS094-1'
 342 		cadaverDir = 'C:\Users\landisb\Workspace\CadaverModels\CMULTIS006-1'
 343 	#
 344 	# 	Adjust meshes to match cadaver and limb segment choice
 345 	meshes = {
 346 		'bone': r'C:\Users\landisb\Downloads\Operation Multis-STL_Files\STL_Files\Lumped\008LL\meshed_bone_quad.stl',
 347 		'skin': r'C:\Users\landisb\Downloads\Operation Multis-STL_Files\STL_Files\Lumped\008LL\008LL_meshed_lump.stl'
 348 		}
 349 	#
 350 	ren = tvtk.Renderer(background=colors.white)
 351 	SimpleMorphing(ren, subjectDir, cadaverDir, meshes, limb, zPlane=False, activeMorphing=True)
 352 	print 'complete'
 353 	myVTK.Show(ren, mouseHighlightActor=False)

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2016-09-16 15:12:44, 34.1 KB) [[attachment:Femur_Adjusted_Points.png]]
  • [get | view] (2016-09-16 15:12:04, 32.9 KB) [[attachment:Femur_Guide_Points.png]]
  • [get | view] (2017-04-11 20:32:58, 20.4 KB) [[attachment:LatestMeshMorphing.py]]
  • [get | view] (2016-09-16 15:13:03, 45.3 KB) [[attachment:Morphed_Femur.png]]
  • [get | view] (2017-06-05 14:43:15, 351.5 KB) [[attachment:Morphed_Leg.png]]
  • [get | view] (2020-02-21 22:17:48, 17.4 KB) [[attachment:MultisMeshMorphing.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.