/
mayavi2.py
611 lines (490 loc) · 18.8 KB
/
mayavi2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
#!/usr/bin/env python
"""The Mayavi application standalone script.
This script parses the command line arguments and launches Mayavi2
suitably. It is meant to be used by those using Mayavi2 as a
standalone application.
Mayavi2: http://docs.enthought.com/mayavi/mayavi/overview.html
"""
# Author: Prabhu Ramachandran <prabhu@enthought.com>
# Copyright (c) 2005-2020, Enthought, Inc.
# License: BSD Style.
# Standard library imports.
import sys
import getopt
import logging
from os.path import splitext, exists, join, abspath
# Local imports.
from mayavi.__version__ import __version__
from mayavi.scripts.util import get_data_dir
# Globals.
OFFSCREEN = False
# A global mayavi instance so we can close it correctly.
mayavi = None
###########################################################################
# Utility functions.
###########################################################################
def usage():
msg = """Usage:\n\nmayavi2 [options] ... [arg1] [arg2] ...
Where arg1, arg2 ... are optional file names that correspond to saved
Mayavi2 visualizations (file.mv2) or Mayavi2 scripts (file.py) or any
data files supported by Mayavi. Valid options are one or more of the
following:
-d datafile.ext
--data datafile.ext
datafile.ext can be any of the supported data file formats. This
includes VTK file formats (*.vtk, *.xml, *.vt[i,p,r,s,u],
*.pvt[i,p,r,s,u]), VRML2 (*.wrl), 3D Studio (*.3ds), PLOT3D
(*.xyz), STL, BYU, RAW, PLY, PDB, SLC, FACET, OBJ, AVSUCD
(*.inp), GAMBIT (*.neu), Exodus (*.exii), PNG, JPEG, BMP,
PNM, DCM, DEM, MHA, MHD, MINC, XIMG, TIFF, and various others that
are supported. datafile.ext can also be a source object not
associated with a file, for example ParametricSurface or PointLoad
will load the corresponding data sources into Mayavi.
--filter filter-name
-f filter-name
The passed filter name is loaded with respect to the current
source/filter object. The filter name must be a valid one if not
you will get an error message.
If the filter is specified as 'package.sub.filter.SomeFilter'
then the filter (`SomeFilter`) is imported from
'package.sub.filter'. Standard modules provided with mayavi2 do
not need the full path specification.
Example::
mayavi2 -d data.vtk -f ExtractVectorNorm -f m2_user_filters.TestFilter
In this example 'ExtractVectorNorm' is a standard filter and
'm2_user_filters.TestFilter' is some user defined filter.
-m module-name
--module module-name
The passed module name is loaded in the current ModuleManager.
The module name must be a valid one if not you will get an error
message.
If a module is specified as 'package.sub.module.SomeModule' then
the module (`SomeModule`) is imported from 'package.sub.module'.
Standard modules provided with mayavi2 do not need the full path
specification.
Example::
mayavi2 -d data.vtk -m Outline -m m2_user_modules.TestModule
In this example 'Outline' is a standard module and
'm2_user_modules.TestModule' is some user defined module.
-M
--module-mgr
Starts up a new module manager on the Mayavi pipeline.
-n
--new-scene
Creates a new TVTK scene. Any options passed after this will
apply to this newly created scene.
-o
--offscreen
Run Mayavi in offscreen mode without any graphical user interface.
This is most useful for scripts that need to render images
offscreen.
-x script-file
--exec script-file
This executes the given script in a namespace where we guarantee
that the name 'mayavi' is Mayavi's script instance -- just like
in the embedded Python interpreter.
**WARNING**: Note that this uses `execfile`, so please note that
this can be dangerous if the script does something
nasty!
-s python-expression
--set python-expression
Execute the expression on the last created object. For example,
lets say the previous object was a module. If you want to set the
color of that object and save the scene you may do::
$ mayavi2 [...] -m Outline -s"actor.property.color = (1,0,0)" \
-s "scene.save('test.png', size=(800, 800))"
You should use quotes for the expression.
**WARNING**: Note that this uses `exec`, so please note that
this can be dangerous!
-z saved-visualization-file
--viz saved-visualization-file
--visualization saved-visualization-file
Loads a previously saved Mayavi2 visualization file passed as the
argument.
-t
--test
Runs the mayavi2 test suite and exits. If run as such, this runs
both the TVTK and Mayavi2 unittests. If any additional arguments
are passed they are passed along to the test runner. So this may
be used to run other tests as well. For example::
mayavi2 -t apptools.persistence
This will run just the tests inside the apptools.persistence
package. You can also specify a directory with test files to run
with this, for example::
mayavi2 -t relative_path_to/integrationtests/mayavi
will run the integration tests from the mayavi sources.
-v
--verbose
Prints verbose logs on the console.
-V
--version
Prints the Mayavi version.
-h
--help
Prints this help message.
Examples::
mayavi2 test.mv2 (or mayavi2 -z test.mv2)
mayavi2 test.py (or mayavi2 -x test.py)
mayavi2 test.mv2 other.mv2 (or mayavi2 -z test.mv2 -z other.mv2)
mayavi2 -d test.vtk -m Axes -m GridPlane \\
-f Threshold -m IsoSurface \\
-n -d heart.vtk -m Outline -m ContourGridPlane
"""
return msg
def parse_cmd_line(arguments):
"""Returns the options and arguments parsed via getopts.
Due to the special needs (order of args is important and repeated
options will be supplied) of this command line parser we must use
getopts and not optparse.
Input Arguments:
arguments -- This can be either a list of arguments as in
sys.argv[1:] or a string that is similar to the one
passed on the command line. If it is a string the
string is split to create a list of arguments.
"""
if type(arguments) is str:
arguments = arguments.split()
options = "d:m:f:z:x:s:nMvo"
long_opts = ['data=',
'module=', 'filter=',
'visualization=', 'viz=',
'exec=',
'set=',
'verbose',
'module-mgr', 'new-scene', 'offscreen']
try:
opts, args = getopt.getopt(arguments, options, long_opts)
except getopt.error as msg:
print(msg)
print(usage())
print('-'*70)
print(msg)
sys.exit(1)
return opts, args
def _get_non_file_sources():
"""Returns a dict indexed on the name of non-file related sources
ids with the value being the corresponding metadata object.
"""
from mayavi.core.registry import registry
data = {}
for src in registry.sources:
if len(src.extensions) == 0:
name = src.id[:-6]
data[name] = src
return data
def process_cmd_line(app, opts, args):
""" Processes the passed command line arguments.
Input Arguments:
app -- A Mayavi application instance.
opts -- The list of options returned by getopt.
args -- The remaining arguments returned by getopt.
"""
from mayavi.core.common import error, exception
from tvtk.common import camel2enthought
sources = _get_non_file_sources()
script = app.script
last_obj = None
# Start a new scene by default if there is none currently and none
# was specified at the start of the command line arguments.
if script.engine.current_scene is None:
new_scene = False
if len(opts) == 0:
if len(args) == 0:
new_scene = True
elif (opts[0][0] not in ('-n', '--new-scene', '-z',
'--visualization', '--viz',
'-x', '--exec')):
new_scene = True
if new_scene:
last_obj = script.new_scene()
for o, a in opts:
if o in ('-d', '--data'):
base, ext = splitext(a)
if exists(a):
last_obj = script.open(a)
elif a in sources:
md = sources[a]
src = md.get_callable()()
script.add_source(src)
last_obj = src
else:
error("File/Source %s does not exist!" % a)
return
if o in ('-m', '--module'):
if '.' in a:
idx = a.rfind('.')
modname = a[:idx]
classname = a[idx+1:]
else:
modname = 'mayavi.modules.%s' % camel2enthought(a)
classname = a
try:
mod = __import__(modname, globals(), locals(), [classname])
except ImportError as msg:
exception(str(msg))
return
else:
m = getattr(mod, classname)()
if classname == 'Labels':
m.object = script.engine.current_object
script.add_module(m)
last_obj = m
if o in ('-f', '--filter'):
if '.' in a:
idx = a.rfind('.')
modname = a[:idx]
classname = a[idx+1:]
else:
if a[:12] == 'UserDefined:':
modname = 'mayavi.filters.user_defined'
classname = 'UserDefined'
# Create the wrapped filter.
fname = a[12:]
from tvtk.api import tvtk
try:
extra = getattr(tvtk, fname)()
except (AttributeError, TypeError):
# Don't worry about errors.
extra = None
else:
modname = 'mayavi.filters.%s' % camel2enthought(a)
classname = a
extra = None
try:
mod = __import__(modname, globals(), locals(), [classname])
except ImportError as msg:
exception(str(msg))
return
else:
klass = getattr(mod, classname)
if classname != 'UserDefined':
f = klass()
else:
if extra is not None:
f = klass(filter=extra)
else:
f = klass()
f.setup_filter()
script.add_filter(f)
last_obj = f
if o in ('-M', '--module-mgr'):
from mayavi.core.module_manager \
import ModuleManager
mm = ModuleManager()
script.add_filter(mm)
last_obj = mm
if o in ('-n', '--new-scene'):
script.new_scene()
e = script.engine
s = e.scenes[-1]
e.trait_set(current_scene=s, current_object=s)
last_obj = s
if o in ('-x', '--exec'):
err = run_script(script, a)
if err: # stop processing options.
return
if o in ('-s', '--set'):
try:
stmt = 'last_obj.' + a
exec(stmt, locals(), globals())
except Exception as msg:
exception(str(msg))
if o in ('-z', '--visualization', '--viz'):
script.load_visualization(a)
# for remaining arguments simply load saved visualizations.
for arg in args:
base, ext = splitext(arg)
if ext == '.mv2':
script.load_visualization(arg)
elif ext == '.py':
err = run_script(script, arg)
if err: # stop processing arguments.
return
else:
script.open(arg)
def run_script(mayavi, script_name):
"""Execfiles a given script. The name `mayavi` is bound to the
mayavi script instance just like in the embedded interpreter.
`script_name` is the name of the script to execute.
Note that this function uses `execfile`. You should be careful
when using this.
It returns `False` if everything was OK and `True` if not.
"""
from mayavi.core.common import exception
g = sys.modules['__main__'].__dict__
if 'mayavi' not in g:
g['mayavi'] = mayavi
g['engine'] = mayavi.engine
g['__file__'] = script_name
error = False
# Do execfile
try:
# If we don't pass globals twice we get NameErrors and nope,
# using exec open(script_name).read() does not fix it.
exec(compile(open(script_name).read(), script_name, 'exec'), g, g)
except Exception as msg:
exception(str(msg))
error = True
return error
# This runs the runtests script and sends any args to it.
if ('-t' in sys.argv[1:]) or ('--test' in sys.argv[1:]):
from mayavi.tests import runtests
for arg in ('-t', '--test'):
if arg in sys.argv[1:]:
sys.argv.remove(arg)
runtests.main()
# If the user just wants help messages. Print them before importing
# any of the big modules.
if ('-h' in sys.argv[1:]) or ('--help' in sys.argv[1:]):
print(usage())
sys.exit(0)
if ('-V' in sys.argv[1:]) or ('--version' in sys.argv[1:]):
print('Mayavi %s' % __version__)
sys.exit(0)
for opt, arg in parse_cmd_line(sys.argv[1:])[0]:
if opt in ('-o', '--offscreen'):
OFFSCREEN = True
break
# Create opt and arg to be able to delete them even if the previous loop
# is empty.
opt, arg = None, None
del opt, arg
# The vtk module is imported by the engine, so mayavi is never going to
# start without it. Let us capture the error early, and give a meaningful
# error message
try:
import vtk
except ImportError as m:
msg = '%s\n%s\nDo you have vtk installed properly?\n' \
'VTK (and build instructions) can be obtained from '\
'http://www.vtk.org\n' \
% (m, '_'*80)
raise ImportError(msg)
# Importing here to avoid time-consuming import when user only wanted
# version/help information.
from mayavi.plugins.app import Mayavi, setup_logger
##########################################################################
# `MayaviApp` class
##########################################################################
class MayaviApp(Mayavi):
def parse_command_line(self, argv):
"""Parse command line options."""
# Parse and store command line options to process once app has
# started in `run`.
options, args = parse_cmd_line(argv)
self.cmd_line_opts = (options, args)
# If the verbose option is set, change the log mode.
for opts, args in options:
if opts in ('-v', '--verbose'):
self.log_mode = logging.DEBUG
break
def run(self):
"""Process the command line options and setup mayavi as per
the users needs.
"""
options, args = self.cmd_line_opts
# Process the options.
process_cmd_line(self, options, args)
###############################################################################
# `MayaviOffscreen` class.
###############################################################################
class MayaviOffscreen(MayaviApp):
"""
The mayavi application used for offscreen rendering.
"""
def _script_default(self):
from mayavi.plugins.script import Script
from mayavi.core.off_screen_engine import OffScreenEngine
engine = OffScreenEngine()
engine.start()
s = Script(engine=engine)
return s
def setup_logger(self):
from traits.etsconfig.api import ETSConfig
path = join(ETSConfig.application_data,
'mayavi_e3', 'mayavi.log')
path = abspath(path)
logger = logging.getLogger()
setup_logger(logger, path, mode=self.log_mode)
def main(self, argv=None):
if argv is None:
argv = []
self.parse_command_line(argv)
self.setup_logger()
self.run()
##########################################################################
# Helper functions
##########################################################################
def get_mayavi_script_instance():
"""Return the mayavi Script instance from the first available set of
envisage engines registered in the registry.
"""
from mayavi.core.registry import registry
from mayavi.plugins.envisage_engine import EnvisageEngine
from mayavi.plugins.script import Script
for name, engine in registry.engines.items():
if isinstance(engine, EnvisageEngine):
return engine.window.get_service(Script)
return
def contains_mayavi(namespace):
"""Returns if the given namespace contains a 'mayavi' name bound to
a mayavi script instance.
"""
from mayavi.plugins.script import Script
if 'mayavi' in namespace:
if isinstance(namespace.get('mayavi'), Script):
return True
return False
def standalone(func):
"""A decorator to run a function from within mayavi. This lets
users write a normal Python function and have that run from within
mayavi. It implicitly assumes that the name 'mayavi' refers the the
Script instance and will overwrite it if not.
"""
def wrapper(*args, **kw):
script = get_mayavi_script_instance()
if script is None and mayavi is not None:
script = mayavi.script
if script is None:
def caller(script):
"""Callback that runs the function inside the mayavi
app."""
# Bind the 'mayavi' name to the script instance
func.__globals__['mayavi'] = script
# Run the function in the event loop.
g = script.window.application.gui
g.invoke_later(func, *args, **kw)
# Start up mayavi and invoke caller when the script instance
# is available.
m = Mayavi()
m.on_trait_change(caller, 'script')
# Run the mayavi app.
m.main()
else:
ns = func.__globals__
if not contains_mayavi(ns):
# Bind the 'mayavi' name to the script instance
ns['mayavi'] = script
# Now run the function.
func(*args, **kw)
return wrapper
def main():
"""This starts up the mayavi2 application.
"""
global mayavi
# Make sure '.' is in sys.path
if '' not in sys.path:
sys.path.insert(0, '')
# Start the app.
if OFFSCREEN:
mayavi = MayaviOffscreen()
else:
mayavi = MayaviApp()
mayavi.main(sys.argv[1:])
def close():
""" This closes the mayavi2 application.
"""
if mayavi is not None and not OFFSCREEN:
mayavi.window.close()
if __name__ == '__main__':
main()