cgeorgiaw HF Staff commited on
Commit
53c8150
·
verified ·
1 Parent(s): 648cd8a

Separate Genome atlas and Data bank into styled tabs

Browse files
Files changed (5) hide show
  1. FEATURE_BACKLOG.md +5 -1
  2. README.md +1 -1
  3. app.css +26 -0
  4. app.py +54 -48
  5. taxonomy.py +2 -2
FEATURE_BACKLOG.md CHANGED
@@ -2,7 +2,7 @@
2
 
3
  Track desired features that are not yet implemented. Add new ideas here and update each item's status as work progresses. These entries describe future work; implementation details and priorities remain open unless explicitly agreed.
4
 
5
- Last updated: 2026-09-30.
6
 
7
  ## Overview
8
 
@@ -143,3 +143,7 @@ Desired behavior:
143
  ## Adding and updating entries
144
 
145
  Give each new feature a stable ID, a goal, a status, desired behavior, and open decisions or dependencies. Link implementation work when it starts. Once shipped, mark the entry implemented and record any remaining scope rather than deleting its history.
 
 
 
 
 
2
 
3
  Track desired features that are not yet implemented. Add new ideas here and update each item's status as work progresses. These entries describe future work; implementation details and priorities remain open unless explicitly agreed.
4
 
5
+ Last updated: 2026-10-05.
6
 
7
  ## Overview
8
 
 
143
  ## Adding and updating entries
144
 
145
  Give each new feature a stable ID, a goal, a status, desired behavior, and open decisions or dependencies. Link implementation work when it starts. Once shipped, mark the entry implemented and record any remaining scope rather than deleting its history.
146
+
147
+ ## Atlas research sections
148
+
149
+ Planned: add RefSeq annotation exploration and wet-lab results beneath the eukaryotic tree in the **Genome atlas** tab. Decide on the comparison views and experimental summaries when those data are ready. Accession lookup, segment viewing, and downloads now live in the separate **Data bank** tab.
README.md CHANGED
@@ -17,7 +17,7 @@ Future features and open design decisions are tracked in the [feature backlog](F
17
 
18
  ## GenBank taxonomy
19
 
20
- The landing page opens with an interactive **tree of eukaryotic life**, using NCBI's current GenBank assembly summary and taxonomy. Curved branches and nodes are colored by the fraction of assemblies with published annotations: gray for none found in the inventory, increasingly dark teal for higher percentages. Counts and percentages appear beside the groups, with details on hover. Annotation lookup sits below the graphic and searches its existing smaller index.
21
 
22
  The viewer is restricted to **Eukaryota (NCBI taxid 2759) and its descendants**, including search, breadcrumbs, and parent navigation. The model predicts eukaryotic annotations; bacteria, archaea, and viruses are outside this display. The current eukaryotic snapshot contains **70,395 assemblies**, of which **17,561 (24.9%)** have published annotations. The underlying SQLite snapshot retains the full NCBI inventory for refresh/provenance, while the interface uses only the eukaryotic subtree and its denominators. Assemblies with unresolved taxonomy cannot be assigned to that subtree and are excluded.
23
 
 
17
 
18
  ## GenBank taxonomy
19
 
20
+ The landing page opens with an interactive **tree of eukaryotic life**, using NCBI's current GenBank assembly summary and taxonomy. Curved branches and nodes are colored by the fraction of assemblies with published annotations: gray for none found in the inventory, increasingly dark teal for higher percentages. Counts and percentages appear beside the groups, with details on hover. The default **Genome atlas** tab contains the tree and its lineage controls. A separate **Data bank** tab holds accession search, segment visualization, and downloads, using the existing smaller index. This leaves the atlas view available for future RefSeq exploration and wet-lab results.
21
 
22
  The viewer is restricted to **Eukaryota (NCBI taxid 2759) and its descendants**, including search, breadcrumbs, and parent navigation. The model predicts eukaryotic annotations; bacteria, archaea, and viruses are outside this display. The current eukaryotic snapshot contains **70,395 assemblies**, of which **17,561 (24.9%)** have published annotations. The underlying SQLite snapshot retains the full NCBI inventory for refresh/provenance, while the interface uses only the eukaryotic subtree and its denominators. Assemblies with unresolved taxonomy cannot be assigned to that subtree and are excluded.
23
 
app.css CHANGED
@@ -35,3 +35,29 @@
35
  .gradio-container .action-button { width:100%; }
36
  }
37
  @media(prefers-reduced-motion:reduce) { .gradio-container .atlas-panel button { transition:none; } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  .gradio-container .action-button { width:100%; }
36
  }
37
  @media(prefers-reduced-motion:reduce) { .gradio-container .atlas-panel button { transition:none; } }
38
+
39
+ /* Two distinct views, with the same quiet atlas palette. */
40
+ #atlas-navigation { border:0; background:transparent; }
41
+ #atlas-navigation > .tab-wrapper > [role="tablist"] { gap:8px; border-bottom:1px solid #dbe3d3; padding:0 0 12px; margin-bottom:16px; }
42
+ #atlas-navigation > .tab-wrapper > [role="tablist"] > button { border:1px solid transparent; border-radius:999px; padding:10px 20px; color:#647467; font:500 14px/1.4 Arial,Helvetica,sans-serif; background:transparent; }
43
+ #atlas-navigation > .tab-wrapper > [role="tablist"] > button:hover { background:#edf3e7; color:#173c30; }
44
+ #atlas-navigation > .tab-wrapper > [role="tablist"] > button[aria-selected="true"] { color:#fffefa; background:#245d45; border-color:#245d45; }
45
+ #atlas-overview, #atlas-data-bank { border:0; background:transparent; padding:0; }
46
+ .data-bank-heading { padding:14px 4px 12px; }
47
+ .data-bank-heading .data-bank-eyebrow { color:#647467; font:11px/1.4 Arial,Helvetica,sans-serif; letter-spacing:2px; margin:0 0 12px; }
48
+ .data-bank-heading h1 { color:#173c30; font:400 clamp(32px,5vw,48px)/1.1 Georgia,'Times New Roman',serif; letter-spacing:-1px; margin:0 0 12px; }
49
+ .data-bank-heading p { color:#647467; font:14px/1.6 Arial,Helvetica,sans-serif; margin:0; }
50
+ @media(max-width:700px) {
51
+ #atlas-navigation > .tab-wrapper > [role="tablist"] { gap:6px; }
52
+ #atlas-navigation > .tab-wrapper > [role="tablist"] > button { padding:10px 16px; }
53
+ }
54
+
55
+ #atlas-navigation > .tab-wrapper { border-bottom:0; }
56
+ #atlas-navigation > .tab-wrapper > .tab-container > button { padding:10px 20px; font:500 14px/1.4 Arial,Helvetica,sans-serif; border-width:1px; }
57
+ @media(max-width:700px) { #atlas-navigation > .tab-wrapper > .tab-container > button { padding:10px 16px; } }
58
+ /* Prevent a loaded Plotly chart from widening the page after a viewport resize. */
59
+ .gradio-container { width:100%!important; min-width:0!important; box-sizing:border-box; }
60
+ #atlas-navigation, #atlas-overview, #atlas-data-bank { min-width:0; max-width:100%; }
61
+ #atlas-navigation > .tab-wrapper > .tab-container > button { min-height:44px; height:auto; box-sizing:border-box; }
62
+ #atlas-navigation > .tab-wrapper { height:auto; min-height:64px; }
63
+ #atlas-navigation > .tab-wrapper > .tab-container { height:auto; min-height:56px; align-items:center; }
app.py CHANGED
@@ -127,54 +127,60 @@ def build_app(catalog=None):
127
  return str(target)
128
 
129
  with gr.Blocks(title="GenBank Annotation Explorer", delete_cache=(3600, 3600)) as demo:
130
- build_taxonomy_tab()
131
- with gr.Column(elem_classes="atlas-panel"):
132
- gr.HTML(section_header("01", "Find an accession", "Start with an assembly or contig to explore its predicted coding regions."), apply_default_css=False)
133
- gr.Markdown(f"**{len(catalog.records):,} indexed segments** · **{len(catalog.manifest['assemblies']):,} assemblies** · "
134
- f"{catalog.manifest['bases']:,} bases\n\n"
135
- f"Search covers this prototype {scope}. Results are model predictions and assembly coverage may be partial.",
136
- elem_classes=["quiet-note", "scope-note"])
137
- with gr.Row():
138
- accession = gr.Textbox(label="Accession ID", placeholder="Assembly (GCA_…) or contig accession", scale=5)
139
- search_button = gr.Button("Find annotations", variant="primary", scale=1, elem_classes="action-button")
140
- examples = [first["assembly_accession"], first["record_name"]]
141
- if remote_mode:
142
- with closing(catalog.connect()) as conn:
143
- examples += [r[0] for r in conn.execute("SELECT record_name FROM segments WHERE id IN (SELECT min(id) FROM segments GROUP BY object_path)")]
144
- examples += ["JBPJTW010000350.1"]
145
- gr.Examples(examples=[[e] for e in dict.fromkeys(examples)], inputs=accession, label="Try an accession", elem_id="annotation-examples")
146
- status = gr.Markdown("Enter an accession or select an example. IDs are case-insensitive; version suffixes are optional.", elem_classes="quiet-note")
147
- results = gr.Dataframe(value=catalog.table([]), interactive=False, label="Matching segments", elem_id="annotation-results")
148
- segment = gr.Dropdown(choices=[], label="Segment to explore", interactive=True)
149
-
150
- with gr.Column(elem_classes="atlas-panel"):
151
- gr.HTML(section_header("02", "Explore the coding landscape", "View CDS probabilities or apply a threshold to see one label per base."), apply_default_css=False)
152
- with gr.Row():
153
- start = gr.Number(label="Start (0-based, inclusive)", precision=0)
154
- end = gr.Number(label="End (exclusive)", precision=0)
155
- view = gr.Button("Update region", elem_classes="action-button")
156
- with gr.Row():
157
- mode = gr.Radio(["Probabilities", "Binary labels"], value="Probabilities", label="Viewer mode")
158
- threshold = gr.Slider(0, 1, value=0.5, step=0.01, label="CDS threshold (max strand P > threshold)")
159
- plot = gr.Plot(label="CDS tracks", elem_id="annotation-plot")
160
- note = gr.Markdown("Choose a segment above to bring its coding landscape into view.", elem_classes="quiet-note")
161
- with gr.Accordion("Segment metadata and provenance", open=False, elem_classes="atlas-disclosure"):
162
- metadata = gr.JSON(label="Source metadata")
163
-
164
- with gr.Column(elem_classes="atlas-panel"):
165
- gr.HTML(section_header("03", "Take the annotations with you", "Download the original segment, with its accession and record name in the filename."), apply_default_css=False)
166
- with gr.Row():
167
- download = gr.Button("Prepare segment download", variant="primary", scale=1, elem_classes="action-button")
168
- file = gr.File(label="Original segment annotations (Parquet)", interactive=False, scale=3)
169
- gr.Markdown("Full per-base probabilities are preserved in the download, including when the viewer shows a binned overview.", elem_classes="quiet-note")
170
- with gr.Accordion("Browse the index and its sources", open=False, elem_classes="atlas-disclosure"):
171
- gr.Markdown(f"Showing the first {len(all_ids):,} indexed segments. Search an accession to find other indexed records. "
172
- "An indexed file does not imply complete coverage of its assembly.\n\n"
173
- "Source: [HuggingFaceBio/genbank-annotations](https://huggingface.co/buckets/HuggingFaceBio/genbank-annotations). "
174
- + (f"Annotations load on demand from {len(catalog.manifest['sources'])} bucket files. "
175
- f"Index updated {catalog.manifest['created_at'][:10]}." if remote_mode else "Offline sample."), elem_classes="quiet-note")
176
- gr.Dataframe(value=catalog.table(all_ids), interactive=False)
177
- gr.JSON(value=catalog.manifest, label="Index provenance" if remote_mode else "Sample provenance")
 
 
 
 
 
 
178
  gr.HTML('<footer class="workspace-footer"><span>Hugging Face Bio · Genome Atlas</span>'
179
  '<a href="https://huggingface.co/buckets/HuggingFaceBio/genbank-annotations" target="_blank" rel="noopener noreferrer">Explore the annotation collection ↗</a></footer>', apply_default_css=False)
180
  outputs = [status, results, segment, file]
 
127
  return str(target)
128
 
129
  with gr.Blocks(title="GenBank Annotation Explorer", delete_cache=(3600, 3600)) as demo:
130
+ with gr.Tabs(selected="atlas", elem_id="atlas-navigation"):
131
+ with gr.Tab("Genome atlas", id="atlas", elem_id="atlas-overview"):
132
+ build_taxonomy_tab()
133
+ with gr.Tab("Data bank", id="data-bank", elem_id="atlas-data-bank"):
134
+ gr.HTML('<header class="data-bank-heading"><p class="data-bank-eyebrow">THE ANNOTATION COLLECTION</p>'
135
+ '<h1>Explore the data bank</h1><p>Find an accession, explore its coding landscape, and download the annotations.</p></header>',
136
+ apply_default_css=False)
137
+ with gr.Column(elem_classes="atlas-panel"):
138
+ gr.HTML(section_header("01", "Find an accession", "Start with an assembly or contig to explore its predicted coding regions."), apply_default_css=False)
139
+ gr.Markdown(f"**{len(catalog.records):,} indexed segments** · **{len(catalog.manifest['assemblies']):,} assemblies** · "
140
+ f"{catalog.manifest['bases']:,} bases\n\n"
141
+ f"Search covers this prototype {scope}. Results are model predictions and assembly coverage may be partial.",
142
+ elem_classes=["quiet-note", "scope-note"])
143
+ with gr.Row():
144
+ accession = gr.Textbox(label="Accession ID", placeholder="Assembly (GCA_…) or contig accession", scale=5)
145
+ search_button = gr.Button("Find annotations", variant="primary", scale=1, elem_classes="action-button")
146
+ examples = [first["assembly_accession"], first["record_name"]]
147
+ if remote_mode:
148
+ with closing(catalog.connect()) as conn:
149
+ examples += [r[0] for r in conn.execute("SELECT record_name FROM segments WHERE id IN (SELECT min(id) FROM segments GROUP BY object_path)")]
150
+ examples += ["JBPJTW010000350.1"]
151
+ gr.Examples(examples=[[e] for e in dict.fromkeys(examples)], inputs=accession, label="Try an accession", elem_id="annotation-examples")
152
+ status = gr.Markdown("Enter an accession or select an example. IDs are case-insensitive; version suffixes are optional.", elem_classes="quiet-note")
153
+ results = gr.Dataframe(value=catalog.table([]), interactive=False, label="Matching segments", elem_id="annotation-results")
154
+ segment = gr.Dropdown(choices=[], label="Segment to explore", interactive=True)
155
+
156
+ with gr.Column(elem_classes="atlas-panel"):
157
+ gr.HTML(section_header("02", "Explore the coding landscape", "View CDS probabilities or apply a threshold to see one label per base."), apply_default_css=False)
158
+ with gr.Row():
159
+ start = gr.Number(label="Start (0-based, inclusive)", precision=0)
160
+ end = gr.Number(label="End (exclusive)", precision=0)
161
+ view = gr.Button("Update region", elem_classes="action-button")
162
+ with gr.Row():
163
+ mode = gr.Radio(["Probabilities", "Binary labels"], value="Probabilities", label="Viewer mode")
164
+ threshold = gr.Slider(0, 1, value=0.5, step=0.01, label="CDS threshold (max strand P > threshold)")
165
+ plot = gr.Plot(label="CDS tracks", elem_id="annotation-plot")
166
+ note = gr.Markdown("Choose a segment above to bring its coding landscape into view.", elem_classes="quiet-note")
167
+ with gr.Accordion("Segment metadata and provenance", open=False, elem_classes="atlas-disclosure"):
168
+ metadata = gr.JSON(label="Source metadata")
169
+
170
+ with gr.Column(elem_classes="atlas-panel"):
171
+ gr.HTML(section_header("03", "Take the annotations with you", "Download the original segment, with its accession and record name in the filename."), apply_default_css=False)
172
+ with gr.Row():
173
+ download = gr.Button("Prepare segment download", variant="primary", scale=1, elem_classes="action-button")
174
+ file = gr.File(label="Original segment annotations (Parquet)", interactive=False, scale=3)
175
+ gr.Markdown("Full per-base probabilities are preserved in the download, including when the viewer shows a binned overview.", elem_classes="quiet-note")
176
+ with gr.Accordion("Browse the index and its sources", open=False, elem_classes="atlas-disclosure"):
177
+ gr.Markdown(f"Showing the first {len(all_ids):,} indexed segments. Search an accession to find other indexed records. "
178
+ "An indexed file does not imply complete coverage of its assembly.\n\n"
179
+ "Source: [HuggingFaceBio/genbank-annotations](https://huggingface.co/buckets/HuggingFaceBio/genbank-annotations). "
180
+ + (f"Annotations load on demand from {len(catalog.manifest['sources'])} bucket files. "
181
+ f"Index updated {catalog.manifest['created_at'][:10]}." if remote_mode else "Offline sample."), elem_classes="quiet-note")
182
+ gr.Dataframe(value=catalog.table(all_ids), interactive=False)
183
+ gr.JSON(value=catalog.manifest, label="Index provenance" if remote_mode else "Sample provenance")
184
  gr.HTML('<footer class="workspace-footer"><span>Hugging Face Bio · Genome Atlas</span>'
185
  '<a href="https://huggingface.co/buckets/HuggingFaceBio/genbank-annotations" target="_blank" rel="noopener noreferrer">Explore the annotation collection ↗</a></footer>', apply_default_css=False)
186
  outputs = [status, results, segment, file]
taxonomy.py CHANGED
@@ -208,7 +208,7 @@ class Taxonomy:
208
 
209
 
210
  def build_taxonomy_tab():
211
- """Render the landing graphic before lookup (name retained for imports)."""
212
  if not DATABASE.exists():
213
  gr.Markdown("The eukaryotic tree will appear when the taxonomy snapshot is available.")
214
  return
@@ -248,7 +248,7 @@ def build_taxonomy_tab():
248
  "The tree layout is illustrative; **counts and colors use the actual assembly inventory**.\n\n"
249
  "Coverage means the fraction of current GenBank assembly versions with published annotations, including partial assemblies. "
250
  "It does not measure the fraction of bases annotated. Parent counts include descendants once each. "
251
- "Exact accession versions are matched. The accession lookup below currently searches a smaller indexed subset.\n\n"
252
  "Sources: [NCBI assembly summary](https://ftp.ncbi.nlm.nih.gov/genomes/ASSEMBLY_REPORTS/assembly_summary_genbank.txt), "
253
  "[NCBI Taxonomy](https://ftp.ncbi.nlm.nih.gov/pub/taxonomy/taxdump.tar.gz), "
254
  "and [published annotations](https://huggingface.co/buckets/HuggingFaceBio/genbank-annotations).")
 
208
 
209
 
210
  def build_taxonomy_tab():
211
+ """Render the tree and its controls inside the Genome atlas tab."""
212
  if not DATABASE.exists():
213
  gr.Markdown("The eukaryotic tree will appear when the taxonomy snapshot is available.")
214
  return
 
248
  "The tree layout is illustrative; **counts and colors use the actual assembly inventory**.\n\n"
249
  "Coverage means the fraction of current GenBank assembly versions with published annotations, including partial assemblies. "
250
  "It does not measure the fraction of bases annotated. Parent counts include descendants once each. "
251
+ "Exact accession versions are matched. The accession lookup in the Data bank tab currently searches a smaller indexed subset.\n\n"
252
  "Sources: [NCBI assembly summary](https://ftp.ncbi.nlm.nih.gov/genomes/ASSEMBLY_REPORTS/assembly_summary_genbank.txt), "
253
  "[NCBI Taxonomy](https://ftp.ncbi.nlm.nih.gov/pub/taxonomy/taxdump.tar.gz), "
254
  "and [published annotations](https://huggingface.co/buckets/HuggingFaceBio/genbank-annotations).")