a b/app/AnyWriter.py
1
import os
2
import re
3
4
import numpy as np
5
6
7
class AnyWriter:
8
    def __init__(self, template_directory='config/anybody_templates/', output_directory='../output/Anybody/'):
9
        self._template_directory = template_directory
10
        self._output_directory = output_directory
11
        self.mapping = {
12
            'Finger1': {'joint_leap': 'RightHandThumb',
13
                        'joint_any': ['CMCFLEXION', 'CMCABDUCTION', 'CMCDEVIATION', 'MCPFLEXION', 'MCPABDUCTION',
14
                                      'MCPDEVIATION', 'DIPFLEXION', 'DIPABDUCTION', 'DIPDEVIATION'],
15
                        'template': 'Thumb.template',
16
                        'function': ['negative']},
17
            'Finger2': {'joint_leap': 'RightHandIndex',
18
                        'joint_any': ['MCPFLEXION', 'MCPABDUCTION', 'MCPDEVIATION', 'PIPFLEXION', 'PIPABDUCTION',
19
                                      'PIPDEVIATION', 'DIPFLEXION', 'DIPABDUCTION', 'DIPDEVIATION'],
20
                        'template': 'Finger.template',
21
                        'function': ['negative']},
22
            'Finger3': {'joint_leap': 'RightHandMiddle',
23
                        'joint_any': ['MCPFLEXION', 'MCPABDUCTION', 'MCPDEVIATION', 'PIPFLEXION', 'PIPABDUCTION',
24
                                      'PIPDEVIATION', 'DIPFLEXION', 'DIPABDUCTION', 'DIPDEVIATION'],
25
                        'template': 'Finger.template',
26
                        'function': ['negative']},
27
            'Finger4': {'joint_leap': 'RightHandRing',
28
                        'joint_any': ['MCPFLEXION', 'MCPABDUCTION', 'MCPDEVIATION', 'PIPFLEXION', 'PIPABDUCTION',
29
                                      'PIPDEVIATION', 'DIPFLEXION', 'DIPABDUCTION', 'DIPDEVIATION'],
30
                        'template': 'Finger.template',
31
                        'function': ['negative']},
32
            'Finger5': {'joint_leap': 'RightHandPinky',
33
                        'joint_any': ['MCPFLEXION', 'MCPABDUCTION', 'MCPDEVIATION', 'PIPFLEXION', 'PIPABDUCTION',
34
                                      'PIPDEVIATION', 'DIPFLEXION', 'DIPABDUCTION', 'DIPDEVIATION'],
35
                        'template': 'Finger.template',
36
                        'function': ['negative']},
37
            'Wrist': {'joint_leap': 'RightHand',
38
                      'joint_any': ['WRISTFLEXION', 'WRISTABDUCTION', 'WRISTDEVIATION'],
39
                      'template': 'Wrist.template',
40
                      'function': ['negative']},
41
            'Elbow': {'joint_leap': 'RightElbow',
42
                      'joint_any': ['ELBOWFLEXION','ELBOWABDUCTION', 'ELBOWPRONATION'],
43
                      'template': 'Elbow.template',
44
                      'function': ['correct_pronation']}}
45
46
        self.regex_find = re.compile(r'{(((\s*-?\d+\.\d+),?)+)};')
47
        self.regex_replace = re.compile(r'(((\s*-?\d+\.\d+),?)+)')
48
49
    def write(self, data):
50
        self.write_joints(data)
51
        self.write_timeseries(data)
52
        self.write_finger_length(data)
53
54
    def write_joints(self, data):
55
        finger_values = {}
56
57
        for finger_name, joint_mapping in self.mapping.items():
58
            finger_values[finger_name] = {}
59
            for joint_name in joint_mapping['joint_any']:
60
                finger_values[finger_name][joint_name] = np.asarray(
61
                    data.values[joint_mapping['joint_leap']
62
                                + self._joint2channel(finger_name, joint_name)].values)
63
64
        np.set_printoptions(formatter={'float': '{: 0.2f}'.format}, threshold=np.inf)
65
66
        #  finger_values = {'Finger2': {'MCPABDUCTION': [0, 1, 2], 'MCPFLEXION': [0, 1, 2]}, 'Finger3': ...}
67
        for finger_name, joint_mapping in self.mapping.items():
68
            _, finger_number = AnyWriter.split_finger(finger_name)
69
            template_dict = {'FINGERNAME': finger_name,
70
                             'FINGERNUMBER': finger_number}
71
72
            for joint_name in joint_mapping['joint_any']:
73
                # Apply functions for correcting data, if set in mapping (see __init__ method)
74
                joint_values = AnyWriter._apply_function(joint_name, joint_mapping['function'],
75
                                                         finger_values[finger_name][joint_name])
76
                template_dict[joint_name] = self._format2outputarray(joint_values)
77
78
            template_filename = joint_mapping['template']
79
            with open(self._template_directory + template_filename, 'r') as f:
80
                template_string = f.read().format(**template_dict)
81
            with open(self._output_directory + finger_name + '.any', 'w') as f:
82
                f.write(template_string)
83
                print('"{} written"'.format(f.name))
84
85
    def write_timeseries(self, data):
86
        # threshold: workaround for printing more than 1000 values
87
        np.set_printoptions(formatter={'float': '{: 0.5f}'.format}, threshold=np.inf)
88
89
        entries = data.values.shape[0]
90
        template_dict = {'TIMESERIES': self._format2outputarray(np.linspace(0, 1, num=entries))}
91
        template_string = open(self._template_directory + 'TimeSeries.template', 'r').read().format(**template_dict)
92
93
        with open(self._output_directory + 'TimeSeries.any', 'w') as f:
94
            f.write(template_string)
95
            print('"{} written"'.format(os.path.normpath(f.name)))
96
97
    def write_finger_length(self, data):
98
        template_dict = {}
99
        # use offsets value from bvh to scale finger lengths in AnyBody
100
        for joint_name, joint_value in data.skeleton.items():
101
            finger_length = np.linalg.norm(np.array(joint_value['offsets'])) / 1000
102
            template_dict[joint_name] = finger_length
103
104
        # hand length, hand breadth
105
        hand_length = np.linalg.norm(
106
            np.array(data.skeleton['RightHandMiddle1']['offsets']) +
107
            np.array(data.skeleton['RightHandMiddle2']['offsets']) +
108
            np.array(data.skeleton['RightHandMiddle3']['offsets']) +
109
            np.array(data.skeleton['RightHandMiddle4']['offsets']) +
110
            np.array(data.skeleton['RightHandMiddle4_Nub']['offsets'])
111
        ) / 1000
112
        template_dict['HANDLENGTH'] = hand_length
113
114
        # hand breadth from leap motion is too small
115
        # template_dict['HANDBREADTH'] = np.linalg.norm(
116
        #     np.array(data.skeleton['RightHandPinky1']['offsets'] + data.skeleton['RightHandPinky2']['offsets']) -
117
        #     np.array(data.skeleton['RightHandIndex1']['offsets'] + data.skeleton['RightHandIndex2']['offsets'])
118
        # ) / 1000
119
120
        # use scaling factor (hand breadth to hand length) from UZWR standard hand
121
        template_dict['HANDBREADTH'] = hand_length * (0.098 / 0.2)
122
123
        template_string = open(self._template_directory + 'FingerLength.template', 'r').read().format(**template_dict)
124
        with open(self._output_directory + 'FingerLength.any', 'w') as f:
125
            f.write(template_string)
126
            print('"{} written"'.format(os.path.normpath(f.name)))
127
128
    @staticmethod
129
    def _joint2channel(finger_name, joint_name):
130
        thumb = 'Finger1' == finger_name
131
        if joint_name == 'CMCFLEXION':
132
            # Thumb only
133
            return '2_Xrotation'
134
135
        if joint_name == 'CMCABDUCTION':
136
            # CMCABDUCTION is named CMCDEVIATION in Anybody unfortunately
137
            # Thumb only
138
            return '2_Yrotation'
139
140
        if joint_name == 'CMCDEVIATION':
141
            # CMCABDUCTION is named CMCDEVIATION in Anybody unfortunately
142
            # Thumb only
143
            return '2_Zrotation'
144
145
        if joint_name == 'MCPFLEXION':
146
            return '3_Xrotation' if thumb else '2_Xrotation'
147
148
        if joint_name == 'MCPABDUCTION':
149
            # MCPABDUCTION is named MCPDEVIATION in Anybody unfortunately
150
            # for all fingers
151
            return '3_Yrotation' if thumb else '2_Yrotation'
152
153
        if joint_name == 'MCPDEVIATION':
154
            # MCPABDUCTION is named MCPDEVIATION in Anybody unfortunately
155
            # for all fingers
156
            return '3_Zrotation' if thumb else '2_Zrotation'
157
158
        if joint_name == 'PIPFLEXION':
159
            # not used for Thumb
160
            return '3_Xrotation'
161
162
        if joint_name == 'PIPABDUCTION':
163
            # not used for Thumb
164
            return '3_Yrotation'
165
166
        if joint_name == 'PIPDEVIATION':
167
            # not used for Thumb
168
            return '3_Zrotation'
169
170
        if joint_name == 'DIPFLEXION':
171
            # for all fingers
172
            return '4_Xrotation'
173
174
        if joint_name == 'DIPABDUCTION':
175
            # for all fingers
176
            return '4_Yrotation'
177
178
        if joint_name == 'DIPDEVIATION':
179
            # for all fingers
180
            return '4_Zrotation'
181
182
        if joint_name == 'WRISTFLEXION':
183
            # only for wrist
184
            return '_Xrotation'
185
186
        if joint_name == 'WRISTABDUCTION':
187
            # only for wrist
188
            return '_Yrotation'
189
190
        if joint_name == 'WRISTDEVIATION':
191
            # only for wrist
192
            return '_Zrotation'
193
194
        if joint_name == 'ELBOWFLEXION':
195
            # only for elbow
196
            return '_Xrotation'
197
198
        if joint_name == 'ELBOWABDUCTION':
199
            # only for elbow
200
            return '_Yrotation'
201
202
        if joint_name == 'ELBOWPRONATION':
203
            # only for elbow
204
            return '_Zrotation'
205
206
    @staticmethod
207
    def _format2outputarray(joint_values):
208
        return np.array2string(joint_values.astype(float), separator=', ')[1:-1]
209
210
    @staticmethod
211
    def split_finger(finger_name):
212
        finger_split = re.split(r'(\d)', finger_name)
213
        if len(finger_split) == 1:
214
            return finger_name, None
215
        return finger_split[0], int(finger_split[1])
216
217
    @staticmethod
218
    def _apply_function(joint_name, operations, joint_values):
219
        for op in operations:
220
            if op == 'negative':
221
                joint_values = np.negative(joint_values)
222
223
            if op == 'correct_pronation' and joint_name == 'ELBOWPRONATION':
224
                joint_values = 95.0 + joint_values
225
226
        return joint_values
227
228
    def extract_frames(self, start, end):
229
        def prepare_result(x):
230
            np.set_printoptions(formatter={'float': '{: 0.2f}'.format}, threshold=np.inf)
231
            return np.array2string(np.fromstring(x[0], sep=',').astype(float)[start:end], separator=', ')[1:-1]
232
233
        for finger_name in self.mapping:
234
            selected_filepath = self._output_directory + finger_name + '.any'
235
            with open(selected_filepath) as file:
236
                old_file = file.read()
237
                matches = list(map(prepare_result, self.regex_find.findall(old_file)))
238
239
            new_file = re.sub(self.regex_replace, '{{}}', old_file)
240
            # replace single brackets with two, so that they don't get replaced by str.format
241
            new_file = re.sub(r'{\w', r'{\g<0>', new_file)
242
            new_file = re.sub(r'\w}', r'\g<0>}', new_file)
243
244
            with open(selected_filepath, 'w') as file:
245
                file.write(new_file.format(*matches))
246
                if not end:
247
                    end = len(np.fromstring(matches[0], sep=','))
248
                print("Extracted values between frame {} and {} from {}"
249
                      .format(start+1, start+end, os.path.normpath(file.name)))
250
251
    def extract_frame_timeseries(self, start, end):
252
        selected_filepath =self._output_directory + 'TimeSeries.any'
253
        with open(selected_filepath) as file:
254
            old_file = file.read()
255
            match = self.regex_find.findall(old_file)
256
257
        if not end:
258
            end = len(np.fromstring(match[0][0], sep=','))
259
260
        new_file = re.sub(self.regex_replace, '{{}}', old_file)
261
262
        np.set_printoptions(formatter={'float': '{: 0.5f}'.format}, threshold=np.inf)
263
        with open(selected_filepath, 'w') as file:
264
            file.write(new_file.format(np.array2string(
265
                np.linspace(0, 1, num=end-start).astype(float), separator=', ')[1:-1]))
266
            print("Extracted values between frame {} and {} from {}"
267
                  .format(start+1, end, os.path.normpath(file.name)))