def load_profile(): with_profile(env.profile) from HemeLbSetupTool.Model.Profile import Profile p = Profile() p.LoadFromFile(os.path.expanduser(os.path.join(env.job_profile_path_local,env.profile)+'.pro')) p.StlFile=os.path.expanduser(os.path.join(env.job_profile_path_local,env.profile)+'.stl') return p
class SetupTool(wx.App): def __init__(self, args={}, profile=None, **kwargs): self.cmdLineArgs = args self.cmdLineProfileFile = profile wx.App.__init__(self, **kwargs) return def OnInit(self): # Model self.profile = Profile() self.pipeline = Pipeline() # Controller self.controller = ProfileController(self.profile) self.controller.Pipeline = PipelineController(self.pipeline, self.controller) # View self.view = MainWindow(self.controller) if self.cmdLineProfileFile is not None: # Load the profile self.profile.LoadFromFile(self.cmdLineProfileFile) pass # override any keys that have been set on cmdline. self.profile.UpdateAttributesBasedOnCmdLineArgs(self.cmdLineArgs) self.SetTopWindow(self.view) return True pass
def __init__(self, proFile): # Configurable parameters self.MaxAllowedMachNumber = 0.05 self.InletReynoldsNumber = 300.0 self.MinRadiusVoxels = 5.0 # Read the profile self.FileName = proFile self._profile = Profile() self._profile.LoadFromFile(proFile) # Proxy its iolets self.Iolets = [IoletProxy(iolet, self) for iolet in self._profile.Iolets] # Work out where to store the centrelines base = os.path.splitext(self.StlFile)[0] self.CentreLineFile = base + '_centrelines.vtp' # Sort iolets -> inlets/outlets inlets = [] outlets = [] for io in self.Iolets: if isinstance(io._iolet, Inlet): inlets.append(io) else: outlets.append(io) pass continue # Require only one inlet! assert len(inlets) == 1 self.Inlet = inlets[0] # Require 1 or more outlet assert len(outlets) > 0 self.Outlets = outlets return
def temporary_profile(tmpdir, name): result = Profile() basename = tmpdir.join(name).strpath outGmyFileName = basename + '.gmy' outXmlFileName = basename + '.xml' result.OutputGeometryFile = outGmyFileName result.OutputXmlFile = outXmlFileName return result
def load_profile(): with_profile(env.profile) from HemeLbSetupTool.Model.Profile import Profile p = Profile() p.LoadFromFile( os.path.expanduser( os.path.join(env.job_profile_path_local, env.profile) + '.pro')) return p
def Upgrade(infilename, outfilename): oldProfile = LoadFakeProfile(infilename) UpdateProfileAttributes(oldProfile) # Create a new, genuine profile newPro = Profile() newPro.CloneFrom(oldProfile) newPro.Save(outfilename) return
def Run(profileFile): """Process all the Inlets specified by profileFile, solving the Navier- Stokes equations for steady flow down an infinite channel with the cross section of the inlet. Writes the point ID and the fluid velocity at that point, the velocity being such that the integral of the flow across the channel is unity. Currently output is written as vtkPolyData to files like "$GEOMETRYFILEBASENAME.inlet$ID.vtp" """ # Load the profile profile = Profile() profile.LoadFromFile(profileFile) surfaceFile = profile.StlFile surfaceFileScale = profile.StlFileUnit.SizeInMetres print profile.OutputXmlFile ipFinder = GeometryInletPointFinder(profile.OutputXmlFile) inletPointIndices = ipFinder.GetInletData() if len(inletPointIndices) == 0: print "WARNING: no inletPointIndices found. This may mean that HemeLb is run with no inlets inside the simulation domain (e.g., the inlets defined in the xml file reside outside of the physical geometry)." intersectionFinder = SurfaceIntersectionFinder() intersectionFinder.SetFileName(surfaceFile) intersectionFinder.SetFileUnitLength(surfaceFileScale) for inletId, inlet in enumerate(io for io in profile.Iolets if isinstance(io, Inlet)): print inletId, len(profile.Iolets), len(inletPointIndices) inletPointPD = inletPointIndices[inletId] intersectionFinder.SetIolet(inlet) tesselator = Tesselator() tesselator.SetInlet(inlet) tesselator.SetEdgeConnection(intersectionFinder.GetOutputPort()) tesselator.SetSitesConnection(inletPointPD.GetProducerPort()) solver = PoiseuilleSolver() solver.SetInputConnection(tesselator.GetOutputPort()) cellToPoint = vtk.vtkCellDataToPointData() cellToPoint.SetInputConnection(solver.GetOutputPort()) cellToPoint.Update() #writer = vtk.vtkXMLPolyDataWriter() writer = vtk.vtkPolyDataWriter() writer.SetFileTypeToASCII() writer.SetInputConnection(cellToPoint.GetOutputPort()) # print type(cellToPoint) #cellToPoint is of type vtkobject # print dir(cellToPoint) base, gmy = os.path.splitext(profile.OutputGeometryFile) writer.SetFileName(base + '.inlet%d.txt' % inletId) writer.Write() return base + '.inlet%d.txt' % inletId
def Run(profileFile): """Process all the Inlets specified by profileFile, solving the Navier- Stokes equations for steady flow down an infinite channel with the cross section of the inlet. Writes the point ID and the fluid velocity at that point, the velocity being such that the integral of the flow across the channel is unity. Currently output is written as vtkPolyData to files like "$GEOMETRYFILEBASENAME.inlet$ID.vtp" """ # Load the profile profile = Profile() profile.LoadFromFile(profileFile) surfaceFile = profile.StlFile surfaceFileScale = profile.StlFileUnit.SizeInMetres ipFinder = GeometryInletPointFinder(profile.OutputGeometryFile) inletPointIndices = ipFinder.GetInletData() intersectionFinder = SurfaceIntersectionFinder() intersectionFinder.SetFileName(surfaceFile) intersectionFinder.SetFileUnitLength(surfaceFileScale) for inletId, inlet in enumerate(io for io in profile.Iolets if isinstance(io, Inlet)): inletPointPD = inletPointIndices[inletId] intersectionFinder.SetIolet(inlet) tesselator = Tesselator() tesselator.SetInlet(inlet) tesselator.SetEdgeConnection(intersectionFinder.GetOutputPort()) tesselator.SetSitesConnection(inletPointPD.GetProducerPort()) solver = PoiseuilleSolver() solver.SetInputConnection(tesselator.GetOutputPort()) cellToPoint = vtk.vtkCellDataToPointData() cellToPoint.SetInputConnection(solver.GetOutputPort()) cellToPoint.Update() writer = vtk.vtkXMLPolyDataWriter() writer.SetInputConnection(cellToPoint.GetOutputPort()) base, gmy = os.path.splitext(profile.OutputGeometryFile) writer.SetFileName(base + '.inlet%d.vtp' % inletId) writer.Write()
def test_regression(self, tmpdir): """Generate a gmy from a stored profile and check that the output is identical. """ dataDir = os.path.join(os.path.split(__file__)[0], 'data') proFileName = os.path.join(dataDir, 'test.pro') p = Profile() p.LoadFromFile(proFileName) # Change the output to the tmpdir basename = tmpdir.join('test').strpath outGmyFileName = basename + '.gmy' outXmlFileName = basename + '.xml' p.OutputGeometryFile = outGmyFileName p.OutputXmlFile = outXmlFileName generator = OutputGeneration.PolyDataGenerator(p) generator.Execute() import filecmp assert filecmp.cmp(outGmyFileName, os.path.join(dataDir, 'test.gmy')) assert filecmp.cmp(outXmlFileName, os.path.join(dataDir, 'test.xml'))
def OnInit(self): # Model self.profile = Profile() self.pipeline = Pipeline() # Controller self.controller = ProfileController(self.profile) self.controller.Pipeline = PipelineController(self.pipeline, self.controller) # View self.view = MainWindow(self.controller) if self.cmdLineProfileFile is not None: # Load the profile self.profile.LoadFromFile(self.cmdLineProfileFile) pass # override any keys that have been set on cmdline. self.profile.UpdateAttributesBasedOnCmdLineArgs(self.cmdLineArgs) self.SetTopWindow(self.view) return True
class ProfileProxy(HemeLbParameters): """Add a few properties to the Profile. Delegates unknown attribute look up to the profile. """ _OutputUnitsMap = HemeLbParameters._UnitQuantitiesToMap([pq.mmHg]) def __init__(self, proFile): # Configurable parameters self.MaxAllowedMachNumber = 0.05 self.InletReynoldsNumber = 300.0 self.MinRadiusVoxels = 5.0 # Read the profile self.FileName = proFile self._profile = Profile() self._profile.LoadFromFile(proFile) # Proxy its iolets self.Iolets = [IoletProxy(iolet, self) for iolet in self._profile.Iolets] # Work out where to store the centrelines base = os.path.splitext(self.StlFile)[0] self.CentreLineFile = base + '_centrelines.vtp' # Sort iolets -> inlets/outlets inlets = [] outlets = [] for io in self.Iolets: if isinstance(io._iolet, Inlet): inlets.append(io) else: outlets.append(io) pass continue # Require only one inlet! assert len(inlets) == 1 self.Inlet = inlets[0] # Require 1 or more outlet assert len(outlets) > 0 self.Outlets = outlets return def __getattr__(self, attr): # This is only called if self doesn't have the requested # attribute. We delegate to the real Profile object return getattr(self._profile, attr) @property def LengthUnit(self): return self.StlFileUnit.SizeInMetres * pq.metre @memo_property def CentreLinePolyData(self): """Compute centrelines based on the profile, reusing our memoed copy or reading from the cache file if possible. """ if (os.path.exists(self.CentreLineFile ) and os.path.getmtime(self.CentreLineFile ) > os.path.getmtime(self.StlFile) and os.path.getmtime(self.CentreLineFile ) > os.path.getmtime(self.FileName)): # Cached! reader = vtk.vtkXMLPolyDataReader() reader.SetFileName(self.CentreLineFile) reader.Update() return reader.GetOutput() # Have to compute it # Read the STL file reader = vtk.vtkSTLReader() reader.SetFileName(profile.StlFile) # Find the seed points for centreline calculation # Use points one iolet radius back along the normal. outletPts = [] def scale(iolet): pt = (iolet.Centre - iolet.Radius * iolet.Normal) pt = pt / self.LengthUnit return pt.magnitude for iolet in self.Iolets: if isinstance(iolet._iolet, Inlet): inletPt = scale(iolet) else: outletPts.append(scale(iolet)) pass continue srcPts, tgtPts = FindSeeds(reader, inletPt, outletPts) # Lazy import since it's so slow! from vmtk import vtkvmtk centreliner = vtkvmtk.vtkvmtkPolyDataCenterlines() centreliner.SetInputConnection(reader.GetOutputPort()) centreliner.SetSourceSeedIds(srcPts) centreliner.SetTargetSeedIds(tgtPts) centreliner.SetRadiusArrayName("radius") centreliner.SetCostFunction("1/R") cleaner = vtk.vtkCleanPolyData() cleaner.SetInputConnection(centreliner.GetOutputPort()) writer = vtk.vtkXMLPolyDataWriter() writer.SetInputConnection(cleaner.GetOutputPort()) writer.SetFileName(self.CentreLineFile) writer.Write() return cleaner.GetOutput() @memo_property def CentreLinePointLocator(self): """A vtkAbstractPointLocator subclass that allows fast searching for points on the centreline. """ clPtLoc = vtk.vtkOctreePointLocator() clPtLoc.SetDataSet(self.CentreLinePolyData) clPtLoc.BuildLocator() return clPtLoc @memo_property def CentreLinePoints(self): return vtk_to_numpy(self.CentreLinePolyData.GetPoints().GetData()) * self.LengthUnit @memo_property def CentreLineRadii(self): return vtk_to_numpy(self.CentreLinePolyData.GetPointData().GetArray('radius')) * self.LengthUnit @memo_property def VolumeEstimate(self): """Very rough estimate of the volume. Assumes that the domain is made up of conical frustra corresponding to the line segments of the centrelines. Ignores any volume outside this (e.g. aneurysm sacs) and the overlap between successive segments at curves and bifurcations. """ clPD = self.CentreLinePolyData nPts = clPD.GetNumberOfPoints() # List of the point IDs that have been done already. # Use this rather than the segments of the vessel tree as this # can cope with a network graph that has mergers as well as # bifurcations. seenPts = SortedVector(capacity=nPts, dtype=int) # Convert to numpy for easier access points = self.CentreLinePoints r = self.CentreLineRadii ans = 0.0 * pq.metre**3 # Get the IDs for each path through the network for linePtIds in IterCellPtIds(clPD.GetLines()): # Iterate over adjacent IDs, each of which defines a segment. for i, j in IterPairs(linePtIds): if j in seenPts: # Consider a segment done if the downstream end # has been see before. continue # Length of the segment delta_s = (np.sum((points[i] - points[j])**2))**0.5 # s = distance along centreline # # r_i_____ # | -------_____r_j # | | # | | # --s_i-----------------s_j-----> s Centreline # # V = \int_{s_i}^{s_j} \pi r^2 ds # # r(s) = (r_j - r_i) * (s - s_i) + r_i # ----------- # (s_j - s_i) # # so dr = (r_j - r_i) * ds # ----------- # (s_j - s_i) # # V = \pi (s_j - s_i) * \int_{r_i}^{r_j} r^2 dr # ----------- # (r_j - r_i) # = \pi (s_j - s_i) * (r_j^3 - r_i^3) # ----------- # (r_j - r_i) delta_V = (r[i]**2 + r[i]*r[j] + r[j]**2) * np.pi * delta_s / 3. ans += delta_V # Mark the point as seen. seenPts.add(j) return ans def _AdjustIoletNormals(self): """Adjust the iolet normals such that they are parallel to the centre line at their location. """ if hasattr(self, "_Adjusted") and self._Adjusted: return # Point IDs of them all closestIds = [iolet.ClosestPointId for iolet in self.Iolets] clinesPD = self.CentreLinePolyData points = vtk_to_numpy(clinesPD.GetPoints().GetData()) # Going to iterate over all the paths through the system and # see which iolets have their closest point along each path. # We need to keep of which have been done to avoid repeating # ourselves. Then at each point we track back and forwards to # neighbouring points to get a better estimate of the local # normal. (Hence why we need the connectivity data of the # lines) adjustedPtIds = set() for linePtIds in IterCellPtIds(clinesPD.GetLines()): nPtsOnLine = len(linePtIds) # Find which iolets are closest to points on this line. for i, closest in enumerate(closestIds): match = np.where(linePtIds == closest)[0] if len(match) == 0: # Not this iolet continue # This iolet is closest to this line. iolet = self.Iolets[i] closestPointOnLine = match[0] if linePtIds[closestPointOnLine] in adjustedPtIds: # But we've seen this one - next! continue # Haven't seen this one, add it so don't repeat ourselves adjustedPtIds.add(linePtIds[closestPointOnLine]) # Find the points connected to it on either side, # first as an index in this cell. (Note: make sure we # don't go outside the array bounds.) lo = max(closestPointOnLine - 1, 0) hi = min(closestPointOnLine + 1, nPtsOnLine - 1) # Then map the index-of-point-on-the-line to the point IDs lo = linePtIds[lo] hi = linePtIds[hi] # Now compute the normal along the line rLo = points[lo] rHi = points[hi] n = rHi - rLo n /= np.linalg.norm(n) # If it's the wrong way round, flip it. if np.dot(n, iolet.Normal) < 0: n *= -1. pass # Set in the iolet object. iolet.Normal = n # iolet.Normal.x = n[0] # iolet.Normal.y = n[1] # iolet.Normal.z = n[2] continue continue self._Adjusted = True return @property def InletRadius(self): return self.Inlet.ActualRadius @property @simplify def InletPeakVelocity(self): return self.KinematicViscosity * self.InletReynoldsNumber / (2. * self.InletRadius) @property @simplify def InletFlowRate(self): """In m^3 / s Using standard poiseuille flow results and Re = rho * U_max * dia / visc """ return np.pi * self.InletReynoldsNumber * self.KinematicViscosity * self.InletRadius / 4.0 @property def OutletPressure(self): return 0. * pq.mmHg @simplify def ResistanceForIds(self, idList): return SpecificResistance(self.CentreLinePoints[idList], self.CentreLineRadii[idList]) * self.DynamicViscosity def IndexForUnknown(self, unkn): return self.Unknowns.index(unkn) @memo_property def Unknowns(self): tmp = self.Tree return Unknowns @memo_property def EquationSystem(self): eqs = self.Tree.BuildSystem(self) assert len(eqs) == len(self.Unknowns) return eqs @memo_property def EquationSystemMatrix(self): # This needs to have uniform type, i.e. no quantities # Use SI base for everything. nUnknowns = len(self.Unknowns) eqs = self.EquationSystem ans = np.zeros((nUnknowns, nUnknowns+1)) with self.SIunits(): for i in xrange(nUnknowns): for j in xrange(nUnknowns): ans[i,j] = self.ScaleForOutput(eqs[i].get(self.Unknowns[j], 0.)) continue ans[i, nUnknowns] = eqs[i]['rhs'] continue pass return ans @memo_property def Matrix(self): return self.EquationSystemMatrix[:,:len(self.Unknowns)] @memo_property def Rhs(self): return self.EquationSystemMatrix[:, len(self.Unknowns)] @memo_property def Tree(self): # Before constructing the tree, must trim away the bits of # centreline beyond the iolets. inletPtId = self.Inlet.ClosestPointId outletPtIds = [out.ClosestPointId for out in self.Outlets] linePtIds = [] for pathIdList in IterCellPtIds(self.CentreLinePolyData.GetLines()): # Find where the inlet is inletIndex = np.where(pathIdList == inletPtId)[0] assert inletIndex.shape == (1,) inletIndex = inletIndex[0] # Now the outlet # First figure out which outlet outletId = np.where(np.in1d(outletPtIds, pathIdList))[0] # Must be only one outlet. assert outletId.shape == (1,) # Now where is this outlet's point ID in pathIdList? outletIndex = np.where(pathIdList == outletPtIds[outletId])[0] assert outletIndex.shape == (1,) outletIndex = outletIndex[0] linePtIds.append(pathIdList[inletIndex:outletIndex]) continue # Now can split into the tree tree = VesselSegment(linePtIds) # To fully initialise the tree segments, need to BuildTerms self.Unknowns = tree.BuildTerms(flowRate=self.InletFlowRate, finalOutletPressure=self.OutletPressure) return tree @memo_property def SolutionVector(self): return np.linalg.solve(self.Matrix, self.Rhs) @simplify def SolutionForUnknown(self, unkn): i = self.IndexForUnknown(unkn) return self.SolutionVector[i] * unkn.units @memo_property def MaxVelocity(self): return self.Tree.MaxVelocity(self) @default_property @simplify def TimeStep(self): """MaxAllowedMachNumber = MaxVelocity / SpeedOfSound SpeedOfSound = VoxelSize / (sqrt(3) TimeStep) """ return self.MaxAllowedMachNumber * self.VoxelSize / (np.sqrt(3) * self.MaxVelocity) @default_property def VoxelSize(self): return self.Tree.MinRadius(self) / self.MinRadiusVoxels @property def PulsatilePeriod(self): return 1.0 * pq.second @property def SimulationTime(self): return 10 * self.PulsatilePeriod @memo_property def MaxPathLength(self): return self.Tree.MaxLength(self) @property def MachNumber(self): return self.MaxVelocity / self.SpeedOfSound @property def WomersleyNumber(self): omega = 2.0 * np.pi / self.PulsatilePeriod return self.InletRadius * (omega / self.KinematicViscosity)**0.5 @property @simplify def SoundTime(self): return self.MaxPathLength / self.SpeedOfSound @property @simplify def MomentumTime(self): return self.CentreLineRadii.max()**2 / self.KinematicViscosity @property @simplify def InletPressure(self): return self.SolutionForUnknown(self.Tree.InletPressure) @property def PressureDifference(self): return self.InletPressure - self.OutletPressure @property def TotalSups(self): return self.ScaleToLatticeUnits(self.VolumeEstimate) * self.ScaleToLatticeUnits(self.SimulationTime) @property def HectorTime(self): ans = pq.second * (self.TotalSups / 1e6) ans.units = pq.hour return ans def _UpdateProfile(self): self._AdjustIoletNormals() self._profile.TimeStepSeconds = self.ScaleForOutput(self.TimeStep) self._profile.DurationSeconds = self.ScaleForOutput(self.SimulationTime) self._profile.VoxelSize = float(self.VoxelSize / self.LengthUnit) return def RewriteProfile(self): self._UpdateProfile() base, ext = os.path.splitext(self.FileName) newFile = base + '_adjusted' + ext self.Save(newFile) return def Generate(self): self.RewriteProfile() self._profile.Generate() self.RewriteConfigXml() return def RewriteConfigXml(self): from HemeLbSetupTool.Model.XmlWriter import XmlWriter tree = ElementTree.parse(self.OutputXmlFile) root = tree.getroot() self.RewriteProperties(root) self.RewriteInletCondition(root) self.RewriteOutletConditions(root) XmlWriter.indent(root) tree.write(self.OutputXmlFile) return def RewriteInletCondition(self, root): condEl = root.find('inlets/inlet/condition') condEl.clear() condEl.set('type', 'pressure') condEl.set('subtype', 'cosine') inP = self.SolutionForUnknown(self.Tree.InletPressure) self.QuantityToXml(condEl, 'amplitude', inP) self.QuantityToXml(condEl, 'mean', inP) self.QuantityToXml(condEl, 'phase', np.pi * pq.radian) self.QuantityToXml(condEl, 'period', self.PulsatilePeriod) return def RewriteOutletConditions(self, root): for condEl in root.findall('outlets/outlet/condition'): condEl.clear() condEl.set('type', 'pressure') condEl.set('subtype', 'cosine') self.QuantityToXml(condEl, 'amplitude', self.OutletPressure) self.QuantityToXml(condEl, 'mean', self.OutletPressure) self.QuantityToXml(condEl, 'phase', 0.0 * pq.radian) self.QuantityToXml(condEl, 'period', self.PulsatilePeriod) continue return def RewriteProperties(self, root): # Remove any old ones propertiesEl = root.find('properties') while propertiesEl is not None: root.remove(propertiesEl) propertiesEl = root.find('properties') continue propertiesEl = ElementTree.SubElement(root, 'properties') # Get our plane centres planePtIds = self.Tree.SamplePtIds(50) nPoints = len(planePtIds) points = self.CentreLinePoints[planePtIds] # And the next point along for the normals nextPtIds = self.Tree.SamplePtIds(50, start=1) dx = self.CentreLinePoints[nextPtIds] - points normals = dx / np.sum(dx**2,axis=-1)[:, np.newaxis]**0.5 # And the radius at that point, plus a bit radii = self.CentreLineRadii[planePtIds]*1.1 nDigits = int(np.ceil(np.log10(nPoints))) fileFmtStr = 'plane{:0%dd}.xtr' % nDigits periodStr = str(int(self.ScaleToLatticeUnits(self.PulsatilePeriod/25.))) for i in xrange(len(points)): poEl = ElementTree.SubElement(propertiesEl, 'propertyoutput', file=fileFmtStr.format(i), period=periodStr) geoEl = ElementTree.SubElement(poEl, 'geometry', type='plane') self.QuantityToXml(geoEl, 'point', points[i]) self.QuantityToXml(geoEl, 'normal', normals[i]) self.QuantityToXml(geoEl, 'radius', radii[i]) ElementTree.SubElement(poEl, 'field', type='velocity') ElementTree.SubElement(poEl, 'field', type='pressure') continue return def QuantityToXml(self, parent, name, quantity): value = self.ScaleForOutput(quantity) if isinstance(value, np.ndarray): assert value.shape == (3,) value = '({},{},{})'.format(*value) else: value = str(value) pass try: units = self._OutputUnitsMap[quantity.dimensionality.simplified].symbol except KeyError: units = quantity.dimensionality.string pass return ElementTree.SubElement(parent, name, value=value, units=units) pass