CurHarsh commited on
Commit
e8a0737
·
verified ·
1 Parent(s): 87b08b5

Deploy free ZeroGPU FitTest with private responses

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
README.md CHANGED
@@ -1,13 +1,15 @@
1
  ---
2
- title: FitTest 24
3
- emoji: 🦀
4
- colorFrom: red
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 6.30.0
8
- python_version: '3.12'
9
  app_file: app.py
10
- pinned: false
11
  ---
 
 
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
1
  ---
2
+ title: FitTest
3
+ emoji: 📐
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: gradio
 
 
7
  app_file: app.py
 
8
  ---
9
+ # FitTest
10
+ 24 visual fitting questions, one question at a time. Judge A, B, and C independently; at least one option fits.
11
 
12
+ Hosted on the free ZeroGPU tier. This study does not run model inference or request GPU work.
13
+ Submissions go to a private dataset via the server-side HF_TOKEN secret. RESPONSES_REPO identifies the dataset. No answer key or response-reading endpoint is included.
14
+
15
+ Participants should complete the study in one session. Previous/Next retain answers during the session. Final Submit confirms successful storage. No names, emails, or IP addresses are recorded in the submission files.
app.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io,json,os,threading,time,uuid
2
+ from pathlib import Path
3
+ from datetime import datetime,timezone
4
+ import gradio as gr
5
+ from huggingface_hub import HfApi
6
+ ROOT=Path(__file__).parent
7
+ QS=json.loads((ROOT/'questions.json').read_text())
8
+ LOCK=threading.Lock()
9
+ CRITERIA='''## Judging criteria
10
+ **At least one option fits in every question.** Two or all three may also fit. For each image, choose **Fits** or **Does not fit**.
11
+
12
+ - **Surface fit:** the object’s entire footprint must stay within the usable top surface.
13
+ - **Container fit:** the entire rigid object must fit inside the container without protruding.
14
+ '''
15
+ def new_state():return {'id':str(uuid.uuid4()),'index':0,'answers':{},'start':time.time(),'submitted':False}
16
+ def render(s):
17
+ q=QS[s['index']];a=s['answers'].get(q['id'],{})
18
+ 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']), '']
19
+ def init():return render(new_state())
20
+ def move(s,a,b,c,direction):
21
+ if s is None:s=new_state()
22
+ if s['submitted']:raise gr.Error('Your responses have already been submitted.')
23
+ 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.')
24
+ s['answers'][QS[s['index']]['id']]=dict(zip('ABC',[a,b,c]))
25
+ if direction==1 and s['index']==23:
26
+ 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.')
27
+ token,repo=os.getenv('HF_TOKEN'),os.getenv('RESPONSES_REPO')
28
+ if not token or not repo:raise gr.Error('Response storage is not configured. Please contact the study owner.')
29
+ 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()}}
30
+ try:
31
+ 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')
32
+ except Exception:raise gr.Error('Your submission was not saved. Please press Submit again.')
33
+ s['submitted']=True;out=render(s);out[-1]=f"## Thank you!\nYour responses have been saved privately.\n\nReceipt: `{s['id']}`";return out
34
+ s['index']=max(0,min(23,s['index']+direction));return render(s)
35
+ def previous(s,a,b,c):return move(s,a,b,c,-1)
36
+ def next_question(s,a,b,c):return move(s,a,b,c,1)
37
+ with gr.Blocks(title='FitTest') as demo:
38
+ gr.Markdown('# FitTest')
39
+ gr.Markdown(CRITERIA)
40
+ state=gr.State()
41
+ question=gr.Markdown()
42
+ imgs=[];radios=[]
43
+ with gr.Row():
44
+ for l in 'ABC':
45
+ with gr.Column():
46
+ imgs.append(gr.Image(label='Option '+l,interactive=False,show_download_button=False,show_fullscreen_button=True))
47
+ radios.append(gr.Radio(['Fits','Does not fit'],label=l,value=None))
48
+ with gr.Row():
49
+ prev=gr.Button('Previous');nxt=gr.Button('Next',variant='primary')
50
+ status=gr.Markdown()
51
+ outputs=[state,question,*imgs,*radios,prev,nxt,status]
52
+ demo.load(init,outputs=outputs,api_name=False)
53
+ prev.click(previous,inputs=[state,*radios],outputs=outputs,api_name=False)
54
+ nxt.click(next_question,inputs=[state,*radios],outputs=outputs,api_name=False)
55
+ gr.Markdown('Responses are visible only to the study owner. Please finish in one session; refreshing the page starts a new session.')
56
+ if __name__=='__main__':demo.queue(default_concurrency_limit=8).launch()
images/Q01_A.jpg ADDED
images/Q01_B.jpg ADDED
images/Q01_C.jpg ADDED
images/Q02_A.jpg ADDED
images/Q02_B.jpg ADDED
images/Q02_C.jpg ADDED
images/Q03_A.jpg ADDED
images/Q03_B.jpg ADDED
images/Q03_C.jpg ADDED
images/Q04_A.jpg ADDED
images/Q04_B.jpg ADDED
images/Q04_C.jpg ADDED
images/Q05_A.jpg ADDED
images/Q05_B.jpg ADDED
images/Q05_C.jpg ADDED
images/Q06_A.jpg ADDED
images/Q06_B.jpg ADDED
images/Q06_C.jpg ADDED
images/Q07_A.jpg ADDED
images/Q07_B.jpg ADDED
images/Q07_C.jpg ADDED
images/Q08_A.jpg ADDED
images/Q08_B.jpg ADDED
images/Q08_C.jpg ADDED
images/Q09_A.jpg ADDED
images/Q09_B.jpg ADDED
images/Q09_C.jpg ADDED
images/Q10_A.jpg ADDED
images/Q10_B.jpg ADDED
images/Q10_C.jpg ADDED
images/Q11_A.jpg ADDED
images/Q11_B.jpg ADDED
images/Q11_C.jpg ADDED
images/Q12_A.jpg ADDED
images/Q12_B.jpg ADDED
images/Q12_C.jpg ADDED
images/Q13_A.jpg ADDED
images/Q13_B.jpg ADDED
images/Q13_C.jpg ADDED
images/Q14_A.jpg ADDED
images/Q14_B.jpg ADDED
images/Q14_C.jpg ADDED
images/Q15_A.jpg ADDED
images/Q15_B.jpg ADDED
images/Q15_C.jpg ADDED
images/Q16_A.jpg ADDED
images/Q16_B.jpg ADDED
images/Q16_C.jpg ADDED