ProfRick commited on
Commit
cbc02eb
·
verified ·
1 Parent(s): 145a868

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +320 -0
app.py ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import streamlit as st
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ import random
6
+
7
+ st.set_page_config(page_title="Cell–Cell Communication Builder", layout="wide")
8
+
9
+ # -----------------------------
10
+ # Base option sets (your terms)
11
+ # -----------------------------
12
+ SECRETING_CELLS_BASE = [
13
+ "— select —",
14
+ "Presynaptic neuron",
15
+ "Hypothalamus/Pituitary/Adrenal cortex",
16
+ "Cardiomyocyte",
17
+ "Tumor cell",
18
+ ]
19
+
20
+ MOLECULES_BASE = [
21
+ "— select —",
22
+ "EGF",
23
+ "Neurotransmitter",
24
+ "Intracellular ions",
25
+ "Cortisol",
26
+ ]
27
+
28
+ RECEIVING_CELLS_BASE = [
29
+ "— select —",
30
+ "Neighboring cardiomyocytes",
31
+ "Same cell",
32
+ "Postsynaptic cell",
33
+ "Liver & skeletal muscle",
34
+ ]
35
+
36
+ SIGNAL_TYPES_BASE = [
37
+ "— select —",
38
+ "direct",
39
+ "autocrine",
40
+ "paracrine",
41
+ "endocrine",
42
+ ]
43
+
44
+ MOLECULE_CLASS_BASE = [
45
+ "— select —",
46
+ "hydrophilic",
47
+ "lipophilic",
48
+ "N/A (not applicable)",
49
+ ]
50
+
51
+ RECEPTOR_LOCS_BASE = [
52
+ "— select —",
53
+ "membrane-bound",
54
+ "intracellular",
55
+ "N/A (not applicable)",
56
+ ]
57
+
58
+ # -----------------------------
59
+ # Scenarios (titles simplified; type NOT shown in title)
60
+ # -----------------------------
61
+ SCENARIOS = {
62
+ "Cardiomyocyte signaling": {
63
+ "secreting": "Cardiomyocyte",
64
+ "molecule": "Intracellular ions",
65
+ "receiving": "Neighboring cardiomyocytes",
66
+ "type": "direct",
67
+ "mol_class": "N/A (not applicable)",
68
+ "receptor": "N/A (not applicable)",
69
+ "explain": {
70
+ "type": "Gap junctions are contact dependent → direct signaling.",
71
+ "mol_class": "Electrical/ionic coupling across connexons; not a classic ligand.",
72
+ "receptor": "No classic receptor: current spreads via channels/pores.",
73
+ },
74
+ },
75
+ "Cancer proliferation": {
76
+ "secreting": "Tumor cell",
77
+ "molecule": "EGF",
78
+ "receiving": "Same cell",
79
+ "type": "autocrine",
80
+ "mol_class": "hydrophilic",
81
+ "receptor": "membrane-bound",
82
+ "explain": {
83
+ "type": "Cell releases a signal that acts on itself → autocrine.",
84
+ "mol_class": "EGF is a protein → hydrophilic → can’t cross the bilayer.",
85
+ "receptor": "EGF binds EGFR (RTK) at the membrane.",
86
+ },
87
+ },
88
+ "Synapse signaling": {
89
+ "secreting": "Presynaptic neuron",
90
+ "molecule": "Neurotransmitter",
91
+ "receiving": "Postsynaptic cell",
92
+ "type": "paracrine",
93
+ "mol_class": "hydrophilic",
94
+ "receptor": "membrane-bound",
95
+ "explain": {
96
+ "type": "Very short-distance diffusion across synaptic cleft → paracrine.",
97
+ "mol_class": "Classical neurotransmitters act extracellularly.",
98
+ "receptor": "Postsynaptic receptors are membrane proteins (ionotropic/GPCR).",
99
+ },
100
+ },
101
+ "HPA axis": {
102
+ "secreting": "Hypothalamus/Pituitary/Adrenal cortex",
103
+ "molecule": "Cortisol",
104
+ "receiving": "Liver & skeletal muscle",
105
+ "type": "endocrine",
106
+ "mol_class": "lipophilic",
107
+ "receptor": "intracellular",
108
+ "explain": {
109
+ "type": "Hormone travels via blood to distant targets → endocrine.",
110
+ "mol_class": "Cortisol is steroidal → lipophilic → crosses the membrane.",
111
+ "receptor": "Steroids bind cytosolic/nuclear receptors → gene transcription.",
112
+ },
113
+ },
114
+ }
115
+
116
+ # -----------------------------
117
+ # Session state helpers
118
+ # -----------------------------
119
+ def shuffled(opts):
120
+ head, rest = opts[0], opts[1:]
121
+ random.shuffle(rest)
122
+ return [head] + rest
123
+
124
+ def ensure_state():
125
+ if "secret_opts" not in st.session_state:
126
+ st.session_state.secret_opts = shuffled(SECRETING_CELLS_BASE[:])
127
+ st.session_state.molecule_opts = shuffled(MOLECULES_BASE[:])
128
+ st.session_state.recv_opts = shuffled(RECEIVING_CELLS_BASE[:])
129
+ st.session_state.type_opts = shuffled(SIGNAL_TYPES_BASE[:])
130
+ st.session_state.class_opts = shuffled(MOLECULE_CLASS_BASE[:])
131
+ st.session_state.recept_opts = shuffled(RECEPTOR_LOCS_BASE[:])
132
+ if "selections" not in st.session_state:
133
+ st.session_state.selections = {
134
+ "secreting": "— select —",
135
+ "molecule": "— select —",
136
+ "receiving": "— select —",
137
+ "type": "— select —",
138
+ "mol_class": "— select —",
139
+ "receptor": "— select —",
140
+ }
141
+
142
+ def reshuffle_all():
143
+ st.session_state.secret_opts = shuffled(SECRETING_CELLS_BASE[:])
144
+ st.session_state.molecule_opts = shuffled(MOLECULES_BASE[:])
145
+ st.session_state.recv_opts = shuffled(RECEIVING_CELLS_BASE[:])
146
+ st.session_state.type_opts = shuffled(SIGNAL_TYPES_BASE[:])
147
+ st.session_state.class_opts = shuffled(MOLECULE_CLASS_BASE[:])
148
+ st.session_state.recept_opts = shuffled(RECEPTOR_LOCS_BASE[:])
149
+ clear_selections()
150
+
151
+ def clear_selections():
152
+ for k in st.session_state.selections.keys():
153
+ st.session_state.selections[k] = "— select —"
154
+
155
+ def evaluate(sel, key, scenario_key):
156
+ if sel == "— select —":
157
+ return None, "Incomplete — choose an option."
158
+ correct = SCENARIOS[scenario_key][key]
159
+ if sel == correct:
160
+ return True, "✔"
161
+ explain_map = SCENARIOS[scenario_key]["explain"]
162
+ why = explain_map.get("type" if key not in ("mol_class", "receptor") else key, "")
163
+ return False, f"Expected: {correct}. {why}"
164
+
165
+ def draw_diagram(selections, results, scenario_label):
166
+ fig, ax = plt.subplots(figsize=(9, 4.6))
167
+ ax.set_xlim(0, 13)
168
+ ax.set_ylim(0, 6.3)
169
+ ax.axis("off")
170
+
171
+ def color_box(x, y, text, ok, blank=False):
172
+ w, h = 2.8, 1.0
173
+ face = "#FFF8E1" if blank else ("#E8F5E9" if ok else "#FDECEA")
174
+ edge = "#FFB300" if blank else ("#2E7D32" if ok else "#C62828")
175
+ ax.add_patch(plt.Rectangle((x, y), w, h, fc=face, ec=edge, lw=2))
176
+ ax.text(x + w/2, y + h/2, text, ha="center", va="center", fontsize=11)
177
+ return (x, y, w, h)
178
+
179
+ def arrow(start_rect, end_rect, label=""):
180
+ x, y, w, h = start_rect
181
+ x2, y2, w2, h2 = end_rect
182
+ ax.annotate("", xy=(x2, y2 + h/2), xytext=(x + w, y + h/2),
183
+ arrowprops=dict(arrowstyle="->", lw=2))
184
+ if label:
185
+ ax.text((x + w + x2)/2, y + h/2 + 0.15, label, ha="center", fontsize=10)
186
+
187
+ # top row: secreting → molecule → receiving
188
+ s_ok = results["secreting"][0] if results["secreting"][0] is not None else True
189
+ m_ok = results["molecule"][0] if results["molecule"][0] is not None else True
190
+ r_ok = results["receiving"][0] if results["receiving"][0] is not None else True
191
+
192
+ s_rect = color_box(0.6, 3.9, f"Secreting Cell/Tissue\n{selections['secreting']}", s_ok, selections['secreting']=="— select —")
193
+ m_rect = color_box(4.6, 3.9, f"Molecule\n{selections['molecule']}", m_ok, selections['molecule']=="— select —")
194
+ r_rect = color_box(8.6, 3.9, f"Receiving Cell/Tissue\n{selections['receiving']}", r_ok, selections['receiving']=="— select —")
195
+
196
+ # bottom row: type → class → receptor
197
+ t_ok = results["type"][0] if results["type"][0] is not None else True
198
+ c_ok = results["mol_class"][0] if results["mol_class"][0] is not None else True
199
+ rc_ok = results["receptor"][0] if results["receptor"][0] is not None else True
200
+
201
+ t_rect = color_box(2.6, 1.6, f"Signaling Type\n{selections['type']}", t_ok, selections['type']=="— select —")
202
+ c_rect = color_box(6.6, 1.6, f"Molecule Class\n{selections['mol_class']}", c_ok, selections['mol_class']=="— select —")
203
+ rc_rect = color_box(10.6,1.6, f"Receptor Location\n{selections['receptor']}", rc_ok, selections['receptor']=="— select —")
204
+
205
+ arrow(s_rect, m_rect, "signal")
206
+ arrow(m_rect, r_rect, "response")
207
+ arrow(t_rect, c_rect)
208
+ arrow(c_rect, rc_rect)
209
+
210
+ ax.text(6.5, 5.9, scenario_label, ha="center", fontsize=12, fontweight="bold")
211
+ st.pyplot(fig)
212
+
213
+ # -----------------------------
214
+ # App UI
215
+ # -----------------------------
216
+ ensure_state()
217
+ st.title("Cell–Cell Communication Builder")
218
+
219
+ left, right = st.columns([1.1, 0.9])
220
+ with left:
221
+ scenario_label = st.selectbox(
222
+ "Scenario",
223
+ options=list(SCENARIOS.keys()),
224
+ index=0,
225
+ key="scenario_select"
226
+ )
227
+ with right:
228
+ shuffle_clicked = st.button("🔀 Shuffle options", use_container_width=True)
229
+ if shuffle_clicked:
230
+ reshuffle_all()
231
+
232
+ st.markdown("Build a consistent map of **Secreting cell/tissue → Molecule → Receiving cell/tissue** and choose **Signaling type**, **Molecule class**, and **Receptor location**. Then click **Test**.")
233
+
234
+ col1, col2 = st.columns(2)
235
+
236
+ with col1:
237
+ st.subheader("Actors")
238
+ st.session_state.selections["secreting"] = st.selectbox(
239
+ "Secreting Cell or Tissue",
240
+ options=st.session_state.secret_opts,
241
+ index=st.session_state.secret_opts.index(st.session_state.selections["secreting"])
242
+ if st.session_state.selections["secreting"] in st.session_state.secret_opts else 0,
243
+ key="sec_dd",
244
+ )
245
+ st.session_state.selections["molecule"] = st.selectbox(
246
+ "Molecule",
247
+ options=st.session_state.molecule_opts,
248
+ index=st.session_state.molecule_opts.index(st.session_state.selections["molecule"])
249
+ if st.session_state.selections["molecule"] in st.session_state.molecule_opts else 0,
250
+ key="mol_dd",
251
+ )
252
+ st.session_state.selections["receiving"] = st.selectbox(
253
+ "Receiving Cell or Tissue",
254
+ options=st.session_state.recv_opts,
255
+ index=st.session_state.recv_opts.index(st.session_state.selections["receiving"])
256
+ if st.session_state.selections["receiving"] in st.session_state.recv_opts else 0,
257
+ key="recv_dd",
258
+ )
259
+
260
+ with col2:
261
+ st.subheader("Mechanism")
262
+ st.session_state.selections["type"] = st.selectbox(
263
+ "Signaling Type",
264
+ options=st.session_state.type_opts,
265
+ index=st.session_state.type_opts.index(st.session_state.selections["type"])
266
+ if st.session_state.selections["type"] in st.session_state.type_opts else 0,
267
+ key="type_dd",
268
+ )
269
+ st.session_state.selections["mol_class"] = st.selectbox(
270
+ "Molecule Class",
271
+ options=st.session_state.class_opts,
272
+ index=st.session_state.class_opts.index(st.session_state.selections["mol_class"])
273
+ if st.session_state.selections["mol_class"] in st.session_state.class_opts else 0,
274
+ key="class_dd",
275
+ )
276
+ st.session_state.selections["receptor"] = st.selectbox(
277
+ "Receptor Location",
278
+ options=st.session_state.recept_opts,
279
+ index=st.session_state.recept_opts.index(st.session_state.selections["receptor"])
280
+ if st.session_state.selections["receptor"] in st.session_state.recept_opts else 0,
281
+ key="recept_dd",
282
+ )
283
+
284
+ action_col1, action_col2 = st.columns([1,1])
285
+ with action_col1:
286
+ tested = st.button("✅ Test", type="primary", use_container_width=True)
287
+ with action_col2:
288
+ cleared = st.button("🧹 Clear / Reshuffle", use_container_width=True)
289
+ if cleared:
290
+ reshuffle_all()
291
+
292
+ results = {}
293
+ if tested:
294
+ keys = ["secreting", "molecule", "receiving", "type", "mol_class", "receptor"]
295
+ for k in keys:
296
+ results[k] = evaluate(st.session_state.selections[k], k, st.session_state.scenario_select)
297
+
298
+ # feedback
299
+ incomplete = any(v[0] is None for v in results.values())
300
+ n_ok = sum(1 for v in results.values() if v[0] is True)
301
+ total = len(results)
302
+
303
+ if incomplete:
304
+ st.warning("◻️ **INCOMPLETE** — make all selections to test consistency.")
305
+ elif n_ok == total:
306
+ st.success(f"✅ **CONSISTENT** ({n_ok}/{total}) — Nice! Logical mapping.")
307
+ else:
308
+ st.error(f"⚠️ **INCONSISTENT** ({n_ok}/{total}). See hints below.")
309
+ with st.expander("Why some choices are inconsistent"):
310
+ for k, (ok, msg) in results.items():
311
+ if ok is False:
312
+ st.markdown(f"- **{k.title()}**: {msg}")
313
+
314
+ draw_diagram(st.session_state.selections, results, st.session_state.scenario_select)
315
+ else:
316
+ # Draw a neutral diagram if not tested yet
317
+ tmp = {k:(None,"") for k in ["secreting","molecule","receiving","type","mol_class","receptor"]}
318
+ draw_diagram(st.session_state.selections, tmp, st.session_state.scenario_select)
319
+
320
+ st.caption("Options are shuffled on load and when you clear/reshuffle, to emphasize reasoning over pattern matching.")