File size: 3,804 Bytes
e8a0737
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602a3c9
e8a0737
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import io,json,os,threading,time,uuid
from pathlib import Path
from datetime import datetime,timezone
import gradio as gr
from huggingface_hub import HfApi
ROOT=Path(__file__).parent
QS=json.loads((ROOT/'questions.json').read_text())
LOCK=threading.Lock()
CRITERIA='''## Judging criteria
**At least one option fits in every question.** Two or all three may also fit. For each image, choose **Fits** or **Does not fit**.

- **Surface fit:** the object’s entire footprint must stay within the usable top surface.
- **Container fit:** the entire rigid object must fit inside the container without protruding.
'''
def new_state():return {'id':str(uuid.uuid4()),'index':0,'answers':{},'start':time.time(),'submitted':False}
def render(s):
 q=QS[s['index']];a=s['answers'].get(q['id'],{})
 return [s,f"### Question {s['index']+1} of 24 · {'Surface fit' if q['domain']=='area' else 'Container fit'}\n\n{q['question']}",*[str(ROOT/'images'/Path(q['images'][l]).name) for l in 'ABC'],*[gr.update(value=a.get(l),interactive=not s['submitted']) for l in 'ABC'],gr.update(interactive=s['index']>0 and not s['submitted']),gr.update(value='Submit' if s['index']==23 else 'Next',interactive=not s['submitted']), '']
def init():return render(new_state())
def move(s,a,b,c,direction):
 if s is None:s=new_state()
 if s['submitted']:raise gr.Error('Your responses have already been submitted.')
 if direction==1 and any(v not in ['Fits','Does not fit'] for v in [a,b,c]):raise gr.Error('Please judge A, B, and C before continuing.')
 s['answers'][QS[s['index']]['id']]=dict(zip('ABC',[a,b,c]))
 if direction==1 and s['index']==23:
  if len(s['answers'])!=24 or any(any(v not in ['Fits','Does not fit'] for v in r.values()) for r in s['answers'].values()):raise gr.Error('Please answer all options before submitting.')
  token,repo=os.getenv('HF_TOKEN'),os.getenv('RESPONSES_REPO')
  if not token or not repo:raise gr.Error('Response storage is not configured. Please contact the study owner.')
  data={'participant_id':s['id'],'study_version':'fit-min1-24-20261009-v1','submitted_at':datetime.now(timezone.utc).isoformat(),'elapsed_seconds':int(time.time()-s['start']),'answers':{qid:{l:'Yes' if v=='Fits' else 'No' for l,v in row.items()} for qid,row in s['answers'].items()}}
  try:
   with LOCK:HfApi(token=token).upload_file(path_or_fileobj=io.BytesIO(json.dumps(data,indent=2).encode()),path_in_repo=f"responses/{s['id']}.json",repo_id=repo,repo_type='dataset',commit_message='Save FitTest submission')
  except Exception:raise gr.Error('Your submission was not saved. Please press Submit again.')
  s['submitted']=True;out=render(s);out[-1]=f"## Thank you!\nYour responses have been saved privately.\n\nReceipt: `{s['id']}`";return out
 s['index']=max(0,min(23,s['index']+direction));return render(s)
def previous(s,a,b,c):return move(s,a,b,c,-1)
def next_question(s,a,b,c):return move(s,a,b,c,1)
with gr.Blocks(title='FitTest') as demo:
 gr.Markdown('# FitTest')
 gr.Markdown(CRITERIA)
 state=gr.State()
 question=gr.Markdown()
 imgs=[];radios=[]
 with gr.Row():
  for l in 'ABC':
   with gr.Column():
    imgs.append(gr.Image(label='Option '+l,interactive=False))
    radios.append(gr.Radio(['Fits','Does not fit'],label=l,value=None))
 with gr.Row():
  prev=gr.Button('Previous');nxt=gr.Button('Next',variant='primary')
 status=gr.Markdown()
 outputs=[state,question,*imgs,*radios,prev,nxt,status]
 demo.load(init,outputs=outputs,api_name=False)
 prev.click(previous,inputs=[state,*radios],outputs=outputs,api_name=False)
 nxt.click(next_question,inputs=[state,*radios],outputs=outputs,api_name=False)
 gr.Markdown('Responses are visible only to the study owner. Please finish in one session; refreshing the page starts a new session.')
if __name__=='__main__':demo.queue(default_concurrency_limit=8).launch()