multimodalart HF Staff commited on
Commit
89d702d
·
verified ·
1 Parent(s): c02be09

Upload folder using huggingface_hub

Browse files
toolkit/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Minimal toolkit namespace used by the SWD-H stage1 release."""
toolkit/globals.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ emos_mer = ['neutral', 'angry', 'happy', 'sad', 'worried', 'surprise']
2
+ emo2idx_mer = {emo: idx for idx, emo in enumerate(emos_mer)}
3
+ idx2emo_mer = {idx: emo for idx, emo in enumerate(emos_mer)}
toolkit/utils/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Utility helpers for dataset IO, parsing, and vLLM-based scoring."""
toolkit/utils/chatgpt.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """No-network stubs kept only for compatibility with legacy imports.
2
+
3
+ The open-source stage1 package does not use OpenAI or other hosted GPT APIs.
4
+ Use local Hugging Face/vLLM models for evaluation label extraction.
5
+ """
6
+
7
+
8
+ def _removed_api(*args, **kwargs):
9
+ raise RuntimeError(
10
+ "Hosted GPT API helpers were removed from the SWD-H stage1 open-source package."
11
+ )
12
+
13
+
14
+ func_get_completion = _removed_api
15
+ get_completion = _removed_api
16
+ get_translate_eng2chi = _removed_api
17
+ get_translate_chi2eng = _removed_api
18
+ get_image_emotion_batch = _removed_api
19
+ get_video_emotion_batch = _removed_api
20
+ get_text_emotion_batch = _removed_api
21
+ get_multi_emotion_batch = _removed_api
22
+ get_evoke_emotion_batch = _removed_api
23
+ get_micro_emotion_batch = _removed_api
24
+ get_different_format = _removed_api
toolkit/utils/functions.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import math
4
+
5
+ import cv2
6
+ import numpy as np
7
+ import pandas as pd
8
+ import torchaudio
9
+ from PIL import Image
10
+
11
+
12
+ def string_to_list(value):
13
+ if isinstance(value, np.ndarray):
14
+ value = value.tolist()
15
+ if isinstance(value, list):
16
+ return value
17
+ if value == '' or pd.isna(value):
18
+ return []
19
+
20
+ value = str(value).strip()
21
+ if value.startswith('['):
22
+ value = value[1:]
23
+ if value.endswith(']'):
24
+ value = value[:-1]
25
+ return [item.strip() for item in re.split('[\'\",]', value)
26
+ if item.strip() not in ['', ',']]
27
+
28
+
29
+ def func_gain_videopath(video_root, vid_name):
30
+ for suffix in ('.mp4', '.avi'):
31
+ candidate = f"{video_root}/{vid_name}{suffix}"
32
+ if os.path.exists(candidate):
33
+ return candidate
34
+ return f"{video_root}/{vid_name}.mp4"
35
+
36
+
37
+ def func_gain_audiopath(video_root, vid_name):
38
+ return f"{video_root}/{vid_name}.wav"
39
+
40
+
41
+ def func_gain_name2trans(trans_path):
42
+ from toolkit.utils.read_files import func_read_key_from_csv
43
+
44
+ names = func_read_key_from_csv(trans_path, 'name')
45
+ chis = func_read_key_from_csv(trans_path, 'chinese')
46
+ return {name: chi for name, chi in zip(names, chis)}
47
+
48
+
49
+ def func_read_audio_second(audio_path):
50
+ waveform, sr = torchaudio.load(audio_path)
51
+ if len(waveform.shape) == 2:
52
+ return waveform.shape[1] / sr
53
+ if len(waveform.shape) == 1:
54
+ return len(waveform) / sr
55
+ raise ValueError('Unsupported waveform shape')
56
+
57
+
58
+ def func_opencv_to_image(img):
59
+ return Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
60
+
61
+
62
+ def func_decord_to_image(img):
63
+ return Image.fromarray(img)
64
+
65
+
66
+ def func_opencv_to_decord(img):
67
+ return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
68
+
69
+
70
+ def func_discrte_label_distribution(labels):
71
+ unique, counts = np.unique(labels, return_counts=True)
72
+ return dict(zip(unique.tolist(), counts.tolist()))
73
+
74
+
75
+ def func_label_distribution(labels):
76
+ return func_discrte_label_distribution(labels)
77
+
78
+
79
+ def split_list_into_batch(items, split_num=None, batchsize=None):
80
+ """Split a list into non-empty batches while preserving item order."""
81
+ if split_num is None and batchsize is None:
82
+ raise ValueError("Either split_num or batchsize must be provided.")
83
+ if batchsize is not None and batchsize <= 0:
84
+ raise ValueError("batchsize must be positive.")
85
+ if split_num is not None and split_num <= 0:
86
+ raise ValueError("split_num must be positive.")
87
+ if len(items) == 0:
88
+ return []
89
+
90
+ if split_num is None:
91
+ split_num = math.ceil(len(items) / batchsize)
92
+
93
+ batches = []
94
+ each_split = math.ceil(len(items) / split_num)
95
+ for idx in range(split_num):
96
+ batch = items[idx * each_split:(idx + 1) * each_split]
97
+ if batch:
98
+ batches.append(batch)
99
+
100
+ return batches
toolkit/utils/qwen.py ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import cv2
4
+ import math
5
+ import time
6
+ import tqdm
7
+ import glob
8
+ import base64
9
+ import numpy as np
10
+
11
+
12
+ # ====================================================== #
13
+ ############ 模型基本调用策略 ############
14
+ # ====================================================== #
15
+ def func_postprocess_qwen(response):
16
+ response = response.strip()
17
+ if response.startswith("输入"): response = response[len("输入"):]
18
+ if response.startswith("输出"): response = response[len("输出"):]
19
+ if response.startswith("翻译"): response = response[len("翻译"):]
20
+ if response.startswith("让我们来翻译一下:"): response = response[len("让我们来翻译一下:"):]
21
+ if response.startswith("output"): response = response[len("output"):]
22
+ if response.startswith("Output"): response = response[len("Output"):]
23
+ if response.startswith("input"): response = response[len("input"):]
24
+ if response.startswith("Input"): response = response[len("Input"):]
25
+ response = response.strip()
26
+ if response.startswith(":"): response = response[len(":"):]
27
+ if response.startswith(":"): response = response[len(":"):]
28
+ response = response.strip()
29
+ response = response.replace('\n', '') # remove \n
30
+ response = response.strip()
31
+ return response
32
+
33
+
34
+ # 采用qwen完成接口调用:同时支持 prompt 或者 prompt_list
35
+ def get_completion_qwen(model, tokenizer, prompt):
36
+
37
+ assert isinstance(prompt, str)
38
+ messages = [
39
+ {"role": "system", "content": "You are a helpful assistant."},
40
+ {"role": "user", "content": prompt}
41
+ ]
42
+
43
+ text = tokenizer.apply_chat_template(
44
+ messages,
45
+ tokenize=False,
46
+ add_generation_prompt=True
47
+ )
48
+ model_inputs = tokenizer([text], return_tensors="pt").to("cuda")
49
+
50
+ generated_ids = model.generate(
51
+ model_inputs.input_ids,
52
+ max_new_tokens=512
53
+ )
54
+ generated_ids = [
55
+ output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
56
+ ]
57
+
58
+ response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
59
+ response = func_postprocess_qwen(response)
60
+ print(f"Prompt: {prompt} \n Response: {response}")
61
+ return response
62
+
63
+
64
+ # 依赖于 vllm
65
+ def get_completion_qwen_bacth(llm, sampling_params, tokenizer, prompt_list):
66
+
67
+ assert isinstance(prompt_list, list)
68
+
69
+ message_batch = []
70
+ for prompt in prompt_list:
71
+ message_batch.append([{"role": "user", "content": prompt}])
72
+
73
+ text_batch = tokenizer.apply_chat_template(
74
+ message_batch,
75
+ tokenize=False,
76
+ add_generation_prompt=True,
77
+ )
78
+
79
+ outputs = llm.generate(text_batch, sampling_params)
80
+
81
+ # => batch_responses
82
+ batch_responses = []
83
+ for output in outputs:
84
+ prompt = output.prompt
85
+ response = output.outputs[0].text
86
+ response = func_postprocess_qwen(response)
87
+ batch_responses.append(response)
88
+ print(f"Prompt: {prompt} \n Response: {response}")
89
+ return batch_responses
90
+
91
+
92
+
93
+ # ========================= #
94
+ ## 基本操作:翻译 ##
95
+ # ========================= #
96
+ def translate_chi2eng_qwen(model=None, tokenizer=None, llm=None, sampling_params=None, reason=None, batch_reasons=None):
97
+
98
+ def func_prompt_template(reason):
99
+ prompt = f"""Please translate the Chinese input into English. Please ensure the translated results does not contain any Chinese words.
100
+ Input: 高兴; Output: happy \
101
+ Input: 生气; Output: angry \
102
+ Input: {reason}; Output: """
103
+ return prompt
104
+
105
+ ## process for reason
106
+ if reason is not None:
107
+ prompt = func_prompt_template(reason)
108
+ response = get_completion_qwen(model, tokenizer, prompt)
109
+ return response
110
+
111
+ ## process for reason_list
112
+ if batch_reasons is not None:
113
+ prompt_list = []
114
+ for reason in batch_reasons:
115
+ prompt = func_prompt_template(reason)
116
+ prompt_list.append(prompt)
117
+ response_list = get_completion_qwen_bacth(llm, sampling_params, tokenizer, prompt_list)
118
+ return response_list
119
+
120
+
121
+ def translate_eng2chi_qwen(model=None, tokenizer=None, llm=None, sampling_params=None, reason=None, batch_reasons=None):
122
+
123
+ def func_prompt_template(reason):
124
+ prompt = f"""Please translate the English input into Chinese.
125
+ Input: happy; Output: 高兴 \
126
+ Input: angry; Output: 生气 \
127
+ Input: {reason}; Output: """
128
+ return prompt
129
+
130
+ ## process for reason
131
+ if reason is not None:
132
+ prompt = func_prompt_template(reason)
133
+ response = get_completion_qwen(model, tokenizer, prompt)
134
+ return response
135
+
136
+ ## process for reason_list
137
+ if batch_reasons is not None:
138
+ prompt_list = []
139
+ for reason in batch_reasons:
140
+ prompt = func_prompt_template(reason)
141
+ prompt_list.append(prompt)
142
+ response_list = get_completion_qwen_bacth(llm, sampling_params, tokenizer, prompt_list)
143
+ return response_list
144
+
145
+
146
+
147
+
148
+ # ========================== #
149
+ ## reason merging ##
150
+ # ========================== #
151
+ def reason_merge_qwen(model=None, tokenizer=None, llm=None, sampling_params=None,
152
+ reason=None, subtitle=None, batch_reasons=None, batch_subtitles=None):
153
+
154
+ def func_prompt_template(reason, subtitle):
155
+
156
+ assert subtitle != "", 'Error: subtitle cannot be empty.'
157
+
158
+ if reason != '':
159
+ reason_merge = ""
160
+ reason_merge += f"Clue: {reason};"
161
+ reason_merge += f"Subtitle: {subtitle}"
162
+ prompt = f"Please assume the role of an expert in the field of emotions. \
163
+ We have provided clues from the video that may be related to the characters' emotional states. \
164
+ In addition, we have also provided the subtitle content of the video. \
165
+ Please merge all these information to infer the emotional states of the characters, and provide reasoning for your inferences. \
166
+ Input: {reason_merge}\
167
+ Output:"
168
+ else:
169
+ reason_merge = ""
170
+ reason_merge += f"Subtitle: {subtitle}"
171
+ prompt = f"Please assume the role of an expert in the field of emotions.\
172
+ We have provided the subtitle content of the video.\
173
+ Please infer the emotional states of the characters, and provide reasoning process for your inferences.\
174
+ Input: {reason_merge}\
175
+ Output:"
176
+ return prompt
177
+
178
+ ## process for reason
179
+ if reason is not None:
180
+ prompt = func_prompt_template(reason, subtitle)
181
+ response = get_completion_qwen(model, tokenizer, prompt)
182
+ return response
183
+
184
+ ## process for reason_list
185
+ if batch_reasons is not None:
186
+ prompt_list = []
187
+ for reason, subtitle in zip(batch_reasons, batch_subtitles):
188
+ prompt = func_prompt_template(reason, subtitle)
189
+ prompt_list.append(prompt)
190
+ response_list = get_completion_qwen_bacth(llm, sampling_params, tokenizer, prompt_list)
191
+ return response_list
192
+
193
+
194
+
195
+ ############################################################################
196
+ ############################################################################
197
+ ############################################################################
198
+ ## 后面这些都是跟标签和评价相关的部分
199
+
200
+ # ====================================================== #
201
+ ## reason -> (onehot, rank, openset, valence) ##
202
+ # ====================================================== #
203
+ def reason_to_onehot_qwen(model=None, tokenizer=None, llm=None, sampling_params=None, reason=None, batch_reasons=None):
204
+
205
+ # 1. 给 few-shot 的结果看起来更合理一些 => 至少格式看着正确一些
206
+ # 2. 增加一个相对复杂的shot看看结果
207
+ # 3. 再次强调看看呢? => 依旧是很多输出 mix 的结果,说明模型的指令追随能力一般般
208
+ def func_prompt_template(reason):
209
+ prompt = f"""Please act as an expert in the field of emotions. \
210
+ We provide clues that related to the character's emotions. Based on the provided clues, please identify the emotional states of the main character. \
211
+ The main character is the one with the most detailed clues. \
212
+ Please select one of the following emotion labels that best matches the given clues: [happy, angry, worried, sad, surprise, neutral]. \
213
+ We would like to emphasize that please must only output one label from the above candidates: [happy, angry, worried, sad, surprise, neutral]. You cannot output label outside these candidates, like mixed, happiness. \
214
+ Input: We cannot recognize his emotional state; Output: neutral \
215
+ Input: His emotional state is joyful, happiness, anger; Output: happy \
216
+ Input: While the woman in the video appears to be in a positive emotional state, the audio suggests that the speaker might be experiencing anxiety or nervousness, particularly when discussing the shopping card; Output: worried \
217
+ Input: The character likely experiences a range of positive emotions including excitement, enthusiasm, and confidence. They might feel motivated and inspired by the topic they are discussing, demonstrating a high level of engagement and investment; Output: happy \
218
+ Input: {reason}; Output: """
219
+ return prompt
220
+
221
+ # 标签后处理: 删除结尾处的 “句号”
222
+ def func_onehot_label_polish(onehot):
223
+ onehot = onehot.split('.')[0]
224
+ return onehot
225
+
226
+ ## process for reason
227
+ if reason is not None:
228
+ prompt = func_prompt_template(reason)
229
+ response = get_completion_qwen(model, tokenizer, prompt)
230
+ response = func_onehot_label_polish(response)
231
+ return response
232
+
233
+ ## process for reason_list
234
+ if batch_reasons is not None:
235
+ prompt_list = []
236
+ for reason in batch_reasons:
237
+ prompt = func_prompt_template(reason)
238
+ prompt_list.append(prompt)
239
+ response_list = get_completion_qwen_bacth(llm, sampling_params, tokenizer, prompt_list)
240
+ for ii, response in enumerate(response_list):
241
+ response_list[ii] = func_onehot_label_polish(response)
242
+ return response_list
243
+
244
+ def reason_to_rank_qwen(model=None, tokenizer=None, llm=None, sampling_params=None, reason=None, batch_reasons=None):
245
+
246
+ def func_prompt_template(reason):
247
+ prompt = f"""Please assume the role of an expert in the emotional domain. We provide clues that may be related to the emotions of the character. \
248
+ Based on the provided clues, identify the emotional states of the main character. \
249
+ We provide a set of emotional candidates, please rank them in order of likelihood from high to low. \
250
+ The candidate set is [happy, angry, worried, sad, surprise, neutral]. Please directly output the ranking results. \
251
+ Input: {reason}; Output: """
252
+ return prompt
253
+
254
+ ## process for reason
255
+ if reason is not None:
256
+ prompt = func_prompt_template(reason)
257
+ response = get_completion_qwen(model, tokenizer, prompt)
258
+ return response
259
+
260
+ ## process for reason_list
261
+ if batch_reasons is not None:
262
+ prompt_list = []
263
+ for reason in batch_reasons:
264
+ prompt = func_prompt_template(reason)
265
+ prompt_list.append(prompt)
266
+ response_list = get_completion_qwen_bacth(llm, sampling_params, tokenizer, prompt_list)
267
+ return response_list
268
+
269
+
270
+ def reason_to_openset_qwen(model=None, tokenizer=None, llm=None, sampling_params=None, reason=None, batch_reasons=None):
271
+
272
+ def func_prompt_template(reason):
273
+ prompt = f"""Please assume the role of an expert in the field of emotions. \
274
+ We provide clues that may be related to the emotions of the characters. Based on the provided clues, please identify the emotional states of the main character. \
275
+ The main character is the one with the most detailed clues. \
276
+ Please separate different emotional categories with commas and output only the clearly identifiable emotional categories in a list format. \
277
+ If none are identified, please output an empty list. \
278
+ Input: We cannot recognize his emotional state; Output: [] \
279
+ Input: His emotional state is happy, sad, and angry; Output: [happy, sad, angry] \
280
+ Input: {reason}; Output: """
281
+ return prompt
282
+
283
+ ## process for reason
284
+ if reason is not None:
285
+ prompt = func_prompt_template(reason)
286
+ response = get_completion_qwen(model, tokenizer, prompt)
287
+ return response
288
+
289
+ ## process for reason_list
290
+ if batch_reasons is not None:
291
+ prompt_list = []
292
+ for reason in batch_reasons:
293
+ prompt = func_prompt_template(reason)
294
+ prompt_list.append(prompt)
295
+ response_list = get_completion_qwen_bacth(llm, sampling_params, tokenizer, prompt_list)
296
+ return response_list
297
+
298
+
299
+
300
+ def reason_to_valence_qwen(model=None, tokenizer=None, llm=None, sampling_params=None, reason=None, batch_reasons=None):
301
+
302
+ def func_prompt_template(reason):
303
+ prompt = f"""Please identify the overall positive or negative emotional polarity of the main characters. \
304
+ The output should be a floating-point number ranging from -1 to 1. \
305
+ Here, -1 indicates extremely negative emotions, 0 indicates neutral emotions, and 1 indicates extremely positive emotions. \
306
+ Please provide your judgment as a floating-point number. \
307
+ Input: I am very happy; Output: 1 \
308
+ Input: I am very angry; Output: -1 \
309
+ Input: I am neutral; Output: 0 \
310
+ Input: {reason}; Output: """
311
+ return prompt
312
+
313
+ ## process for reason
314
+ if reason is not None:
315
+ prompt = func_prompt_template(reason)
316
+ response = get_completion_qwen(model, tokenizer, prompt)
317
+ return response
318
+
319
+ ## process for reason_list
320
+ if batch_reasons is not None:
321
+ prompt_list = []
322
+ for reason in batch_reasons:
323
+ prompt = func_prompt_template(reason)
324
+ prompt_list.append(prompt)
325
+ response_list = get_completion_qwen_bacth(llm, sampling_params, tokenizer, prompt_list)
326
+ return response_list
327
+
328
+
329
+
330
+ # ========================================== #
331
+ ## openset -> (onehot, sentiment) ##
332
+ # ========================================== #
333
+ def openset_to_onehot_qwen(model=None, tokenizer=None, llm=None, sampling_params=None, reason=None, batch_reasons=None):
334
+ def func_prompt_template(reason):
335
+ prompt = f"""Please act as an expert in the field of emotions. \
336
+ We provide a few words to describe the emotions of a character. \
337
+ Please choose the emotion label from the following list that is closest to the given words: happy, angry, worried, sad, surprise, neutral.
338
+ Input: [joyful]; Output: happy \
339
+ Input: []; Output: neutral \
340
+ Input: {reason}; Output: """
341
+ return prompt
342
+
343
+ ## process for reason
344
+ if reason is not None:
345
+ prompt = func_prompt_template(reason)
346
+ response = get_completion_qwen(model, tokenizer, prompt)
347
+ return response
348
+
349
+ ## process for reason_list
350
+ if batch_reasons is not None:
351
+ prompt_list = []
352
+ for reason in batch_reasons:
353
+ prompt = func_prompt_template(reason)
354
+ prompt_list.append(prompt)
355
+ response_list = get_completion_qwen_bacth(llm, sampling_params, tokenizer, prompt_list)
356
+ return response_list
357
+
358
+ def openset_to_sentiment_qwen(model=None, tokenizer=None, llm=None, sampling_params=None, reason=None, batch_reasons=None):
359
+ def func_prompt_template(reason):
360
+ prompt = f"""Please act as an expert in the field of emotions. \
361
+ We provide a few words to describe the emotions of a character. \
362
+ Please choose the most likely sentiment from the given candidates: [positive, negative, neutral] \
363
+ Please direct output answer without analyzing process. \
364
+ Input: [joyful]; Output: positive \
365
+ Input: []; Output: neutral \
366
+ Input: {reason}; Output: """
367
+ return prompt
368
+
369
+ ## process for reason
370
+ if reason is not None:
371
+ prompt = func_prompt_template(reason)
372
+ response = get_completion_qwen(model, tokenizer, prompt)
373
+ return response
374
+
375
+ ## process for reason_list
376
+ if batch_reasons is not None:
377
+ prompt_list = []
378
+ for reason in batch_reasons:
379
+ prompt = func_prompt_template(reason)
380
+ prompt_list.append(prompt)
381
+ response_list = get_completion_qwen_bacth(llm, sampling_params, tokenizer, prompt_list)
382
+ return response_list
383
+
384
+
toolkit/utils/read_files.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import math
4
+ import random
5
+ import numpy as np
6
+ import pandas as pd
7
+ import tqdm
8
+
9
+ ## read pkl
10
+ # videoIDs, videoSpeakers, videoLabels, videoText, videoAudio, videoVisual1, videoSentence, trainVid, \
11
+ # testVid = pickle.load(open(pkl_path, "rb"), encoding='latin1')
12
+
13
+ ## write pkl
14
+ # pickle.dump([videoIDs, videoSpeakers, videoLabelsNew, videoTextNew, videoAudioNew, videoVisualNew, videoSentence, trainVid, \
15
+ # testVid], open(save_path, 'wb'))
16
+
17
+ ## read txt
18
+ # with open(output_path, encoding='utf8') as f: lines = [line.strip() for line in f]
19
+ # lines = [line for line in lines if len(line)!=0]
20
+
21
+ ## write txt
22
+ # file_object = open('thefile.txt', 'w')
23
+ # file_object.write(all_the_text)
24
+ # file_object.close()
25
+
26
+ ## read csv file
27
+ # df_label = pd.read_csv(label_file)
28
+ # meta_columns = ['timestamp', 'segment_id']
29
+ # metas = df_label[meta_columns].values # change to numpy
30
+ # label_timestamps = metas[:,0]
31
+ # df = pd.concat(segment_dfs) ## concat different csv files
32
+ # for _, row in df.iterrows(): ## read for each row
33
+ # word = row['word']
34
+
35
+ ## write csv file
36
+ # meta_columns = ['timestamp', 'segment_id']
37
+ # columns = meta_columns + [str(i) for i in range(embedding_dim)] # x,x,0,1,2,3,4,5,...
38
+ # data = np.column_stack([metas, aligned_embeddings])
39
+ # df = pd.DataFrame(data=data, columns=columns)
40
+ # df[meta_columns] = df[meta_columns].astype(np.int64)
41
+ # df.to_csv(csv_file, index=False)
42
+
43
+ ## read json
44
+ # with open("../config/record.json",'r') as load_f:
45
+ # load_dict = json.load(load_f)
46
+
47
+ ## write json
48
+ # with open("../config/record.json","w") as f:
49
+ # json.dump(new_dict,f)
50
+
51
+
52
+
53
+ # 功能1:只支持一个keyname
54
+ def func_labelstudio_init_key(keyname, names, values, save_path=""):
55
+ whole_json = []
56
+ for ii, name in enumerate(names):
57
+ # s3_path = f's3://zeroqiaoba-first/video3/{name}.webm' # case1 [ok]
58
+ # s3_path = f's3://zeroqiaoba/video5/{name}.webm'
59
+ # s3_path = f's3://zeroqiaoba-first\\video3\\{name}.webm' # case2 [unwork]
60
+ s3_path = f'/data/local-files/?d=video_webm/{name}.webm' # local storage
61
+ onefile_json = {}
62
+ onefile_json['id'] = ii
63
+ onefile_json['data'] = {}
64
+ onefile_json['data']['video'] = s3_path
65
+ onefile_json['data'][keyname] = values[ii]
66
+ onefile_json['annotations'] = []
67
+ onefile_json['predictions'] = []
68
+ whole_json.append(onefile_json)
69
+ ## save whole_json
70
+ with open(save_path, "w") as f:
71
+ json.dump(whole_json, f)
72
+ return whole_json
73
+
74
+
75
+ # 功能1:给一个json文件增加一个key
76
+ def func_labelstudio_update_key(json_path, val_name, name2val):
77
+ with open(json_path, 'r', encoding='utf-8') as f:
78
+ data = json.load(f)
79
+
80
+ for item in data:
81
+ video = item['data']['video']
82
+ videoname = os.path.basename(video).rsplit('.', 1)[0] # 对于 case1 [ok]
83
+ # videoname = video.split('\\')[-1].rsplit('.', 1)[0] # case2 [unwork]
84
+ item['data'][val_name] = name2val[videoname]
85
+
86
+ with open(json_path, "w") as f:
87
+ json.dump(data, f)
88
+
89
+
90
+ # 功能:将一个json分割到多个json,并存储在store_root中
91
+ def func_labelstudio_split_json(json_path, store_root, split_num=8, shuffle=True):
92
+ if not os.path.exists(store_root):
93
+ os.makedirs(store_root)
94
+
95
+ with open(json_path, 'r', encoding='utf-8') as f:
96
+ data = json.load(f)
97
+
98
+ if shuffle:
99
+ data = func_shuffle_list_data(data)
100
+
101
+ subset_number = math.ceil(len(data)/split_num)
102
+ for ii in range(split_num):
103
+ sub_data = data[ii*subset_number:(ii+1)*subset_number]
104
+
105
+ save_path = os.path.join(store_root, f'split-{ii}.json')
106
+ with open(save_path, "w") as f:
107
+ json.dump(sub_data, f)
108
+
109
+ # 功能:将一个list文件分成多份,存储在store_root中
110
+ def func_split_list_data(data, store_root, split_num=8, shuffle=True):
111
+ if not os.path.exists(store_root):
112
+ os.makedirs(store_root)
113
+
114
+ if shuffle:
115
+ data = func_shuffle_list_data(data)
116
+
117
+ subset_number = math.ceil(len(data)/split_num)
118
+ for ii in range(split_num):
119
+ sub_data = data[ii*subset_number:(ii+1)*subset_number]
120
+
121
+ save_path = os.path.join(store_root, f'split-{ii}.npy')
122
+ np.save(save_path, sub_data)
123
+
124
+
125
+ # 功能2:读取key值对应的 name2key [因为可能存在多个values,所以返回的values都变成list格式了]
126
+ def func_labelstudio_read_key(json_path):
127
+ with open(json_path,'r',encoding='utf-8') as f:
128
+ data = json.load(f)
129
+
130
+ name2val = {}
131
+ for item in data:
132
+ values = []
133
+
134
+ ## analyze videoname
135
+ videopath = item['data']['video']
136
+ videoname = os.path.basename(videopath).rsplit('.', 1)[0]
137
+ # case1: sample_00001189.webm
138
+ # case2: def5d5b7-sample_00001189.webm
139
+ videoname_split = videoname.split('-', 1)
140
+ if len(videoname_split) == 2:
141
+ videoname = videoname_split[1]
142
+ elif len(videoname_split) == 1:
143
+ videoname = videoname_split[0]
144
+ else:
145
+ print (videoname)
146
+ raise ValueError('videoname has some errors!!')
147
+
148
+ ## analyze annotations
149
+ keys, values = [], []
150
+ annotations = item['annotations']
151
+ assert len(annotations) == 1
152
+ result = annotations[0]['result']
153
+ for ii in range(len(result)): # result 可能有多个 value
154
+
155
+ # 分析 choices 内容
156
+ if 'choices' in result[ii]['value']:
157
+ item = result[ii]['value']['choices']
158
+ keyname = result[ii]['from_name']
159
+ values.append(item)
160
+ keys.append(keyname)
161
+
162
+ # 分析 text 内容
163
+ if 'text' in result[ii]['value']:
164
+ item = result[ii]['value']['text']
165
+ keyname = result[ii]['from_name']
166
+ values.append(item)
167
+ keys.append(keyname)
168
+
169
+ name2val[videoname] = (keys, values)
170
+ return name2val
171
+
172
+
173
+ def func_shuffle_list_data(whole_json):
174
+ indices = np.arange(len(whole_json))
175
+ random.shuffle(indices)
176
+
177
+ new_json = []
178
+ for index in indices:
179
+ new_json.append(whole_json[index])
180
+ return new_json
181
+
182
+
183
+ # 功能3:从csv中读取特定的key对应的值
184
+ def func_read_key_from_csv(csv_path, key):
185
+ values = []
186
+ df = pd.read_csv(csv_path)
187
+ # for _, row in df.iterrows():
188
+ for _, row in df.iterrows():
189
+ if key not in row:
190
+ values.append("")
191
+ else:
192
+ value = row[key]
193
+ if pd.isna(value): value=""
194
+ values.append(value)
195
+ return values
196
+
197
+
198
+ # names[ii] -> keys=name2key[names[ii]], containing keynames
199
+ def func_write_key_to_csv(csv_path, names, name2key, keynames):
200
+ ## specific case: only save names
201
+ if len(name2key) == 0 or len(keynames) == 0:
202
+ df = pd.DataFrame(data=names, columns=['name'])
203
+ df.to_csv(csv_path, index=False)
204
+ return
205
+
206
+ ## other cases:
207
+ if isinstance(keynames, str):
208
+ keynames = [keynames]
209
+ assert isinstance(keynames, list)
210
+ columns = ['name'] + keynames
211
+
212
+ values = []
213
+ for name in names:
214
+ value = name2key[name]
215
+ values.append(value)
216
+ values = np.array(values)
217
+ # ensure keynames is mapped
218
+ if len(values.shape) == 1:
219
+ assert len(keynames) == 1
220
+ else:
221
+ assert values.shape[-1] == len(keynames)
222
+ data = np.column_stack([names, values])
223
+
224
+ df = pd.DataFrame(data=data, columns=columns)
225
+ df.to_csv(csv_path, index=False)
226
+
227
+
228
+ # 仅限于utf-8
229
+ def func_read_text_file(file_path):
230
+ try:
231
+ with open(file_path, encoding='utf8') as f: lines = [line.strip() for line in f]
232
+ lines = [line for line in lines if len(line)!=0]
233
+ return lines
234
+ except:
235
+ with open(file_path, encoding='ansi') as f: lines = [line.strip() for line in f]
236
+ lines = [line for line in lines if len(line)!=0]
237
+ return lines
238
+
239
+ ##############################################################################################
240
+ ## names[ii] -> values[ii], 可能存在多个values,写到keyname+{jj} 中,返回json内容,存储是后面存储的
241
+ # whole_json = func_labelstudio_init_key(keyname, names, values)
242
+
243
+ ## 给一个json_path增加一个key,并按照原始路径保存到json_path
244
+ # func_labelstudio_update_key(json_path, val_name, name2val)
245
+
246
+ ## 功能:将一个json分割到多个json,并存储在store_root中
247
+ # func_labelstudio_split_json(json_path, store_root, split_num=8, shuffle=True)
248
+
249
+ ## 功能:将一个list数据分割成split_num
250
+ # func_split_list_data(data, store_root, split_num=8, shuffle=True)
251
+
252
+ ## 功能:读取key值对应的 name2key,可能有多个values值
253
+ # name2val = func_labelstudio_read_key(json_path)
254
+
255
+ ## 将json信息打乱
256
+ ## new_json = func_shuffle_list_data(whole_json)
257
+
258
+ ## 功能:从csv中读取特定的key对应的值
259
+ # func_read_key_from_csv(csv_path, key)
260
+
261
+ ## names[ii] -> keys=name2key[names[ii]], containing keynames -> csv_path
262
+ ## func_write_key_to_csv(csv_path, names, name2key, keynames)
263
+ ##############################################################################################