/comet/pyhed/IO/saveload.py

https://gitlab.com/Skibbe/comet · Python · 325 lines · 144 code · 39 blank · 142 comment · 50 complexity · 3ef02089e537e6788d281d774b3e783f MD5 · raw file

  1. """
  2. Part of comet/pyhed/IO
  3. """
  4. # COMET - COupled Magnetic resonance Electrical resistivity Tomography
  5. # Copyright (C) 2019 Nico Skibbe
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. import numpy as np
  17. import os
  18. from pathlib import Path
  19. class ArgsError(Exception):
  20. def __init__(self, value):
  21. self.value = value
  22. def __str__(self):
  23. return repr(self.value)
  24. def cutExtension(path):
  25. filename = os.path.split(path)[1].split('.')[0]
  26. return os.path.join(os.path.dirname(path), filename)
  27. class TetgenNotFoundError(Exception):
  28. """ Special Exception to catch in a try except. """
  29. pass
  30. def searchforTetgen(returnPathfile=False):
  31. """ Try to find a valid tetgen installation for meshing purposes.
  32. Returns:
  33. --------
  34. path: string
  35. Path to tetgen installation or path to pathfile of pyhed itself.
  36. """
  37. import comet.pyhed as ph
  38. import sys
  39. import subprocess as sp
  40. if sys.version_info[:2] < (3, 3):
  41. error = IOError
  42. else:
  43. error = FileNotFoundError
  44. pathfile = os.path.join(os.path.dirname(ph.__file__), '_PATH_.txt')
  45. try:
  46. p = sp.Popen(['tetgen'], stdout=sp.PIPE)
  47. _, err = p.communicate()
  48. PATH = None
  49. except FileNotFoundError:
  50. try:
  51. with open(pathfile, 'r') as pathf:
  52. PATH = pathf.readline()
  53. while PATH.lstrip().startswith('#'):
  54. PATH = pathf.readline()
  55. try:
  56. try:
  57. PATH = PATH.split('TETGENPATH: ')[1].rstrip()
  58. except TypeError:
  59. PATH = PATH.decode().split('TETGENPATH: ')[1].rstrip()
  60. except IndexError:
  61. PATH = ''
  62. if PATH in ['Enter/path/here/', '']:
  63. raise error
  64. except error:
  65. with open(pathfile, 'w') as pathf:
  66. try:
  67. p = sp.Popen(['which', 'tetgen'], stdout=sp.PIPE)
  68. out, err = p.communicate()
  69. # print(out, err)
  70. if len(out) > 0:
  71. try:
  72. PATH = Path(out.split('tetgen')[0])
  73. except TypeError:
  74. PATH = Path(out.decode().split('tetgen')[0])
  75. if not PATH.exists():
  76. # windows root not detected corretly
  77. # there is a special case that can be intercepted
  78. import platform
  79. if platform.system() == 'Windows':
  80. if PATH.drive == '':
  81. parts = PATH.parts
  82. if parts[0] == '\\':
  83. PATH = Path('{}:/'.format(parts[1]),
  84. *parts[2:])
  85. if PATH.exists():
  86. pathf.writelines('TETGENPATH: {}'
  87. .format(PATH.as_posix()))
  88. else:
  89. raise error
  90. except error:
  91. pathf.writelines('TETGENPATH: Enter/path/here/')
  92. raise Exception(
  93. 'Tetgen not found. Please add or link a '
  94. 'executabe tetgen instance to your PATH or alter file'
  95. 'in "{}"'.format(pathfile))
  96. if returnPathfile is True:
  97. return pathfile
  98. else:
  99. if PATH is None:
  100. return None
  101. else:
  102. return Path(PATH).as_posix()
  103. def getItem(archive, key, default=None):
  104. """
  105. Get item for *key* from numpy *archive* via try except with given *default*
  106. value for None.
  107. """
  108. try:
  109. item = archive[key].item()
  110. except ValueError:
  111. item = archive[key]
  112. except KeyError:
  113. item = default
  114. return item
  115. def checkDirectory(savename, filename=False, verbose=False):
  116. """ Checks for directory and creates if not. """
  117. from comet.pyhed import log
  118. if '.' in savename and filename is False and not savename.startswith('.'):
  119. filename = True
  120. if filename is True:
  121. dirname = os.path.dirname(savename)
  122. if dirname == '':
  123. if '.' in savename:
  124. directory = '.'
  125. else:
  126. return
  127. else:
  128. directory = os.path.abspath(dirname)
  129. else:
  130. directory = savename
  131. exists = os.path.exists(directory)
  132. log.debug('checkDirectory: "{}" exists? : {}'.format(directory, exists))
  133. log.debug('todo: replace "checkDirectory" with proper pathlib tools')
  134. if not exists:
  135. os.makedirs(directory)
  136. def checkForFile(name):
  137. """ Checks if file exists and creates a directory if it does not."""
  138. if os.path.exists(name):
  139. return True
  140. else:
  141. checkDirectory(name)
  142. return False
  143. def delLastLine(opened_file, line_ending='\n'):
  144. """ Efficient way of deleting the last line of a large file. """
  145. opened_file.seek(0, os.SEEK_END) # end of file
  146. pos = opened_file.tell() - 1
  147. while pos > 0 and opened_file.read(1) != line_ending:
  148. pos -= 1
  149. opened_file.seek(pos, os.SEEK_SET)
  150. if pos > 0:
  151. opened_file.seek(pos, os.SEEK_SET)
  152. opened_file.truncate()
  153. return opened_file
  154. def addVolumeConstraintToPoly(name, regions, float_format='6.3f'):
  155. '''
  156. Append region information in form of volume constraints to a tetgen.poly
  157. file. The given regions has to be of shape (n, 5 or 6), with
  158. n times: [number, x, y, z, regional attribute, volume constraint]
  159. '''
  160. with open(name, "r+") as in_file:
  161. string = '{}'
  162. for i in range(len(regions[0]) - 1): # either 4 or 5 floats possible
  163. string += ' {:%s}' % (float_format)
  164. string += os.linesep
  165. delLastLine(in_file)
  166. in_file.seek(0, os.SEEK_END)
  167. in_file.write(os.linesep)
  168. lines = '# volume constraints{}'.format(os.linesep)
  169. lines += '{:d}{}'.format(len(regions), os.linesep)
  170. for region in regions:
  171. lines += string.format(*region)
  172. in_file.write(lines)
  173. def createCustEMDirectories(m_dir='.', r_dir='.'):
  174. """ Creates the used custEM directories based on *m_dir* and *r_dir*. """
  175. m_path = Path(m_dir)
  176. r_path = Path(r_dir)
  177. m_path.joinpath('_h5').mkdir(exist_ok=True, parents=True)
  178. m_path.joinpath('_mesh').mkdir(exist_ok=True)
  179. m_path.joinpath('para').mkdir(exist_ok=True)
  180. r_path.joinpath('primary_fields/custom').mkdir(exist_ok=True, parents=True)
  181. r_path.joinpath('E_s').mkdir(exist_ok=True)
  182. r_path.joinpath('H_s').mkdir(exist_ok=True)
  183. #def exportTetgenPolyFile(filename, poly, float_format='.12e'):
  184. # '''
  185. # Writes a given piecewise linear complex (mesh/poly ) into a Ascii file in
  186. # tetgen's .poly format.
  187. #
  188. # Parameters
  189. # ----------
  190. #
  191. # filename: string
  192. # Name in which the result will be written. The recommended file
  193. # ending is '.poly'.
  194. #
  195. # poly: pg.Mesh
  196. # Piecewise linear complex as pygimli mesh to be exported.
  197. #
  198. # float_format: format string ('.12e')
  199. # Format that will be used to write float values in the Ascii file.
  200. # Default is the exponential float form with a precision of 12 digits.
  201. #
  202. # '''
  203. # filename = filename.rstrip('.poly') + '.poly'
  204. # polytxt = ''
  205. # sep = '\t' # standard tab seperated file
  206. # assert poly.dim() == 3, 'Exit, only for 3D meshes.'
  207. # boundary_marker = 1
  208. # attribute_count = 0
  209. #
  210. # # Part 1/4: node list
  211. # # intro line
  212. # # <nodecount> <dimension (3)> <# of attributes> <boundary markers (0 or 1)>
  213. # polytxt += '{0}{5}{1}{5}{2}{5}{3}{4}'.format(poly.nodeCount(), 3,
  214. # attribute_count,
  215. # boundary_marker,
  216. # os.linesep, sep)
  217. # # loop over positions, attributes and marker(node)
  218. # # <point idx> <x> <y> <z> [attributes] [boundary marker]
  219. # point_str = '{:d}' # index of the point
  220. # for i in range(3):
  221. # # coords as float with given precision
  222. # point_str += sep + '{:%s}' % (float_format)
  223. # point_str += sep + '{:d}' + os.linesep # node marker
  224. # for j, node in enumerate(poly.nodes()):
  225. # fill = [node.id()]
  226. # fill.extend([pos for pos in node.pos()])
  227. # fill.append(node.marker())
  228. # polytxt += point_str.format(*fill)
  229. #
  230. # # Part 2/4: boundary list
  231. # # intro line
  232. # # <# of facets> <boundary markers (0 or 1)>
  233. # polytxt += '{0:d}{2}1{1}'.format(poly.boundaryCount(), os.linesep, sep)
  234. # # loop over facets, each facet can contain an arbitrary number of holes
  235. # # and polygons, in our case, there is always one polygon per facet.
  236. # for k, bound in enumerate(poly.boundaries()):
  237. # # one line per facet
  238. # # <# of polygons> [# of holes] [boundary marker]
  239. # npolys = 1
  240. # polytxt += '1{2}0{2}{0:d}{1}'.format(bound.marker(), os.linesep, sep)
  241. # # inner loop over polygons
  242. # # <# of corners> <corner 1> <corner 2> ... <corner #>
  243. # for l in range(npolys):
  244. # poly_str = '{:d}'.format(bound.nodeCount())
  245. # for ind in bound.ids():
  246. # poly_str += sep + '{:d}'.format(ind)
  247. # polytxt += '{0}{1}'.format(poly_str, os.linesep)
  248. # # inner loop over holes
  249. # # not necessary yet ?! why is there an extra hole section?
  250. # # because this is for 2D holes in facets only
  251. #
  252. # # part 3/4: hole list
  253. # # intro line
  254. # # <# of holes>
  255. # holes = poly.holeMarker()
  256. # polytxt += '{:d}{}'.format(len(holes), os.linesep)
  257. # # loop over hole markers
  258. # # <hole #> <x> <y> <z>
  259. # hole_str = '{:d}'
  260. # for m in range(3):
  261. # hole_str += sep + '{:%s}' % float_format
  262. # hole_str += os.linesep
  263. # for n, hole in enumerate(holes):
  264. # polytxt += hole_str.format(n, *hole)
  265. #
  266. # # part 4/4: region attributes and volume constraints (optional)
  267. # # intro line
  268. # # <# of regions>
  269. # regions = poly.regionMarker()
  270. # polytxt += '{:d}{}'.format(len(regions), os.linesep)
  271. # # loop over region markers
  272. # # <region #> <x> <y> <z> <region number> <region attribute>
  273. # region_str = '{:d}'
  274. # for o in range(3):
  275. # region_str += sep + '{:%s}' % (float_format)
  276. # region_str += sep + '{:d}%s{:%s}' % (sep, float_format) + os.linesep
  277. # for p, region in enumerate(regions):
  278. # polytxt += region_str.format(p, region.x(), region.y(), region.z(),
  279. # region.marker(),
  280. # region.area())
  281. #
  282. # # writing file
  283. # with open(filename, 'w') as out:
  284. # out.write(polytxt)
  285. # THE END