Spaces:
Runtime error
Runtime error
Deploy free ZeroGPU FitTest with private responses
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- README.md +10 -8
- app.py +56 -0
- images/Q01_A.jpg +0 -0
- images/Q01_B.jpg +0 -0
- images/Q01_C.jpg +0 -0
- images/Q02_A.jpg +0 -0
- images/Q02_B.jpg +0 -0
- images/Q02_C.jpg +0 -0
- images/Q03_A.jpg +0 -0
- images/Q03_B.jpg +0 -0
- images/Q03_C.jpg +0 -0
- images/Q04_A.jpg +0 -0
- images/Q04_B.jpg +0 -0
- images/Q04_C.jpg +0 -0
- images/Q05_A.jpg +0 -0
- images/Q05_B.jpg +0 -0
- images/Q05_C.jpg +0 -0
- images/Q06_A.jpg +0 -0
- images/Q06_B.jpg +0 -0
- images/Q06_C.jpg +0 -0
- images/Q07_A.jpg +0 -0
- images/Q07_B.jpg +0 -0
- images/Q07_C.jpg +0 -0
- images/Q08_A.jpg +0 -0
- images/Q08_B.jpg +0 -0
- images/Q08_C.jpg +0 -0
- images/Q09_A.jpg +0 -0
- images/Q09_B.jpg +0 -0
- images/Q09_C.jpg +0 -0
- images/Q10_A.jpg +0 -0
- images/Q10_B.jpg +0 -0
- images/Q10_C.jpg +0 -0
- images/Q11_A.jpg +0 -0
- images/Q11_B.jpg +0 -0
- images/Q11_C.jpg +0 -0
- images/Q12_A.jpg +0 -0
- images/Q12_B.jpg +0 -0
- images/Q12_C.jpg +0 -0
- images/Q13_A.jpg +0 -0
- images/Q13_B.jpg +0 -0
- images/Q13_C.jpg +0 -0
- images/Q14_A.jpg +0 -0
- images/Q14_B.jpg +0 -0
- images/Q14_C.jpg +0 -0
- images/Q15_A.jpg +0 -0
- images/Q15_B.jpg +0 -0
- images/Q15_C.jpg +0 -0
- images/Q16_A.jpg +0 -0
- images/Q16_B.jpg +0 -0
- images/Q16_C.jpg +0 -0
README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
| 1 |
---
|
| 2 |
-
title: FitTest
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|