PDAson commited on
Commit
aac1fab
·
verified ·
1 Parent(s): a203f9e

Upload 3 files

Browse files
annotator_v3_3.py ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ import os
4
+ import re
5
+ from collections import defaultdict
6
+
7
+ def load_main_data(json_file_path):
8
+ """假设 JSON 是一个列表,每个元素形如:
9
+ {
10
+ 'image_path': 'some_path',
11
+ 'subject': 'xxx',
12
+ 'object': 'yyy',
13
+ 'options': { 'state': [...], 'action': [...], 'spatial': [...] }
14
+ }
15
+ """
16
+ with open(json_file_path, 'r', encoding='utf-8') as f:
17
+ return json.load(f)
18
+
19
+ def load_output_dict(output_file):
20
+ """读取已标注数据。如果不存在,则返回空字典。"""
21
+ if os.path.exists(output_file):
22
+ try:
23
+ with open(output_file, 'r', encoding='utf-8') as f:
24
+ data = json.load(f)
25
+ if not isinstance(data, dict):
26
+ data = {}
27
+ return data
28
+ except json.JSONDecodeError:
29
+ return {}
30
+ else:
31
+ return {}
32
+
33
+ def save_output_dict(output_file, data):
34
+ """保存标注结果到 output.json"""
35
+ with open(output_file, 'w', encoding='utf-8') as f:
36
+ json.dump(data, f, indent=2, ensure_ascii=False)
37
+
38
+ def extract_image_id_from_path(full_path):
39
+ """
40
+ 从 full_path 提取 'image_数字' 这一段,若找不到则返回去掉目录的文件名。
41
+ 例如:
42
+ annotated_image_folder\\split_4\\output_images_1592503\\image_1592503_pair_2_black bus_parked on.jpg
43
+ -> 'image_1592503'
44
+ """
45
+ # 先统一斜杠
46
+ full_path = full_path.replace("\\", "/")
47
+ filename = os.path.basename(full_path)
48
+ # 用正则匹配 "image_后面若干数字"
49
+ m = re.search(r"(image_\d+)", filename)
50
+ if m:
51
+ return m.group(1)
52
+ return filename # 如果失败,就退而求其次(不建议这么多文件都失败)
53
+
54
+ def gradio_interface(json_file_path='sample_six_grid_split_4_new.json'):
55
+ """
56
+ 主要变化:
57
+ 1) 用 extract_image_id_from_path 提取 image_XXXX 做分组,以便同一原图的多个 pair 正确显示 "Pair x/y for this image"。
58
+ 2) 保留 Subject / Object 并排显示,并在 status 中额外显示:此 pair 在当前图片中是第几/共几。
59
+ """
60
+
61
+ data = load_main_data(json_file_path) # 假设是 list
62
+ output_file = 'output.json'
63
+ labeled_data = load_output_dict(output_file)
64
+
65
+ # ---------------------------------------------------
66
+ # 1) 预处理:根据 "image_id" 分组
67
+ # ---------------------------------------------------
68
+ image_to_indices = defaultdict(list)
69
+ for idx, item in enumerate(data):
70
+ raw_path = item.get("image_path", "")
71
+ image_id = extract_image_id_from_path(raw_path)
72
+ image_to_indices[image_id].append(idx)
73
+
74
+ local_index_map = {}
75
+ local_count_map = {}
76
+ for image_id, idx_list in image_to_indices.items():
77
+ # 保持出现顺序
78
+ for local_i, real_idx in enumerate(idx_list):
79
+ local_index_map[real_idx] = local_i
80
+ local_count_map[real_idx] = len(idx_list)
81
+
82
+ # ---------------------------------------------------
83
+ # 2) 一些辅助函数
84
+ # ---------------------------------------------------
85
+ def get_item_info(idx):
86
+ item = data[idx]
87
+ image_path = item.get("image_path", "")
88
+ if not os.path.exists(image_path):
89
+ image_path = "placeholder.jpg"
90
+ subject = item.get("subject", "")
91
+ obj = item.get("object", "")
92
+ opts = item.get("options", {})
93
+ return image_path, subject, obj, opts
94
+
95
+ def split_options(options_list):
96
+ """前5个给 Radio,其余给 Dropdown"""
97
+ if len(options_list) <= 5:
98
+ return options_list, []
99
+ else:
100
+ return options_list[:5], options_list[5:]
101
+
102
+ def update_final_selection(radio_val, dropdown_val):
103
+ """Radio 优先,否则 Dropdown"""
104
+ if radio_val:
105
+ return radio_val
106
+ return dropdown_val or None
107
+
108
+ def update_skip_value(checked):
109
+ """skip_checkbox => bool -> str"""
110
+ return str(checked)
111
+
112
+ # ---------------------------------------------------
113
+ # 3) 初始化:idx=0
114
+ # ---------------------------------------------------
115
+ init_idx = 0
116
+ init_image, init_sub, init_obj, init_opts = get_item_info(init_idx)
117
+ state_radio_list, state_dropdown_list = split_options(init_opts.get("state", []))
118
+ action_radio_list, action_dropdown_list = split_options(init_opts.get("action", []))
119
+ spatial_radio_list, spatial_dropdown_list = split_options(init_opts.get("spatial", []))
120
+
121
+ init_radio_val = None
122
+ init_dropdown_val = None
123
+ init_skip_val = False
124
+
125
+ # ---------------------------------------------------
126
+ # 4) 搭建 Gradio 界面
127
+ # ---------------------------------------------------
128
+ with gr.Blocks() as demo:
129
+ cur_idx_state = gr.State(init_idx)
130
+
131
+ with gr.Row():
132
+ # 左侧:图像、Status、Details,以及翻页按钮
133
+ with gr.Column(scale=1):
134
+ img_view = gr.Image(value=init_image, label="Image")
135
+
136
+ # 这里的 status_box 会显示 全局进度+当前图片内的进度
137
+ status_box = gr.Textbox(
138
+ value="",
139
+ label="Status",
140
+ interactive=False
141
+ )
142
+ info_box = gr.Textbox(
143
+ value="Details: (will be updated...)",
144
+ label="Details",
145
+ interactive=False
146
+ )
147
+ with gr.Row():
148
+ btn_prev = gr.Button("← Previous", variant="secondary")
149
+ btn_next = gr.Button("Next →", variant="primary")
150
+
151
+ # 右侧:主逻辑
152
+ with gr.Column(scale=1):
153
+ # 在同一个 Row 显示 (Subject -> Object) + skip_checkbox
154
+ with gr.Row():
155
+ subject_object_md = gr.Markdown(
156
+ f"**{init_sub} → {init_obj}**",
157
+ elem_id="subject_object_header"
158
+ )
159
+ skip_checkbox = gr.Checkbox(
160
+ value=init_skip_val,
161
+ label="No relation (skip this pair)"
162
+ )
163
+ skip_final = gr.Textbox(value=str(init_skip_val), visible=False)
164
+ skip_checkbox.change(
165
+ fn=update_skip_value,
166
+ inputs=[skip_checkbox],
167
+ outputs=[skip_final]
168
+ )
169
+
170
+ # --- State ---
171
+ gr.Markdown("### State")
172
+ state_radio = gr.Radio(choices=state_radio_list, value=init_radio_val, label="Top 5")
173
+ state_dd = gr.Dropdown(choices=state_dropdown_list, value=init_dropdown_val, label="More Options")
174
+ state_final = gr.Textbox(value=None, visible=False, label="Final State")
175
+
176
+ state_radio.change(
177
+ fn=update_final_selection,
178
+ inputs=[state_radio, state_dd],
179
+ outputs=state_final
180
+ )
181
+ state_dd.change(
182
+ fn=update_final_selection,
183
+ inputs=[state_radio, state_dd],
184
+ outputs=state_final
185
+ )
186
+
187
+ # --- Action ---
188
+ gr.Markdown("### Action")
189
+ action_radio = gr.Radio(choices=action_radio_list, value=init_radio_val, label="Top 5")
190
+ action_dd = gr.Dropdown(choices=action_dropdown_list, value=init_dropdown_val, label="More Options")
191
+ action_final = gr.Textbox(value=None, visible=False, label="Final Action")
192
+
193
+ action_radio.change(
194
+ fn=update_final_selection,
195
+ inputs=[action_radio, action_dd],
196
+ outputs=action_final
197
+ )
198
+ action_dd.change(
199
+ fn=update_final_selection,
200
+ inputs=[action_radio, action_dd],
201
+ outputs=action_final
202
+ )
203
+
204
+ # --- Spatial ---
205
+ gr.Markdown("### Spatial")
206
+ spatial_radio = gr.Radio(choices=spatial_radio_list, value=init_radio_val, label="Top 5")
207
+ spatial_dd = gr.Dropdown(choices=spatial_dropdown_list, value=init_dropdown_val, label="More Options")
208
+ spatial_final = gr.Textbox(value=None, visible=False, label="Final Spatial")
209
+
210
+ spatial_radio.change(
211
+ fn=update_final_selection,
212
+ inputs=[spatial_radio, spatial_dd],
213
+ outputs=spatial_final
214
+ )
215
+ spatial_dd.change(
216
+ fn=update_final_selection,
217
+ inputs=[spatial_radio, spatial_dd],
218
+ outputs=spatial_final
219
+ )
220
+
221
+ # 底部的 Save
222
+ with gr.Row():
223
+ btn_save = gr.Button("Save", variant="primary")
224
+
225
+ # ---------------------------------------------------
226
+ # 5) 翻页函数
227
+ # ---------------------------------------------------
228
+ def go_next(cur_idx):
229
+ new_idx = (cur_idx + 1) % len(data)
230
+ return _jump_to_index(new_idx)
231
+
232
+ def go_prev(cur_idx):
233
+ new_idx = (cur_idx - 1) % len(data)
234
+ return _jump_to_index(new_idx)
235
+
236
+ def _jump_to_index(new_idx):
237
+ # 获取数据
238
+ image_path, sub, obj, opts = get_item_info(new_idx)
239
+ # 全局进度:new_idx+1 / len(data)
240
+ global_status = f"Currently showing: {new_idx+1}/{len(data)}"
241
+
242
+ # 获取本图的局部索引
243
+ local_idx = local_index_map[new_idx] # 从 0 开始
244
+ local_count = local_count_map[new_idx]
245
+ # 组合显示
246
+ new_status = f"{global_status}. (Pair {local_idx+1}/{local_count} for this image.)"
247
+
248
+ new_info = f"Subject: {sub}, Object: {obj}"
249
+ # 改 Markdown: "**sub -> obj**"
250
+ subobj_md = f"**{sub} → {obj}**"
251
+
252
+ st_list, st_dd = split_options(opts.get("state", []))
253
+ ac_list, ac_dd = split_options(opts.get("action", []))
254
+ sp_list, sp_dd = split_options(opts.get("spatial", []))
255
+
256
+ rec = labeled_data.get(str(new_idx), {})
257
+ skip_val = rec.get("skip", False)
258
+ if skip_val is True:
259
+ final_st_val = None
260
+ final_ac_val = None
261
+ final_sp_val = None
262
+ else:
263
+ final_st_val = rec.get("state", None)
264
+ final_ac_val = rec.get("action", None)
265
+ final_sp_val = rec.get("spatial", None)
266
+
267
+ return (
268
+ # 更新索引
269
+ new_idx,
270
+ # 更新图像
271
+ image_path,
272
+ # 更新 Status, Info
273
+ new_status,
274
+ new_info,
275
+ # 更新 subject_object_md
276
+ subobj_md,
277
+ # skip
278
+ bool(skip_val),
279
+ str(skip_val),
280
+ # state
281
+ gr.update(choices=st_list, value=None),
282
+ gr.update(choices=st_dd, value=None),
283
+ final_st_val,
284
+ # action
285
+ gr.update(choices=ac_list, value=None),
286
+ gr.update(choices=ac_dd, value=None),
287
+ final_ac_val,
288
+ # spatial
289
+ gr.update(choices=sp_list, value=None),
290
+ gr.update(choices=sp_dd, value=None),
291
+ final_sp_val
292
+ )
293
+
294
+ btn_next.click(
295
+ fn=go_next,
296
+ inputs=[cur_idx_state],
297
+ outputs=[
298
+ cur_idx_state,
299
+ img_view,
300
+ status_box,
301
+ info_box,
302
+ subject_object_md,
303
+ skip_checkbox,
304
+ skip_final,
305
+ state_radio,
306
+ state_dd,
307
+ state_final,
308
+ action_radio,
309
+ action_dd,
310
+ action_final,
311
+ spatial_radio,
312
+ spatial_dd,
313
+ spatial_final
314
+ ]
315
+ )
316
+
317
+ btn_prev.click(
318
+ fn=go_prev,
319
+ inputs=[cur_idx_state],
320
+ outputs=[
321
+ cur_idx_state,
322
+ img_view,
323
+ status_box,
324
+ info_box,
325
+ subject_object_md,
326
+ skip_checkbox,
327
+ skip_final,
328
+ state_radio,
329
+ state_dd,
330
+ state_final,
331
+ action_radio,
332
+ action_dd,
333
+ action_final,
334
+ spatial_radio,
335
+ spatial_dd,
336
+ spatial_final
337
+ ]
338
+ )
339
+
340
+ # ---------------------------------------------------
341
+ # 6) 保存逻辑
342
+ # ---------------------------------------------------
343
+ def handle_save(st_val, ac_val, sp_val, cur_idx, skip_val):
344
+ skip_flag = (skip_val == "True")
345
+ image_path, sub, obj, _ = get_item_info(cur_idx)
346
+ if skip_flag:
347
+ labeled_data[str(cur_idx)] = {
348
+ "subject": sub,
349
+ "object": obj,
350
+ "skip": True
351
+ }
352
+ save_output_dict(output_file, labeled_data)
353
+ return f"Skipped pair: {sub} - {obj}."
354
+ else:
355
+ if not st_val or not ac_val or not sp_val:
356
+ return "Please select all 3 categories or check 'no suitable option'!"
357
+ labeled_data[str(cur_idx)] = {
358
+ "subject": sub,
359
+ "object": obj,
360
+ "skip": False,
361
+ "state": st_val,
362
+ "action": ac_val,
363
+ "spatial": sp_val
364
+ }
365
+ save_output_dict(output_file, labeled_data)
366
+ return f"Saved: {sub}, {obj}, state={st_val}, action={ac_val}, spatial={sp_val}"
367
+
368
+ btn_save.click(
369
+ fn=handle_save,
370
+ inputs=[state_final, action_final, spatial_final, cur_idx_state, skip_final],
371
+ outputs=status_box
372
+ )
373
+
374
+ return demo
375
+
376
+
377
+ if __name__ == '__main__':
378
+ gradio_interface().launch(share=True)
requirements.txt ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiofiles==23.2.1
2
+ aiohappyeyeballs==2.4.4
3
+ aiohttp==3.10.11
4
+ aiosignal==1.3.1
5
+ altair==5.4.1
6
+ annotated-types==0.7.0
7
+ anyio==4.5.2
8
+ async-timeout==5.0.1
9
+ attrs==24.2.0
10
+ certifi==2024.8.30
11
+ charset-normalizer==3.4.0
12
+ click==8.1.7
13
+ colorama==0.4.6
14
+ contourpy==1.1.1
15
+ cycler==0.12.1
16
+ exceptiongroup==1.2.2
17
+ fastapi==0.115.5
18
+ ffmpy==0.4.0
19
+ filelock==3.16.1
20
+ fonttools==4.55.0
21
+ frozenlist==1.5.0
22
+ fsspec==2024.10.0
23
+ gradio==3.48.0
24
+ gradio_client==0.6.1
25
+ h11==0.14.0
26
+ httpcore==1.0.7
27
+ httpx==0.27.2
28
+ huggingface-hub==0.26.2
29
+ idna==3.10
30
+ importlib_resources==6.4.5
31
+ Jinja2==3.1.4
32
+ jsonschema==4.23.0
33
+ jsonschema-specifications==2023.12.1
34
+ kiwisolver==1.4.7
35
+ linkify-it-py==2.0.3
36
+ markdown-it-py==2.2.0
37
+ MarkupSafe==2.1.5
38
+ matplotlib==3.7.5
39
+ mdit-py-plugins==0.3.3
40
+ mdurl==0.1.2
41
+ multidict==6.1.0
42
+ narwhals==1.15.1
43
+ numpy==1.24.4
44
+ opencv-python==4.10.0.84
45
+ orjson==3.10.12
46
+ packaging==24.2
47
+ pandas==2.0.3
48
+ pillow==10.4.0
49
+ pkgutil_resolve_name==1.3.10
50
+ propcache==0.2.0
51
+ pydantic==2.10.2
52
+ pydantic_core==2.27.1
53
+ pydub==0.25.1
54
+ Pygments==2.18.0
55
+ pyparsing==3.1.4
56
+ python-dateutil==2.9.0.post0
57
+ python-multipart==0.0.17
58
+ pytz==2024.2
59
+ PyYAML==6.0.2
60
+ referencing==0.35.1
61
+ requests==2.32.3
62
+ rich==13.9.4
63
+ rpds-py==0.20.1
64
+ ruff==0.8.0
65
+ semantic-version==2.10.0
66
+ shellingham==1.5.4
67
+ six==1.16.0
68
+ sniffio==1.3.1
69
+ starlette==0.41.3
70
+ tomlkit==0.12.0
71
+ tqdm==4.67.1
72
+ typer==0.13.1
73
+ typing_extensions==4.12.2
74
+ tzdata==2024.2
75
+ uc-micro-py==1.0.3
76
+ urllib3==2.2.3
77
+ uvicorn==0.32.1
78
+ websockets==11.0.3
79
+ yarl==1.15.2
80
+ zipp==3.20.2
sample_six_grid_split_4_new.json ADDED
The diff for this file is too large to render. See raw diff