Youngsun Lim commited on
Commit
e7ac655
ยท
1 Parent(s): f99f1f1
Files changed (1) hide show
  1. app.py +109 -19
app.py CHANGED
@@ -92,6 +92,97 @@ def _progress_html(done, total):
92
  <div style="font-size:12px; margin-top:4px;">{done} / {total}</div>
93
  """
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  # -------------------- Example videos (download to local cache) --------------------
96
  EXAMPLES = {
97
  "BodyWeightSquats": {
@@ -268,39 +359,38 @@ with gr.Blocks(fill_height=True, css=GLOBAL_CSS) as demo:
268
  pid.change(_toggle_by_pid, inputs=pid, outputs=save_next)
269
 
270
  # -------- ํŽ˜์ด์ง€ ์ „ํ™˜ & ์ฒซ ๋กœ๋“œ --------
271
- # def _start_and_load_first():
272
- # url, action = pick_one()
273
- # return (
274
- # gr.update(visible=False), # page_intro off
275
- # gr.update(visible=True), # page_eval on
276
- # url, action, 5.0, "", 0, _progress_html(0, TOTAL_PER_PARTICIPANT)
277
- # )
278
-
279
- # start_btn.click(
280
- # _start_and_load_first,
281
- # inputs=[],
282
- # outputs=[page_intro, page_eval, video, action_tb, score, status, done_state, progress]
283
- # )
284
 
285
  def _start_and_load_first():
286
- # ํ‰๊ฐ€ํ•  ์ธ๋ฑ์Šค ์ˆœ์„œ (์…”ํ”Œ)
287
- order = random.sample(range(len(V)), k=min(TOTAL_PER_PARTICIPANT, len(V)))
 
 
 
 
 
 
 
288
  first_idx = order[0]
289
  v0 = V[first_idx]
290
- url0, action0 = v0["url"], _extract_action(v0)
 
 
291
  return (
292
  gr.update(visible=False), # page_intro off
293
  gr.update(visible=True), # page_eval on
294
  url0, # video
295
  action0, # action_tb
296
  5.0, # score ์ดˆ๊ธฐ๊ฐ’
297
- "", # status (๋นˆ ๋ฉ”์‹œ์ง€)
298
  0, # done_state
299
  _progress_html(0, TOTAL_PER_PARTICIPANT),
300
- order, # order_state ์ดˆ๊ธฐํ™”
301
- 1 # ptr_state: ๋‹ค์Œ ์ธ๋ฑ์Šค ํฌ์ธํ„ฐ
302
  )
303
 
 
304
  start_btn.click(
305
  _start_and_load_first,
306
  inputs=[],
 
92
  <div style="font-size:12px; margin-top:4px;">{done} / {total}</div>
93
  """
94
 
95
+ def _build_order_with_anchor(total:int, anchor_idx:int, repeats:int, pool_size:int, min_gap:int=1):
96
+ """
97
+ total: TOTAL_PER_PARTICIPANT (e.g., 30)
98
+ anchor_idx: index of the anchor video in V (0 for first item)
99
+ repeats: how many times to show anchor (e.g., 5)
100
+ pool_size: len(V)
101
+ min_gap: ์ตœ์†Œ ๊ฐ„๊ฒฉ(์ธ์ ‘ ๊ธˆ์ง€ => 1)
102
+
103
+ return: list of indices (length=total)
104
+ """
105
+ assert repeats <= total, "repeats must be <= total"
106
+ assert pool_size >= 1, "videos pool must be non-empty"
107
+
108
+ # 1) ๋‹ค๋ฅธ ๋น„๋””์˜ค 25๊ฐœ(์ค‘๋ณต ์—†์ด) ๋ฝ‘๊ธฐ
109
+ others_needed = total - repeats
110
+ # anchor๋ฅผ ์ œ์™ธํ•œ ํ›„๋ณด ์ธ๋ฑ์Šค
111
+ candidates = list(range(1, pool_size)) if anchor_idx == 0 else [i for i in range(pool_size) if i != anchor_idx]
112
+ if len(candidates) < others_needed:
113
+ raise ValueError("Not enough unique non-anchor videos to fill the schedule without duplication.")
114
+
115
+ others = random.sample(candidates, k=others_needed)
116
+
117
+ # 2) ๊ธฐ๋ณธ ์‹œํ€€์Šค(others)๋ฅผ ๋ฌด์ž‘์œ„๋กœ ์„ž๊ธฐ
118
+ random.shuffle(others)
119
+
120
+ # 3) ์•ต์ปค๋ฅผ min_gap๋ฅผ ๋งŒ์กฑํ•˜๋„๋ก ์‚ฝ์ž…ํ•  ์œ„์น˜ ์„ ์ •
121
+ # 30๊ฐœ๋ฅผ 5๊ตฌ๊ฐ„์œผ๋กœ ๋‚˜๋ˆ , ๊ฐ ๊ตฌ๊ฐ„ ๋‚ด์—์„œ ์ถฉ๋Œ ๋œ ๋‚˜๊ฒŒ ๋ฐฐ์น˜
122
+ # (๊ฐ„๋‹จํ•˜๊ณ  ์•ˆ์ •์ ์ธ ๋ฐฉ์‹)
123
+ seq = others[:] # ๊ธธ์ด=25
124
+ anchor_positions = []
125
+ segment = total // repeats # 30//5 = 6
126
+
127
+ for k in range(repeats):
128
+ # ๊ฐ ๊ตฌ๊ฐ„ [k*segment, (k+1)*segment) ์•ˆ์—์„œ ํ›„๋ณด ์œ„์น˜๋ฅผ ๊ณ ๋ฆ„
129
+ lo = k * segment
130
+ hi = (k + 1) * segment if k < repeats - 1 else total # ๋งˆ์ง€๋ง‰์€ ๋๊นŒ์ง€
131
+ # ๊ฒฝ๊ณ„ ๋‚ด ์ž„์˜ ์˜คํ”„์…‹ ์„ ํƒ (์—ฌ์œ ๋ฅผ ๋‘๊ณ  ์ถฉ๋Œ์„ ํ”ผํ•จ)
132
+ candidate_pos = random.randrange(lo, hi)
133
+
134
+ # ์ธ์ ‘ ๊ธˆ์ง€ ๋ณด์ •: ์ด๋ฏธ ๋ฐฐ์ •๋œ anchor ์œ„์น˜๋“ค๊ณผ์˜ ๊ฑฐ๋ฆฌ๊ฐ€ min_gap ์ด์ƒ ๋˜๋„๋ก ์กฐ์ •
135
+ # ํ•„์š” ์‹œ ์ขŒ์šฐ๋กœ ๊ทผ์ ‘ํ•œ ๋นˆ ์Šฌ๋กฏ ํƒ์ƒ‰
136
+ def ok(pos):
137
+ return all(abs(pos - p) >= min_gap + 1 for p in anchor_positions) # ์—ฐ์†๊ธˆ์ง€ => ๊ฑฐ๋ฆฌ >= 2
138
+
139
+ # ๊ทผ๋ฐฉ ํƒ์ƒ‰ ํญ
140
+ found = None
141
+ for delta in range(0, segment): # ๊ตฌ๊ฐ„ ํฌ๊ธฐ ๋‚ด์—์„œ ํƒ์ƒ‰
142
+ # ์ขŒ/์šฐ ๋ฒˆ๊ฐˆ์•„๊ฐ€๋ฉฐ ํ›„๋ณด ์‹œ๋„
143
+ for sign in (+1, -1):
144
+ pos = candidate_pos + sign * delta
145
+ if 0 <= pos < total and ok(pos):
146
+ found = pos
147
+ break
148
+ if found is not None:
149
+ break
150
+
151
+ if found is None:
152
+ # ์ตœํ›„: 0..total-1 ๋ฒ”์œ„์—์„œ ์•„๋ฌด ๋ฐ๋‚˜ ์ถฉ๋Œ ์—†๋Š” ๊ณณ ์ฐพ๊ธฐ
153
+ for pos in range(total):
154
+ if ok(pos):
155
+ found = pos
156
+ break
157
+ if found is None:
158
+ raise RuntimeError("Failed to place anchor without adjacency. Try different strategy or loosen min_gap.")
159
+
160
+ anchor_positions.append(found)
161
+
162
+ # 4) others๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ธธ์ด total์˜ ๋นˆ ์‹œํ€€์Šค๋ฅผ ๋งŒ๋“ค๊ณ  anchor๋ฅผ ์ฃผ์ž…
163
+ # ์šฐ์„  ๋นˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ณ  anchor ์œ„์น˜๋ฅผ ์ฑ„์šด ํ›„, ๋‚˜๋จธ์ง€๋ฅผ others๋กœ ์ฑ„์›€
164
+ result = [None] * total
165
+ for pos in anchor_positions:
166
+ result[pos] = anchor_idx
167
+
168
+ # others ํฌ์ธํ„ฐ
169
+ j = 0
170
+ for i in range(total):
171
+ if result[i] is None:
172
+ result[i] = others[j]
173
+ j += 1
174
+
175
+ # ์•ˆ์ „์ฒดํฌ
176
+ assert len(result) == total
177
+ # ์ธ์ ‘ anchor ์—†๋Š”์ง€ ํ™•์ธ
178
+ for i in range(1, total):
179
+ assert not (result[i] == anchor_idx and result[i-1] == anchor_idx), "Adjacent anchors found."
180
+ # anchor ๊ฐœ์ˆ˜ ํ™•์ธ
181
+ assert sum(1 for x in result if x == anchor_idx) == repeats, "Anchor count mismatch."
182
+
183
+ return result
184
+
185
+
186
  # -------------------- Example videos (download to local cache) --------------------
187
  EXAMPLES = {
188
  "BodyWeightSquats": {
 
359
  pid.change(_toggle_by_pid, inputs=pid, outputs=save_next)
360
 
361
  # -------- ํŽ˜์ด์ง€ ์ „ํ™˜ & ์ฒซ ๋กœ๋“œ --------
362
+ ANCHOR_IDX = 0 # videos.json์˜ ๋งจ ์ฒซ ๋ฒˆ์งธ ๋น„๋””์˜ค
363
+ ANCHOR_REPEATS = 5 # ์•ต์ปค 5ํšŒ ๋…ธ์ถœ
 
 
 
 
 
 
 
 
 
 
 
364
 
365
  def _start_and_load_first():
366
+ total = TOTAL_PER_PARTICIPANT
367
+ order = _build_order_with_anchor(
368
+ total=total,
369
+ anchor_idx=ANCHOR_IDX,
370
+ repeats=ANCHOR_REPEATS,
371
+ pool_size=len(V),
372
+ min_gap=1 # ์ธ์ ‘ ๊ธˆ์ง€
373
+ )
374
+
375
  first_idx = order[0]
376
  v0 = V[first_idx]
377
+ url0 = v0["url"]
378
+ action0 = _extract_action(v0)
379
+
380
  return (
381
  gr.update(visible=False), # page_intro off
382
  gr.update(visible=True), # page_eval on
383
  url0, # video
384
  action0, # action_tb
385
  5.0, # score ์ดˆ๊ธฐ๊ฐ’
386
+ gr.update(visible=False, value=""), # status ์ดˆ๊ธฐํ™” (hidden)
387
  0, # done_state
388
  _progress_html(0, TOTAL_PER_PARTICIPANT),
389
+ order, # order_state
390
+ 1 # ptr_state (๋‹ค์Œ ์•„์ดํ…œ ๊ฐ€๋ฆฌํ‚ด)
391
  )
392
 
393
+
394
  start_btn.click(
395
  _start_and_load_first,
396
  inputs=[],