maskil commited on
Commit
1f4ffe1
Β·
verified Β·
1 Parent(s): fd3dbc2

Upload 4 files

Browse files
Files changed (4) hide show
  1. ai_model.py +166 -0
  2. app.py +333 -0
  3. rabi_model_best.pt +3 -0
  4. requirements.txt +8 -0
ai_model.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AI Model for Rabi Oscillation Parameter Estimation.
3
+ Transformer-based model that predicts fit parameters (A, T, phi, C) from raw signal data.
4
+ """
5
+ import torch
6
+ import torch.nn as nn
7
+ import torch.nn.functional as F
8
+ import numpy as np
9
+ import math
10
+
11
+ DEVICE = torch.device("mps") if torch.backends.mps.is_available() else torch.device("cpu")
12
+ FIXED_LEN = 256
13
+ A_MIN, A_MAX = 1e-5, 0.5
14
+ T_MIN, T_MAX = 0.02, 0.3
15
+ C_MIN, C_MAX = -0.1, 1.0
16
+ LOG_A_MIN, LOG_A_MAX = math.log(A_MIN), math.log(A_MAX)
17
+ LOG_T_MIN, LOG_T_MAX = math.log(T_MIN), math.log(T_MAX)
18
+ AUX_SCALES = np.array([0.1, 0.1, 0.1, 0.05, 0.1, 2.0], dtype=np.float32)
19
+
20
+
21
+ def preprocess_sample(x_raw, y_raw):
22
+ xu = np.linspace(x_raw[0], x_raw[-1], FIXED_LEN)
23
+ yr = np.interp(xu, x_raw, y_raw)
24
+ ymin, ymax = float(yr.min()), float(yr.max())
25
+ ymean, ystd = float(yr.mean()), float(yr.std())
26
+ xrange = float(x_raw[-1] - x_raw[0])
27
+ ydm = yr - ymean
28
+ zc = np.where(np.diff(np.sign(ydm)))[0]
29
+ nper = len(zc) / 2.0 if len(zc) >= 1 else 0.5
30
+ span = ymax - ymin
31
+ if span < 1e-15:
32
+ span = 1.0
33
+ yn = ((yr - ymin) / span).astype(np.float32)
34
+ fft = np.fft.rfft(yn)
35
+ fft_mag = np.abs(fft[:64])
36
+ fft_ang = np.angle(fft[:64])
37
+ if len(fft_mag) < 64:
38
+ fft_mag = np.pad(fft_mag, (0, 64 - len(fft_mag)))
39
+ fft_ang = np.pad(fft_ang, (0, 64 - len(fft_ang)))
40
+ fft_feats = np.concatenate([fft_mag, fft_ang]).astype(np.float32)
41
+ aux = np.array([ymin, ymax, ymean, ystd, xrange, nper], dtype=np.float32) / AUX_SCALES
42
+ return yn, fft_feats, aux
43
+
44
+
45
+ def target_to_params(t):
46
+ nA, nT, sp, cp, nC = t[..., 0], t[..., 1], t[..., 2], t[..., 3], t[..., 4]
47
+ A = np.exp(nA * (LOG_A_MAX - LOG_A_MIN) + LOG_A_MIN)
48
+ T = np.exp(nT * (LOG_T_MAX - LOG_T_MIN) + LOG_T_MIN)
49
+ phi = np.arctan2(sp, cp)
50
+ C = nC * (C_MAX - C_MIN) + C_MIN
51
+ if t.ndim == 1:
52
+ return np.array([A, T, phi, C], dtype=np.float64)
53
+ return np.stack([A, T, phi, C], -1).astype(np.float64)
54
+
55
+
56
+ class PositionalEncoding(nn.Module):
57
+ def __init__(self, d_model, max_len=512):
58
+ super().__init__()
59
+ pe = torch.zeros(max_len, d_model)
60
+ position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
61
+ div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
62
+ pe[:, 0::2] = torch.sin(position * div_term)
63
+ pe[:, 1::2] = torch.cos(position * div_term)
64
+ self.register_buffer('pe', pe)
65
+
66
+ def forward(self, x):
67
+ return x + self.pe[:x.size(1), :]
68
+
69
+
70
+ class RabiEstimator(nn.Module):
71
+ def __init__(self):
72
+ super().__init__()
73
+ self.conv1 = nn.Sequential(nn.Conv1d(1, 64, kernel_size=7, stride=2, padding=3), nn.BatchNorm1d(64), nn.GELU())
74
+ self.conv2 = nn.Sequential(nn.Conv1d(64, 128, kernel_size=5, stride=2, padding=2), nn.BatchNorm1d(128), nn.GELU())
75
+ self.conv3 = nn.Sequential(nn.Conv1d(128, 256, kernel_size=5, stride=2, padding=2), nn.BatchNorm1d(256), nn.GELU())
76
+ self.d_model = 256
77
+ self.pos_enc = PositionalEncoding(self.d_model)
78
+ encoder_layer = nn.TransformerEncoderLayer(d_model=self.d_model, nhead=8, dim_feedforward=512, dropout=0.1, batch_first=True)
79
+ self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=6)
80
+ self.fft_net = nn.Sequential(nn.Linear(128, 256), nn.GELU(), nn.Dropout(0.1), nn.Linear(256, 128), nn.GELU())
81
+ self.aux_net = nn.Sequential(nn.Linear(6, 64), nn.GELU(), nn.Linear(64, 128), nn.GELU())
82
+ self.fusion = nn.Sequential(nn.Linear(256 + 128 + 128, 512), nn.BatchNorm1d(512), nn.GELU(), nn.Dropout(0.1))
83
+ self.head_atc = nn.Sequential(nn.Linear(512, 256), nn.GELU(), nn.Linear(256, 3))
84
+ self.head_ph = nn.Sequential(nn.Linear(512, 256), nn.GELU(), nn.Linear(256, 2))
85
+
86
+ def forward(self, wave, fft_f, aux):
87
+ x = wave.unsqueeze(1)
88
+ x = self.conv3(self.conv2(self.conv1(x)))
89
+ x = x.permute(0, 2, 1)
90
+ x = self.pos_enc(x)
91
+ x = self.transformer(x)
92
+ x_pool = x.mean(dim=1)
93
+ f = self.fft_net(fft_f)
94
+ a = self.aux_net(aux)
95
+ feat = self.fusion(torch.cat([x_pool, f, a], dim=1))
96
+ atc = torch.sigmoid(self.head_atc(feat))
97
+ ph = torch.tanh(self.head_ph(feat))
98
+ return torch.cat([atc[:, :2], ph, atc[:, 2:3]], 1)
99
+
100
+
101
+ # ═══════════════════════════════════════════════════════════════════
102
+ # Helper functions
103
+ # ═══════════════════════════════════════════════════════════════════
104
+
105
+ def rabi_formula(x, A, T, phi, C):
106
+ """Compute Rabi oscillation: A * cos(2*pi/T * x + phi) + C"""
107
+ return A * np.cos((2 * np.pi / T) * x + phi) + C
108
+
109
+
110
+ def load_model(model_path):
111
+ """Load trained RabiEstimator weights from disk."""
112
+ model = RabiEstimator().to(DEVICE)
113
+ checkpoint = torch.load(model_path, map_location=DEVICE, weights_only=False)
114
+ if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:
115
+ model.load_state_dict(checkpoint['model_state_dict'])
116
+ else:
117
+ model.load_state_dict(checkpoint)
118
+ model.eval()
119
+ return model
120
+
121
+
122
+ def predict_ai_fit(x_raw, y_raw, model):
123
+ """
124
+ Predict Rabi parameters using the AI model and return the absolute fit curve.
125
+
126
+ Args:
127
+ x_raw: np.array of x values (time/amplitude axis)
128
+ y_raw: np.array of y values (signal)
129
+ model: loaded RabiEstimator model
130
+
131
+ Returns:
132
+ y_fit: np.array of predicted fit values evaluated on x_raw
133
+ params: dict with keys 'amplitude', 'T', 'phase', 'offset'
134
+ """
135
+ x_raw = np.asarray(x_raw, dtype=np.float64)
136
+ y_raw = np.asarray(y_raw, dtype=np.float64)
137
+
138
+ # Ensure eval mode (BatchNorm1d requires it for batch_size=1)
139
+ model.eval()
140
+
141
+ # Sort by x for interpolation
142
+ sort_idx = np.argsort(x_raw)
143
+ x_sorted = x_raw[sort_idx]
144
+ y_sorted = y_raw[sort_idx]
145
+
146
+ yn, fft_feats, aux = preprocess_sample(x_sorted, y_sorted)
147
+
148
+ wave_t = torch.tensor(yn, dtype=torch.float32).unsqueeze(0).to(DEVICE)
149
+ fft_t = torch.tensor(fft_feats, dtype=torch.float32).unsqueeze(0).to(DEVICE)
150
+ aux_t = torch.tensor(aux, dtype=torch.float32).unsqueeze(0).to(DEVICE)
151
+
152
+ with torch.no_grad():
153
+ pred = model(wave_t, fft_t, aux_t)
154
+
155
+ pred_np = pred.cpu().numpy()[0]
156
+ A, T, phi, C = target_to_params(pred_np)
157
+
158
+ # Evaluate fit on the original x values (unsorted order preserved)
159
+ y_fit = rabi_formula(x_raw, float(A), float(T), float(phi), float(C))
160
+
161
+ return y_fit, {
162
+ 'amplitude': float(A),
163
+ 'T': float(T),
164
+ 'phase': float(phi),
165
+ 'offset': float(C),
166
+ }
app.py ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Human-in-the-Loop Feedback Tool for Rabi Oscillation Classification.
3
+ Production-ready Gradio interface with batch processing, AI model integration,
4
+ dynamic graphing based on curve overlap, and MongoDB feedback storage.
5
+ """
6
+ import gradio as gr
7
+ import torch
8
+ import numpy as np
9
+ import plotly.graph_objects as go
10
+ import json
11
+ import os
12
+ from datetime import datetime, timezone
13
+ from pymongo import MongoClient
14
+ import certifi
15
+
16
+ from ai_model import (
17
+ RabiEstimator, predict_ai_fit, load_model, rabi_formula, DEVICE
18
+ )
19
+
20
+ # ═══════════════════════════════════════════════════════════════════════════
21
+ # Configuration
22
+ # ═══════════════════════════════════════════════════════════════════════════
23
+ MONGO_URI = "mongodb+srv://Admin:zeyl3RK8oEu6Qhz3@cluster0.iwlxabj.mongodb.net/?appName=Cluster0"
24
+ DB_NAME = "rabi_classifier"
25
+ COLLECTION_NAME = "physicist_feedback"
26
+ MODEL_WEIGHTS = "rabi_model_best.pt"
27
+
28
+ # ═══════════════════════════════════════════════════════════════════════════
29
+ # Global state
30
+ # ═══════════════════════════════════════════════════════════════════════════
31
+ AI_MODEL = None
32
+ MONGO_COLLECTION = None
33
+
34
+ def init_globals():
35
+ """Initialize AI model and MongoDB connection at startup."""
36
+ global AI_MODEL, MONGO_COLLECTION
37
+
38
+ # Load AI model
39
+ if os.path.exists(MODEL_WEIGHTS):
40
+ try:
41
+ AI_MODEL = load_model(MODEL_WEIGHTS)
42
+ print(f"βœ“ AI model loaded from {MODEL_WEIGHTS} (device: {DEVICE})")
43
+ except Exception as e:
44
+ print(f"⚠ Failed to load AI model: {e}")
45
+ else:
46
+ print(f"⚠ Model weights not found at {MODEL_WEIGHTS}")
47
+
48
+ # Connect to MongoDB (non-blocking β€” app works without it)
49
+ try:
50
+ client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=3000,
51
+ tlsCAFile=certifi.where())
52
+ client.admin.command('ping')
53
+ db = client[DB_NAME]
54
+ MONGO_COLLECTION = db[COLLECTION_NAME]
55
+ print(f"βœ“ MongoDB connected: {DB_NAME}.{COLLECTION_NAME}")
56
+ except Exception as e:
57
+ MONGO_COLLECTION = None
58
+ print(f"⚠ MongoDB not available (will retry on submit): {e}")
59
+
60
+
61
+ # ═══════════════════════════════════════════════════════════════════════════
62
+ # JSON helpers
63
+ # ═══════════════════════════════════════════════════════════════════════════
64
+
65
+ def extract_fit_params(data):
66
+ """Extract the 4 fit parameters from JSON fitted_data."""
67
+ defaults = {'amplitude': 0.0, 'T': 0.05, 'phase': 0.0, 'offset': 0.0}
68
+ fitted = data.get('fitted_data')
69
+ if fitted is None:
70
+ return defaults
71
+ params_list = fitted.get('parameters')
72
+ if params_list is None:
73
+ return defaults
74
+ params = dict(defaults)
75
+ for p in params_list:
76
+ if p.get('name') in params:
77
+ params[p['name']] = float(p.get('value', 0.0))
78
+ return params
79
+
80
+
81
+ # ═══════════════════════════════════════════════════════════════════════════
82
+ # Plotting
83
+ # ═══════════════════════════════════════════════════════════════════════════
84
+
85
+ def create_plot(data, filename, regular_params, ai_params, curves_overlap):
86
+ """Create Plotly plot with raw data, Regular Fit (blue), AI Fit (green)."""
87
+ x = np.array(data['measured_data']['x_values'])
88
+ y = np.array(data['measured_data']['y_values'])
89
+ x_dense = np.linspace(x.min(), x.max(), 500)
90
+
91
+ y_regular = rabi_formula(
92
+ x_dense, regular_params['amplitude'], regular_params['T'],
93
+ regular_params['phase'], regular_params['offset']
94
+ )
95
+
96
+ fig = go.Figure()
97
+
98
+ # Raw data points
99
+ fig.add_trace(go.Scatter(
100
+ x=x, y=y, mode='markers', name='Raw Data',
101
+ marker=dict(color='rgba(52, 152, 219, 0.7)', size=6),
102
+ ))
103
+
104
+ # Regular Fit β€” Blue
105
+ fig.add_trace(go.Scatter(
106
+ x=x_dense, y=y_regular, mode='lines', name='Regular Fit (JSON)',
107
+ line=dict(color='#2980b9', width=2.5),
108
+ ))
109
+
110
+ # AI Fit β€” Green
111
+ if ai_params is not None:
112
+ y_ai = rabi_formula(
113
+ x_dense, ai_params['amplitude'], ai_params['T'],
114
+ ai_params['phase'], ai_params['offset']
115
+ )
116
+ if curves_overlap:
117
+ fig.add_trace(go.Scatter(
118
+ x=x_dense, y=y_ai, mode='lines',
119
+ name='AI Fit (overlapping)',
120
+ line=dict(color='#27ae60', width=3.5, dash='dot'),
121
+ ))
122
+ else:
123
+ fig.add_trace(go.Scatter(
124
+ x=x_dense, y=y_ai, mode='lines', name='AI Fit',
125
+ line=dict(color='#27ae60', width=2.5),
126
+ ))
127
+
128
+ title = f"Rabi Oscillation β€” {filename}"
129
+ if curves_overlap:
130
+ title += " (OVERLAP)"
131
+
132
+ fig.update_layout(
133
+ title=title,
134
+ xaxis_title='Drive Amplitude (a.u.)',
135
+ yaxis_title='Signal (a.u.)',
136
+ height=550,
137
+ margin=dict(l=50, r=30, t=60, b=50),
138
+ legend=dict(x=0.01, y=0.99),
139
+ font=dict(size=14),
140
+ )
141
+ return fig
142
+
143
+
144
+ # ═══════════════════════════════════════════════════════════════════════════
145
+ # Core processing
146
+ # ═══════════════════════════════════════════════════════════════════════════
147
+
148
+ def process_experiment(data, filename):
149
+ x = np.array(data['measured_data']['x_values'])
150
+ y = np.array(data['measured_data']['y_values'])
151
+
152
+ regular_params = extract_fit_params(data)
153
+ ai_params = None
154
+ overlap = True
155
+ model_status_msg = "Awaiting model..."
156
+
157
+ if AI_MODEL is not None:
158
+ try:
159
+ _, ai_params = predict_ai_fit(x, y, AI_MODEL)
160
+ y_reg = rabi_formula(x, regular_params['amplitude'], regular_params['T'],
161
+ regular_params['phase'], regular_params['offset'])
162
+ y_ai = rabi_formula(x, ai_params['amplitude'], ai_params['T'],
163
+ ai_params['phase'], ai_params['offset'])
164
+ overlap = np.allclose(y_reg, y_ai, rtol=1e-3, atol=1e-3)
165
+ model_status_msg = "βœ… AI Prediction successful."
166
+ except Exception as e:
167
+ model_status_msg = f"❌ AI prediction failed: {e}"
168
+ ai_params = None
169
+ overlap = True
170
+ else:
171
+ model_status_msg = "⚠ No AI model loaded. Only regular fit shown."
172
+
173
+ fig = create_plot(data, filename, regular_params, ai_params, overlap)
174
+ return fig, overlap, regular_params, ai_params, model_status_msg
175
+
176
+ def display_experiment(batch_data, idx):
177
+ if not batch_data or idx < 0 or idx >= len(batch_data):
178
+ return (
179
+ None, "πŸ“‚ Upload JSON files to begin", "", "Waiting for data...",
180
+ gr.update(visible=True), gr.update(visible=False),
181
+ None, "", None, None, None, "", "", "",
182
+ batch_data, idx, False
183
+ )
184
+
185
+ exp = batch_data[idx]
186
+ fig, overlap, reg_p, ai_p, mod_stat = process_experiment(exp['data'], exp['filename'])
187
+ progress = f"Experiment {idx + 1} / {len(batch_data)} β€” {exp['filename']}"
188
+
189
+ if overlap:
190
+ overlap_info = "πŸ”— Curves overlap (Regular and AI solutions match). Rate once."
191
+ else:
192
+ overlap_info = "↔️ Curves diverge (Regular = Blue, AI = Green). Rate each separately."
193
+
194
+ return (
195
+ fig, progress, overlap_info, mod_stat,
196
+ gr.update(visible=overlap),
197
+ gr.update(visible=not overlap),
198
+ None, "", None, None, None, "", "", "",
199
+ batch_data, idx, overlap
200
+ )
201
+
202
+ def on_upload(files):
203
+ if not files: return display_experiment([], 0)
204
+ batch_data = []
205
+ for f in files:
206
+ try:
207
+ fpath = f.name if hasattr(f, 'name') else str(f)
208
+ with open(fpath, 'r') as fp: data = json.load(fp)
209
+ batch_data.append({'data': data, 'filename': os.path.basename(fpath)})
210
+ except: pass
211
+ return display_experiment(batch_data, 0)
212
+
213
+ def on_prev(batch, idx): return display_experiment(batch, max(0, idx - 1))
214
+ def on_next(batch, idx): return display_experiment(batch, min(len(batch) - 1, idx + 1) if batch else 0)
215
+
216
+ def on_submit(batch, idx, overlap, data_rate, data_comment, fit_sing, fit_reg, fit_ai, c_sing, c_reg, c_ai):
217
+ if not batch: return ("⚠ No experiment.",) + display_experiment([], 0)
218
+ if data_rate is None: return ("⚠ Rate Data Quality.",) + display_experiment(batch, idx)
219
+ if overlap and fit_sing is None: return ("⚠ Rate Fit Quality.",) + display_experiment(batch, idx)
220
+ if not overlap and (fit_reg is None or fit_ai is None): return ("⚠ Rate both fits.",) + display_experiment(batch, idx)
221
+
222
+ exp = batch[idx]
223
+ x = np.array(exp['data']['measured_data']['x_values'])
224
+ y = np.array(exp['data']['measured_data']['y_values'])
225
+ ai_params = None
226
+ if AI_MODEL:
227
+ try: _, ai_params = predict_ai_fit(x, y, AI_MODEL)
228
+ except: pass
229
+
230
+ doc = {
231
+ 'filename': exp['filename'],
232
+ 'raw_data': exp['data'],
233
+ 'ai_prediction': ai_params,
234
+ 'curves_overlap': bool(overlap),
235
+ 'data_quality_rating': data_rate,
236
+ 'data_comment': data_comment or '',
237
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
238
+ }
239
+ if overlap:
240
+ doc['fit_quality_rating'] = {'combined': fit_sing}
241
+ doc['fit_comments'] = {'combined': c_sing or ''}
242
+ else:
243
+ doc['fit_quality_rating'] = {'regular_fit': fit_reg, 'ai_fit': fit_ai}
244
+ doc['fit_comments'] = {'regular_fit': c_reg or '', 'ai_fit': c_ai or ''}
245
+
246
+ status = "Feedback saved!"
247
+ if MONGO_COLLECTION is not None:
248
+ try: MONGO_COLLECTION.insert_one(doc)
249
+ except Exception as e: status = f"MongoDB error: {e}"
250
+ else: status = "⚠ MongoDB disconnected. Not saved."
251
+
252
+ nxt = idx + 1 if idx < len(batch) - 1 else idx
253
+ status += " Loaded next." if idx < len(batch) - 1 else " Last experiment!"
254
+ return (status,) + display_experiment(batch, nxt)
255
+
256
+
257
+ RATING_CHOICES = ["Bad (0)", "Borderline (1)", "Perfect (2)"]
258
+
259
+ def build_ui():
260
+ init_globals()
261
+ with gr.Blocks(title="Rabi Feedback") as demo:
262
+ batch_data = gr.State([])
263
+ batch_index = gr.State(0)
264
+ overlap_state = gr.State(False)
265
+
266
+ gr.Markdown("# βš›οΈ Rabi Oscillation β€” Physicist Feedback Tool")
267
+ gr.Markdown("Upload JSON experiments, review the quality of the data and fits, and submit your ratings.")
268
+
269
+ with gr.Row():
270
+ file_upload = gr.File(label="Upload JSON Files", file_count="multiple", file_types=[".json"])
271
+
272
+ with gr.Row():
273
+ prev_btn = gr.Button("← Previous")
274
+ progress_md = gr.Markdown("πŸ“‚ Upload JSON files to begin")
275
+ next_btn = gr.Button("Next β†’")
276
+
277
+ plot = gr.Plot()
278
+
279
+ with gr.Row():
280
+ overlap_info = gr.Markdown("")
281
+ model_status = gr.Markdown("Awaiting model...")
282
+
283
+ gr.Markdown("---")
284
+
285
+ with gr.Row():
286
+ with gr.Column():
287
+ gr.Markdown("### 1. Data Quality")
288
+ data_rating = gr.Radio(choices=RATING_CHOICES, label="Rate raw data visually:")
289
+ comment_data = gr.Textbox(label="Data notes", lines=2)
290
+
291
+ with gr.Column(visible=True) as fit_single_col:
292
+ gr.Markdown("### 2. Fit Quality (Combined)")
293
+ fit_single_rating = gr.Radio(choices=RATING_CHOICES, label="Rate the unified fit:")
294
+ comment_single = gr.Textbox(label="Fit notes", lines=2)
295
+
296
+ with gr.Column(visible=False) as fit_dual_col:
297
+ gr.Markdown("### 2. Fit Quality (Diverging)")
298
+ with gr.Row():
299
+ with gr.Column():
300
+ fit_regular_rating = gr.Radio(choices=RATING_CHOICES, label="Regular Fit (Blue):")
301
+ comment_regular = gr.Textbox(label="Notes on blue curve", lines=2)
302
+ with gr.Column():
303
+ fit_ai_rating = gr.Radio(choices=RATING_CHOICES, label="AI Fit (Green):")
304
+ comment_ai = gr.Textbox(label="Notes on green curve", lines=2)
305
+
306
+ gr.Markdown("---")
307
+ submit_btn = gr.Button("Submit Feedback & Next", variant="primary")
308
+ status_md = gr.Markdown("")
309
+
310
+ outputs = [
311
+ plot, progress_md, overlap_info, model_status,
312
+ fit_single_col, fit_dual_col,
313
+ data_rating, comment_data,
314
+ fit_single_rating, fit_regular_rating, fit_ai_rating,
315
+ comment_single, comment_regular, comment_ai,
316
+ batch_data, batch_index, overlap_state
317
+ ]
318
+
319
+ file_upload.change(on_upload, [file_upload], outputs)
320
+ prev_btn.click(on_prev, [batch_data, batch_index], outputs)
321
+ next_btn.click(on_next, [batch_data, batch_index], outputs)
322
+ submit_btn.click(on_submit, [
323
+ batch_data, batch_index, overlap_state,
324
+ data_rating, comment_data,
325
+ fit_single_rating, fit_regular_rating, fit_ai_rating,
326
+ comment_single, comment_regular, comment_ai
327
+ ], [status_md] + outputs)
328
+
329
+ return demo
330
+
331
+ if __name__ == '__main__':
332
+ demo = build_ui()
333
+ demo.launch()
rabi_model_best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6c9062ef5e9c2a954869e825ce7dc8c33975a2644320639b186b0fd95703f28f
3
+ size 16459501
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ torch
2
+ gradio
3
+ pymongo
4
+ dnspython
5
+ scipy
6
+ numpy
7
+ plotly
8
+ certifi