a b/app/LeapGui.py
1
import json
2
from multiprocessing import Process
3
import os
4
5
import LeapRecord
6
from AnyPy import AnyPy
7
from config.Configuration import env
8
from BVHAnimation import bvh_animation
9
from resources.pymo.pymo.parsers import BVHParser as Pymo_BVHParser
10
from gooey.gui import application
11
from gooey.gui import processor
12
from gooey.gui.containers import application as containers_application
13
from gooey.python_bindings import gooey_decorator, gooey_parser
14
15
# strings for the actions in the Gooey side-menu
16
ACTION_RECORD = 'Record'
17
ACTION_ANYBODY = 'AnyBody'
18
ACTION_CONVERTER = 'Converter'
19
ACTION_ANIMATION = 'Animation'
20
EXECUTED_COMMAND = 'command'
21
22
23
class GooeyModification:
24
    # overwrite the Gooey default stop method to cancel Leap Motion recording
25
    def on_stop(self):
26
        """Overload the stop method to allow stopping of Leap Motion Recording"""
27
        executed_command = LeapGui.StoredArgs().load().stored_args[EXECUTED_COMMAND]
28
        if executed_command == ACTION_RECORD:
29
            return self.clientRunner.stop_leap()
30
        self.clientRunner.stop()
31
32
    def stop_leap(self):
33
        if not self._process.stdin.closed:
34
            """Send Keyboard Interrupt to stop Leap Motion recording and parse bvh / interpolation"""
35
            self._process.stdin.write(b"\n")
36
            self._process.stdin.close()
37
            return
38
        self.stop()
39
40
    # overwrite classes and methods of the stop button
41
    containers_application.GooeyApplication.onStopExecution = on_stop
42
    processor.ProcessController.stop_leap = stop_leap
43
44
    containers_application.ProcessController = processor.ProcessController
45
    application.GooeyApplication = containers_application.GooeyApplication
46
    gooey_decorator.application = application
47
48
    # create alias
49
    Gooey = gooey_decorator.Gooey
50
    GooeyParser = gooey_parser.GooeyParser
51
52
53
class LeapGui:
54
    """Implementation of the Gooey including some adaptions"""
55
56
    Gooey = GooeyModification.Gooey
57
    GooeyParser = GooeyModification.GooeyParser
58
59
    @staticmethod
60
    @Gooey(program_name="Leap Motion Recorder (c) Robin, Sean",
61
           sidebar_title='Actions',
62
           # return_to_config=True,
63
           image_dir='config/gooey',
64
           tabbed_groups=True,
65
           default_size=(1000, 700))
66
    def parse_args():
67
        """ Use GooeyParser to build up the arguments we will use in our script
68
        Save the arguments in a default json file so that we can retrieve them
69
        every time we run the script.
70
        """
71
72
        # load default values from json (saved from last run)
73
        stored_args = LeapGui.StoredArgs().load()
74
75
        parser = LeapGui.GooeyParser(description='Record Leap Motion data and export to BVH or AnyBody')
76
        env.add_parser(parser)
77
        subs = parser.add_subparsers(help='Tools', dest='command')
78
79
        # === record === #
80
        record_parser = subs.add_parser(ACTION_RECORD, help='Leap Recording')
81
82
        # settings Group
83
        settings_group = record_parser.add_argument_group(
84
            "Settings",
85
            gooey_options={
86
                'show_border': True,
87
                'columns': 1
88
            }
89
        )
90
91
        basis_group = settings_group.add_mutually_exclusive_group(
92
            required=True,
93
            gooey_options={
94
                'initial_selection': 0
95
            })
96
97
        settings_group.add_argument('frames_per_second',
98
                                    metavar='Frames per second',
99
                                    action='store',
100
                                    default=stored_args.get(ACTION_RECORD, 'frames_per_second', '30'),
101
                                    gooey_options={
102
                                        'validator': {
103
                                            'test': '1 <= int(user_input) <= 150',
104
                                            'message': 'Must be between 1 and 150'
105
                                        }
106
                                    }
107
                                    )
108
109
        settings_group.add_argument('-show_animation',
110
                                    metavar='Animate',
111
                                    help='Show motion animation after recording',
112
                                    action='store_true')
113
114
        basis_group.add_argument('-anybody_basis',
115
                                 metavar='Calculate joint angles to AnyBody basis',
116
                                 action='store_true')
117
118
        basis_group.add_argument('-firstframe_basis',
119
                                 metavar='Calculate joint angles to initial recorded basis (first frame)',
120
                                 action='store_true')
121
122
        # bvh Group
123
        bvh_group = record_parser.add_argument_group(
124
            "BVH Export",
125
            gooey_options={
126
                'show_border': True,
127
                'columns': 1
128
            }
129
        )
130
131
        bvh_group.add_argument('-bvh',
132
                               metavar='Write BVH-File',
133
                               action='store_true')
134
135
        bvh_group.add_argument('-bvh_path',
136
                               metavar='BVH File',
137
                               action='store',
138
                               default=stored_args.get(
139
                                   ACTION_RECORD, 'bvh_path', LeapGui.StoredArgs.path('../output/BVH/RightHand.bvh')),
140
                               widget='FileSaver',
141
                               help='Choose location, where to save the BVH File')
142
143
        bvh_group.add_argument('channels',
144
                               metavar='BVH Channels',
145
                               action='store',
146
                               default=stored_args.get(ACTION_RECORD, 'channels', 'rotation'),
147
                               widget='Dropdown',
148
                               help='Rotation: (X,Y,Z) rotation only (default)\n'
149
                                    'Position: (X,Y,Z) rotation and position for all channels',
150
                               choices=['rotation', 'position'],
151
                               gooey_options={
152
                                   'validator': {
153
                                       'test': 'user_input != "Select Option"',
154
                                       'message': 'Choose a channel setting'
155
                                   }
156
                               })
157
158
        # interpol Group
159
        interpol_group = record_parser.add_argument_group(
160
            "Interpolation Vector",
161
            gooey_options={
162
                'show_border': True,
163
                'columns': 1
164
            }
165
        )
166
        interpol_group.add_argument('-anybody',
167
                                    metavar='Write interpolation files for AnyBody',
168
                                    action='store_true')
169
170
        interpol_group.add_argument('-anybody_template_path',
171
                                    metavar='AnyBody templates',
172
                                    action='store',
173
                                    default=stored_args.get(
174
                                        ACTION_RECORD, 'anybody_template_path',
175
                                        LeapGui.StoredArgs.path('config/anybody_templates')),
176
                                    widget='DirChooser',
177
                                    help='Source directory that contains *.template files for AnyBody')
178
179
        interpol_group.add_argument('-anybody_output_path',
180
                                    metavar='Output directory',
181
                                    action='store',
182
                                    default=stored_args.get(
183
                                        ACTION_RECORD, 'anybody_output_path',
184
                                        LeapGui.StoredArgs.path('../output/Anybody')),
185
                                    widget='DirChooser',
186
                                    help='Output directory for interpolation files')
187
188
        # # c3d Group
189
        # c3d_group = record_parser.add_argument_group(
190
        #     "C3D",
191
        #     gooey_options={
192
        #         'show_border': True,
193
        #         'columns': 2
194
        #     }
195
        # )
196
        # c3d_group.add_argument('-c3d',
197
        #                        metavar='Write C3D-File',
198
        #                        action='store_true')
199
        #
200
        # c3d_group.add_argument('-c3d_filename',
201
        #                        metavar=' ',
202
        #                        action='store',
203
        #                        default=stored_args.get(ACTION_RECORD, 'c3d_filename', 'RightHand'),
204
        #                        help='Filename')
205
        #
206
        # c3d_group.add_argument('-c3d_path',
207
        #                        metavar=' ',
208
        #                        action='store',
209
        #                        default=stored_args.get(
210
        #                            ACTION_RECORD, 'c3d_path', LeapGui.StoredArgs.path('../output/C3D')),
211
        #                        widget='DirChooser',
212
        #                        help='Output directory for c3d file')
213
214
        # === anybody === #
215
        anybody_parser = subs.add_parser(ACTION_ANYBODY, help='Anybody Simulation')
216
        anybody_group = anybody_parser.add_argument_group(
217
            "Source files",
218
            "Make a selection for the source files used for the AnyBody analysis",
219
            gooey_options={
220
                'show_border': True,
221
                'columns': 1
222
            }
223
        )
224
        anybody_file_group = anybody_group.add_mutually_exclusive_group(
225
            required=True,
226
            gooey_options={
227
                'initial_selection': 0
228
            })
229
230
        anybody_file_group.add_argument('-any_interpol_files',
231
                                        metavar='Use existing vector files (in {})'.format(AnyPy.INTERPOL_DIR),
232
                                        help='Use interpolation vector files from AnyBody project default directory',
233
                                        action='store_true')
234
235
        anybody_file_group.add_argument('-any_bvh_file',
236
                                        metavar='Source of the *.bvh file',
237
                                        action='store',
238
                                        default=stored_args.get(
239
                                            ACTION_ANYBODY, 'any_bvh_file',
240
                                            LeapGui.StoredArgs.path('../output/BVH/RightHand.bvh')),
241
                                        widget='FileChooser',
242
                                        help='Choose a bvh file to be converted to the interpolation vector files')
243
244
        anybody_file_group.add_argument('-any_files_dir',
245
                                        metavar='Source (.any)',
246
                                        action='store',
247
                                        default=stored_args.get(
248
                                            ACTION_ANYBODY, 'any_files_dir',
249
                                            LeapGui.StoredArgs.path('../output/Anybody')),
250
                                        widget='DirChooser',
251
                                        help='Source directory that contains interpolation *.any files for AnyBody')
252
253
        anybody_group.add_argument('any_main_file',
254
                                   metavar='Source of HAND.Main.any',
255
                                   action='store',
256
                                   default=stored_args.get(ACTION_ANYBODY, 'any_main_file', ''),
257
                                   widget='FileChooser',
258
                                   help='Choose the main AnyBody file for the calculation')
259
260
        anybody_group.add_argument('-start_frame',
261
                                   metavar='Start Frame',
262
                                   help='default: 1',
263
                                   action='store',
264
                                   # default=stored_args.get(ACTION_ANYBODY, 'start_frame', '1'),
265
                                   gooey_options={
266
                                       'validator': {
267
                                           'test': '1 <= int(user_input)',
268
                                           'message': 'Must be greater or equal than 1'
269
                                       }
270
                                   },
271
                                   type=int)
272
273
        anybody_group.add_argument('-end_frame',
274
                                   metavar='End Frame',
275
                                   help='default: end',
276
                                   action='store',
277
                                   # default=stored_args.get(ACTION_ANYBODY, 'end_frame', 'end'),
278
                                   gooey_options={
279
                                       'validator': {
280
                                           'test': '("end" in user_input.lower()) or (1 <= int(user_input))',
281
                                           'message': 'Must be a positive value and greater than the start frame'
282
                                       }
283
                                   })
284
285
        operation_group = anybody_parser.add_argument_group(
286
            "Operations",
287
            "Select which operations should be executed by AnyBody",
288
            gooey_options={
289
                'show_border': True,
290
                'columns': 1
291
            }
292
        )
293
294
        operation_group.add_argument('-load',
295
                                     metavar='Load AnyBody model',
296
                                     action='store_true')
297
298
        operation_group.add_argument('-initial_conditions',
299
                                     metavar='Calc initial conditions',
300
                                     action='store_true')
301
302
        operation_group.add_argument('-kinematic',
303
                                     metavar='Calc kinematic analysis',
304
                                     action='store_true')
305
306
        operation_group.add_argument('-inverse_dynamics',
307
                                     metavar='Calc inverse dynamics',
308
                                     action='store_true')
309
310
        operation_group.add_argument('-nstep',
311
                                     metavar='Time steps',
312
                                     help='Number of equally spaced time steps\n'
313
                                          '(leave empty for not changing the setting)',
314
                                     action='store',
315
                                     # default=stored_args.get(ACTION_ANYBODY, 'nstep', '50'),
316
                                     gooey_options={
317
                                         'validator': {
318
                                             'test': '1 <= int(user_input)',
319
                                             'message': 'Must be greater or equal than 1'
320
                                         }
321
                                     },
322
                                     type=int)
323
324
        # operation_group.add_argument('-order',
325
        #                              metavar='Order of B-spline interpolation',
326
        #                              help='Interpolates between the data points with a B-spline using this order'
327
        #                                   '\n(leave empty for using the default value)',
328
        #                              action='store',
329
        #                              # default=stored_args.get(ACTION_ANYBODY, 'order', '4'),
330
        #                              gooey_options={
331
        #                                  'validator': {
332
        #                                      'test': '1 <= int(user_input)',
333
        #                                      'message': 'Must be greater or equal than 1'
334
        #                                  }
335
        #                              },
336
        #                              type=int)
337
338
        result_group = anybody_parser.add_argument_group(
339
            "Results",
340
            gooey_options={
341
                'show_border': True,
342
                'columns': 1
343
            }
344
        )
345
346
        result_group.add_argument('-plot',
347
                                  metavar='Open the plot after the analysis',
348
                                  action='store_true')
349
350
        result_group.add_argument('-output_file_path',
351
                                  metavar='Save .anydata.h5 file',
352
                                  action='store',
353
                                  default=stored_args.get(
354
                                      ACTION_ANYBODY, 'output_file_path',
355
                                      LeapGui.StoredArgs.path('../output/Anybody/FreeHand.anydata.h5')),
356
                                  widget='FileSaver',
357
                                  help='Save .anydata.h5 file to save the analysis results')
358
359
        result_group.add_argument('-replay_output',
360
                                  metavar='Open AnyBody and load the results',
361
                                  action='store_true')
362
363
        # === converter === #
364
        converter_parser = subs.add_parser(ACTION_CONVERTER, help='Convert a BVH-File in .any-Files')
365
        converter_group = converter_parser.add_argument_group(
366
            "Converter",
367
            "Convert a BVH-File in .any-Files",
368
            gooey_options={
369
                'show_border': True,
370
                'columns': 1
371
            }
372
        )
373
374
        converter_group.add_argument('bvh_file',
375
                                     metavar='Source: *.bvh',
376
                                     action='store',
377
                                     default=stored_args.get(
378
                                         ACTION_CONVERTER, 'bvh_file',
379
                                         LeapGui.StoredArgs.path('../output/BVH/RightHand.bvh')),
380
                                     widget='FileChooser',
381
                                     help='Source bvh-file to convert')
382
383
        # converter_group.add_argument('-any_file',
384
        #                              metavar='Convert to .any files',
385
        #                              action='store_true')
386
387
        # converter_group.add_argument('-c3d',
388
        #                              metavar='Convert to .c3d files',
389
        #                              action='store_true')
390
391
        converter_group.add_argument('file_dir',
392
                                     metavar='Target: store files',
393
                                     action='store',
394
                                     default=stored_args.get(
395
                                         ACTION_CONVERTER, 'file_dir', LeapGui.StoredArgs.path('../output')),
396
                                     widget='DirChooser',
397
                                     help='Directory to store the converted files')
398
399
        # === bvh animation === #
400
        animation_parser = subs.add_parser(ACTION_ANIMATION, help='Show an animation for a BVH file')
401
        animation_group = animation_parser.add_argument_group(
402
            "Animation",
403
            "Select a BVH file to be animated",
404
            gooey_options={
405
                'show_border': True,
406
                'columns': 1
407
            }
408
        )
409
410
        animation_group.add_argument('bvh_animation',
411
                                     metavar='BVH file path',
412
                                     action='store',
413
                                     default=stored_args.get(
414
                                         ACTION_ANIMATION, 'bvh_animation',
415
                                         LeapGui.StoredArgs.path('../output/BVH/RightHand.bvh')),
416
                                     widget='FileChooser')
417
418
        # start the UI and save arguments to json for next run
419
        stored_args.save(parser.parse_args())
420
421
    class StoredArgs:
422
        """class for loading and saving arguments from/to json, also to handle default values"""
423
424
        def __init__(self):
425
            self.stored_args = {}
426
            self.loaded_actions = {}
427
            # get the script name without the extension & use it to build up the json filename
428
            script_name = os.path.splitext(os.path.basename(__file__))[0]
429
            self.args_file = "{}-args.json".format(script_name)
430
431
        def load(self):
432
            # Read in the prior arguments as a dictionary
433
            if os.path.isfile(self.args_file):
434
                with open(self.args_file) as data_file:
435
                    self.stored_args = json.load(data_file)
436
                    self.loaded_actions = self.stored_args.keys()
437
            return self
438
439
        def get(self, action, arg, default):
440
            return self.stored_args[action].get(arg) if action in self.loaded_actions else default
441
442
        @staticmethod
443
        def path(relative_path):
444
            return os.path.normpath(os.path.join(os.getcwd(), relative_path))
445
446
        def save(self, args):
447
            # Store the values of the arguments to the environment to access them in the code
448
            env.save_arguments(args)
449
            # Store the values of the arguments so we have them next time we run
450
            with open(self.args_file, 'w') as data_file:
451
                # Using vars(args) returns the data as a dictionary
452
                self.stored_args[EXECUTED_COMMAND] = args.command
453
                self.stored_args[args.command] = vars(args)
454
                json.dump(self.stored_args, data_file)
455
456
    @staticmethod
457
    def run():
458
        """Open the Gooey GUI and then run the selected action with the chosen arguments"""
459
        LeapGui.parse_args()
460
461
        # Record, Anybody, Converter
462
        if env.config.command == ACTION_RECORD:
463
            from GuiControl import GuiControl
464
            gui = GuiControl()
465
            gui.set_windows_record()
466
467
            import time
468
            countdown = 5
469
            for ii in range(countdown):
470
                print("Record starting in {} seconds ...".format(countdown-ii))
471
                time.sleep(1)
472
473
            LeapRecord.start_recording()
474
475
            gui.end_record()
476
            print("End of recording\n")
477
478
            if env.config.show_animation:
479
                print("Loading the animation ...")
480
                p = Process(target=bvh_animation.animate)
481
                p.start()
482
                # wait for bvh_animation to be closed
483
                p.join()
484
            return True
485
486
        if env.config.command == ACTION_ANYBODY:
487
            from LogWatcher import log_watcher
488
            anypy = AnyPy(env.config.any_main_file, env.config.any_files_dir)
489
            log_watcher.start(os.path.join(anypy.any_path, anypy.LOG_FILE))
490
            run_status = anypy.run()
491
            log_watcher.stop()
492
            if not run_status:
493
                # AnyBody operations were not successful
494
                return False
495
            if env.config.replay_output:
496
                anypy.post_operations()
497
            if env.config.plot:
498
                anypy.plot()
499
            return True
500
501
        if env.config.command == ACTION_CONVERTER:
502
            from AnyWriter import AnyWriter
503
            any_writer = AnyWriter(template_directory='config/anybody_templates/',
504
                                   output_directory=env.config.file_dir + '/')
505
            any_writer.write(Pymo_BVHParser().parse(env.config.bvh_file))
506
            return True
507
508
        if env.config.command == ACTION_ANIMATION:
509
            print("Loading the animation ...")
510
            bvh_animation.bvh_data = Pymo_BVHParser().parse(env.config.bvh_animation)
511
            bvh_animation.animate()
512
513
514
if __name__ == "__main__":
515
    LeapGui.run()