Skip to content

Commit 7809464

Browse files
authored
Merge pull request #114 from simpeg/feat/vtk
feat/vtk: Add VTK object interface
2 parents cc9520c + 6f27fb2 commit 7809464

20 files changed

+792
-224
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[bumpversion]
2-
current_version = 0.3.4
2+
current_version = 0.3.5
33
files = setup.py discretize/__init__.py docs/conf.py
44

discretize/BaseMesh.py

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
import json
55
from .utils.matutils import mkvc
66

7+
try:
8+
from .mixins import vtkInterface
9+
except ImportError as err:
10+
vtkInterface = object
711

8-
class BaseMesh(properties.HasProperties):
12+
class BaseMesh(properties.HasProperties, vtkInterface):
913
"""
1014
BaseMesh does all the counting you don't want to do.
1115
BaseMesh should be inherited by meshes with a regular structure.
@@ -29,14 +33,16 @@ class BaseMesh(properties.HasProperties):
2933
)
3034

3135
# Instantiate the class
32-
def __init__(self, n, x0=None):
36+
def __init__(self, n, x0=None, **kwargs):
3337
self._n = n # number of dimensions
3438

3539
if x0 is None:
3640
self.x0 = np.zeros(len(self._n))
3741
else:
3842
self.x0 = x0
3943

44+
super(BaseMesh, self).__init__(**kwargs)
45+
4046
# Validators
4147
@properties.validator('_n')
4248
def check_n_shape(self, change):
@@ -52,7 +58,7 @@ def check_n_shape(self, change):
5258
"supported".format(change['value'])
5359
)
5460

55-
if change['previous'] != properties.undefined:
61+
if np.any(change['previous'] != properties.undefined):
5662
# can't change dimension of the mesh
5763
assert len(change['previous']) == len(change['value']), (
5864
"Cannot change dimensionality of the mesh. Expected {} "
@@ -373,11 +379,92 @@ def copy(self):
373379
"""
374380
return properties.copy(self)
375381

382+
axis_u = properties.Vector3(
383+
'Vector orientation of u-direction. For more details see the docs for the :attr:`~discretize.BaseMesh.BaseMesh.rotation_matrix` property.',
384+
default='X',
385+
length=1
386+
)
387+
axis_v = properties.Vector3(
388+
'Vector orientation of v-direction. For more details see the docs for the :attr:`~discretize.BaseMesh.BaseMesh.rotation_matrix` property.',
389+
default='Y',
390+
length=1
391+
)
392+
axis_w = properties.Vector3(
393+
'Vector orientation of w-direction. For more details see the docs for the :attr:`~discretize.BaseMesh.BaseMesh.rotation_matrix` property.',
394+
default='Z',
395+
length=1
396+
)
397+
398+
@properties.validator
399+
def _validate_orientation(self):
400+
"""Check if axes are orthogonal"""
401+
if not (np.abs(self.axis_u.dot(self.axis_v) < 1e-6) and
402+
np.abs(self.axis_v.dot(self.axis_w) < 1e-6) and
403+
np.abs(self.axis_w.dot(self.axis_u) < 1e-6)):
404+
raise ValueError('axis_u, axis_v, and axis_w must be orthogonal')
405+
return True
406+
407+
@property
408+
def reference_is_rotated(self):
409+
"""True if the axes are rotated from the traditional <X,Y,Z> system
410+
with vectors of :math:`(1,0,0)`, :math:`(0,1,0)`, and :math:`(0,0,1)`
411+
"""
412+
if ( np.allclose(self.axis_u, (1, 0, 0)) and
413+
np.allclose(self.axis_v, (0, 1, 0)) and
414+
np.allclose(self.axis_w, (0, 0, 1)) ):
415+
return False
416+
return True
417+
418+
@property
419+
def rotation_matrix(self):
420+
"""Builds a rotation matrix to transform coordinates from their coordinate
421+
system into a conventional cartesian system. This is built off of the
422+
three `axis_u`, `axis_v`, and `axis_w` properties; these mapping
423+
coordinates use the letters U, V, and W (the three letters preceding X,
424+
Y, and Z in the alphabet) to define the projection of the X, Y, and Z
425+
durections. These UVW vectors describe the placement and transformation
426+
of the mesh's coordinate sytem assuming at most 3 directions.
427+
428+
Why would you want to use these UVW mapping vectors the this
429+
`rotation_matrix` property? They allow us to define the realationship
430+
between local and global coordinate systems and provide a tool for
431+
switching between the two while still maintaing the connectivity of the
432+
mesh's cells. For a visual example of this, please see the figure in the
433+
docs for the :class:`~discretize.mixins.vtkModule.vtkInterface`.
434+
"""
435+
return np.array([self.axis_u, self.axis_v, self.axis_w])
436+
437+
438+
reference_system = properties.String(
439+
'The type of coordinate reference frame. Can take on the values ' +
440+
'cartesian, cylindrical, or spherical. Abbreviations of these are allowed.',
441+
default='cartesian',
442+
change_case='lower',
443+
)
444+
445+
@properties.validator
446+
def _validate_reference_system(self):
447+
"""Check if the reference system is of a known type."""
448+
choices = ['cartesian', 'cylindrical', 'spherical']
449+
# Here are a few abbreviations that users can harnes
450+
abrevs = {
451+
'car': choices[0],
452+
'cart': choices[0],
453+
'cy': choices[1],
454+
'cyl': choices[1],
455+
'sph': choices[2],
456+
}
457+
# Get the name and fix it if it is abbreviated
458+
self.reference_system = abrevs.get(self.reference_system, self.reference_system)
459+
if self.reference_system not in choices:
460+
raise ValueError('Coordinate system ({}) unknown.'.format(self.reference_system))
461+
return True
462+
376463

377464
class BaseRectangularMesh(BaseMesh):
378465
"""BaseRectangularMesh"""
379-
def __init__(self, n, x0=None):
380-
BaseMesh.__init__(self, n, x0=x0)
466+
def __init__(self, n, x0=None, **kwargs):
467+
BaseMesh.__init__(self, n, x0=x0, **kwargs)
381468

382469
@property
383470
def nCx(self):

discretize/CylMesh.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class CylMesh(
4343

4444
def __init__(self, h=None, x0=None, **kwargs):
4545
super(CylMesh, self).__init__(h=h, x0=x0, **kwargs)
46+
self.reference_system = 'cylindrical'
4647

4748
if not np.abs(self.hy.sum() - 2*np.pi) < 1e-10:
4849
raise AssertionError("The 2nd dimension must sum to 2*pi")

discretize/MeshIO.py

Lines changed: 6 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
from . import utils
88
from .BaseMesh import BaseMesh
99

10+
try:
11+
from .mixins import vtkTensorRead
12+
except ImportError as err:
13+
vtkTensorRead = object
14+
1015

1116
def load_mesh(filename):
1217
"""
@@ -24,7 +29,7 @@ def load_mesh(filename):
2429
return data
2530

2631

27-
class TensorMeshIO(object):
32+
class TensorMeshIO(vtkTensorRead):
2833

2934
@classmethod
3035
def _readUBC_3DMesh(TensorMesh, fileName):
@@ -154,164 +159,6 @@ def readUBC(TensorMesh, fileName, directory=''):
154159
raise Exception('File format not recognized')
155160
return Tnsmsh
156161

157-
@classmethod
158-
def readVTK(TensorMesh, fileName, directory=''):
159-
"""Read VTK Rectilinear (vtr xml file) and return Tensor mesh and model
160-
161-
Input:
162-
:param str fileName: path to the vtr model file to read or just its name if directory is specified
163-
:param str directory: directory where the UBC GIF file lives
164-
165-
Output:
166-
:rtype: tuple
167-
:return: (TensorMesh, modelDictionary)
168-
"""
169-
from vtk import vtkXMLRectilinearGridReader as vtrFileReader
170-
from vtk.util.numpy_support import vtk_to_numpy
171-
172-
fname = os.path.join(directory, fileName)
173-
# Read the file
174-
vtrReader = vtrFileReader()
175-
vtrReader.SetFileName(fname)
176-
vtrReader.Update()
177-
vtrGrid = vtrReader.GetOutput()
178-
# Sort information
179-
hx = np.abs(np.diff(vtk_to_numpy(vtrGrid.GetXCoordinates())))
180-
xR = vtk_to_numpy(vtrGrid.GetXCoordinates())[0]
181-
hy = np.abs(np.diff(vtk_to_numpy(vtrGrid.GetYCoordinates())))
182-
yR = vtk_to_numpy(vtrGrid.GetYCoordinates())[0]
183-
zD = np.diff(vtk_to_numpy(vtrGrid.GetZCoordinates()))
184-
# Check the direction of hz
185-
if np.all(zD < 0):
186-
hz = np.abs(zD[::-1])
187-
zR = vtk_to_numpy(vtrGrid.GetZCoordinates())[-1]
188-
else:
189-
hz = np.abs(zD)
190-
zR = vtk_to_numpy(vtrGrid.GetZCoordinates())[0]
191-
x0 = np.array([xR, yR, zR])
192-
193-
# Make the object
194-
tensMsh = TensorMesh([hx, hy, hz], x0=x0)
195-
196-
# Grap the models
197-
models = {}
198-
for i in np.arange(vtrGrid.GetCellData().GetNumberOfArrays()):
199-
modelName = vtrGrid.GetCellData().GetArrayName(i)
200-
if np.all(zD < 0):
201-
modFlip = vtk_to_numpy(vtrGrid.GetCellData().GetArray(i))
202-
tM = tensMsh.r(modFlip, 'CC', 'CC', 'M')
203-
modArr = tensMsh.r(tM[:, :, ::-1], 'CC', 'CC', 'V')
204-
else:
205-
modArr = vtk_to_numpy(vtrGrid.GetCellData().GetArray(i))
206-
models[modelName] = modArr
207-
208-
# Return the data
209-
return tensMsh, models
210-
211-
def writeVTK(mesh, fileName, models=None, directory=''):
212-
"""Makes and saves a VTK rectilinear file (vtr)
213-
for a Tensor mesh and model.
214-
215-
Input:
216-
:param str fileName: path to the output vtk file or just its name if directory is specified
217-
:param str directory: directory where the UBC GIF file lives
218-
:param dict models: dictionary of numpy.array - Name('s) and array('s).
219-
Match number of cells
220-
"""
221-
from vtk import vtkRectilinearGrid as rectGrid, vtkXMLRectilinearGridWriter as rectWriter, VTK_VERSION
222-
from vtk.util.numpy_support import numpy_to_vtk
223-
224-
fname = os.path.join(directory, fileName)
225-
# Deal with dimensionalities
226-
if mesh.dim >= 1:
227-
vX = mesh.vectorNx
228-
xD = mesh.nNx
229-
yD, zD = 1, 1
230-
vY, vZ = np.array([0, 0])
231-
if mesh.dim >= 2:
232-
vY = mesh.vectorNy
233-
yD = mesh.nNy
234-
if mesh.dim == 3:
235-
vZ = mesh.vectorNz
236-
zD = mesh.nNz
237-
# Use rectilinear VTK grid.
238-
# Assign the spatial information.
239-
vtkObj = rectGrid()
240-
vtkObj.SetDimensions(xD, yD, zD)
241-
vtkObj.SetXCoordinates(numpy_to_vtk(vX, deep=1))
242-
vtkObj.SetYCoordinates(numpy_to_vtk(vY, deep=1))
243-
vtkObj.SetZCoordinates(numpy_to_vtk(vZ, deep=1))
244-
245-
# Assign the model('s) to the object
246-
if models is not None:
247-
for item in six.iteritems(models):
248-
# Convert numpy array
249-
vtkDoubleArr = numpy_to_vtk(item[1], deep=1)
250-
vtkDoubleArr.SetName(item[0])
251-
vtkObj.GetCellData().AddArray(vtkDoubleArr)
252-
# Set the active scalar
253-
vtkObj.GetCellData().SetActiveScalars(list(models.keys())[0])
254-
255-
# Check the extension of the fileName
256-
ext = os.path.splitext(fname)[1]
257-
if ext is '':
258-
fname = fname + '.vtr'
259-
elif ext not in '.vtr':
260-
raise IOError('{:s} is an incorrect extension, has to be .vtr')
261-
# Write the file.
262-
vtrWriteFilter = rectWriter()
263-
if float(VTK_VERSION.split('.')[0]) >= 6:
264-
vtrWriteFilter.SetInputData(vtkObj)
265-
else:
266-
vtuWriteFilter.SetInput(vtuObj)
267-
vtrWriteFilter.SetFileName(fname)
268-
vtrWriteFilter.Update()
269-
270-
def _toVTRObj(mesh, models=None):
271-
"""
272-
Makes and saves a VTK rectilinear file (vtr) for a
273-
Tensor mesh and model.
274-
275-
Input:
276-
:param str, path to the output vtk file
277-
:param mesh, TensorMesh object - mesh to be transfer to VTK
278-
:param models, dictionary of numpy.array - Name('s) and array('s). Match number of cells
279-
280-
"""
281-
# Import
282-
from vtk import vtkRectilinearGrid as rectGrid, VTK_VERSION
283-
from vtk.util.numpy_support import numpy_to_vtk
284-
285-
# Deal with dimensionalities
286-
if mesh.dim >= 1:
287-
vX = mesh.vectorNx
288-
xD = mesh.nNx
289-
yD, zD = 1, 1
290-
vY, vZ = np.array([0, 0])
291-
if mesh.dim >= 2:
292-
vY = mesh.vectorNy
293-
yD = mesh.nNy
294-
if mesh.dim == 3:
295-
vZ = mesh.vectorNz
296-
zD = mesh.nNz
297-
# Use rectilinear VTK grid.
298-
# Assign the spatial information.
299-
vtkObj = rectGrid()
300-
vtkObj.SetDimensions(xD, yD, zD)
301-
vtkObj.SetXCoordinates(numpy_to_vtk(vX, deep=1))
302-
vtkObj.SetYCoordinates(numpy_to_vtk(vY, deep=1))
303-
vtkObj.SetZCoordinates(numpy_to_vtk(vZ, deep=1))
304-
305-
# Assign the model('s) to the object
306-
if models is not None:
307-
for item in models.iteritems():
308-
# Convert numpy array
309-
vtkDoubleArr = numpy_to_vtk(item[1], deep=1)
310-
vtkDoubleArr.SetName(item[0])
311-
vtkObj.GetCellData().AddArray(vtkDoubleArr)
312-
# Set the active scalar
313-
vtkObj.GetCellData().SetActiveScalars(list(models.keys())[0])
314-
return vtkObj
315162

316163
def _readModelUBC_2D(mesh, fileName):
317164
"""
@@ -655,52 +502,3 @@ def writeUBC(mesh, fileName, models=None):
655502
for item in six.iteritems(models):
656503
# Save the data
657504
np.savetxt(item[0], item[1][ubc_order], fmt='%3.5e')
658-
659-
660-
def writeVTK(self, fileName, models=None):
661-
"""Function to write a VTU file from a TreeMesh and model."""
662-
import vtk
663-
from vtk import vtkXMLUnstructuredGridWriter as Writer, VTK_VERSION
664-
from vtk.util.numpy_support import numpy_to_vtk
665-
666-
# Make the data parts for the vtu object
667-
# Points
668-
ptsMat = np.vstack((self.gridN, self.gridhN))
669-
670-
vtkPts = vtk.vtkPoints()
671-
vtkPts.SetData(numpy_to_vtk(ptsMat, deep=True))
672-
# Cells
673-
cellArray = [c for c in self]
674-
cellConn = np.array([cell.nodes for cell in cellArray])
675-
676-
cellsMat = np.concatenate((np.ones((cellConn.shape[0], 1))*cellConn.shape[1], cellConn), axis=1).ravel()
677-
cellsArr = vtk.vtkCellArray()
678-
cellsArr.SetNumberOfCells(cellConn.shape[0])
679-
cellsArr.SetCells(cellConn.shape[0], numpy_to_vtk(cellsMat, deep=True, array_type=vtk.VTK_ID_TYPE))
680-
681-
# Make the object
682-
vtuObj = vtk.vtkUnstructuredGrid()
683-
vtuObj.SetPoints(vtkPts)
684-
vtuObj.SetCells(vtk.VTK_VOXEL, cellsArr)
685-
# Add the level of refinement as a cell array
686-
cell_levels = np.array([cell._level for cell in cellArray])
687-
refineLevelArr = numpy_to_vtk(cell_levels, deep=1)
688-
refineLevelArr.SetName('octreeLevel')
689-
vtuObj.GetCellData().AddArray(refineLevelArr)
690-
# Assign the model('s) to the object
691-
if models is not None:
692-
for item in six.iteritems(models):
693-
# Convert numpy array
694-
vtkDoubleArr = numpy_to_vtk(item[1], deep=1)
695-
vtkDoubleArr.SetName(item[0])
696-
vtuObj.GetCellData().AddArray(vtkDoubleArr)
697-
698-
# Make the writer
699-
vtuWriteFilter = Writer()
700-
if float(VTK_VERSION.split('.')[0]) >= 6:
701-
vtuWriteFilter.SetInputData(vtuObj)
702-
else:
703-
vtuWriteFilter.SetInput(vtuObj)
704-
vtuWriteFilter.SetFileName(fileName)
705-
# Write the file
706-
vtuWriteFilter.Update()

discretize/TensorMesh.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def __init__(self, h=None, x0=None, **kwargs):
8888
n = np.array([x.size for x in h])
8989

9090
super(BaseTensorMesh, self).__init__(
91-
n, x0=x0
91+
n, x0=x0, **kwargs
9292
)
9393

9494
# Ensure h contains 1D vectors

0 commit comments

Comments
 (0)