ZhiyuanChen commited on
Commit
10dcc8b
·
unverified ·
1 Parent(s): b29c024

implement regulatory-signal app

Browse files
Files changed (5) hide show
  1. .gitignore +449 -0
  2. .pre-commit-config.yaml +50 -0
  3. README.md +22 -6
  4. app.py +334 -0
  5. requirements.txt +6 -0
.gitignore ADDED
@@ -0,0 +1,449 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Ignore Visual Studio temporary files, build results, and
2
+ ## files generated by popular Visual Studio add-ons.
3
+ ##
4
+ ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5
+
6
+ # User-specific files
7
+ *.rsuser
8
+ *.suo
9
+ *.user
10
+ *.userosscache
11
+ *.sln.docstates
12
+
13
+ # User-specific files (MonoDevelop/Xamarin Studio)
14
+ *.userprefs
15
+
16
+ # Mono auto generated files
17
+ mono_crash.*
18
+
19
+ # Build results
20
+ [Dd]ebug/
21
+ [Dd]ebugPublic/
22
+ [Rr]elease/
23
+ [Rr]eleases/
24
+ x64/
25
+ x86/
26
+ [Ww][Ii][Nn]32/
27
+ [Aa][Rr][Mm]/
28
+ [Aa][Rr][Mm]64/
29
+ bld/
30
+ [Bb]in/
31
+ [Oo]bj/
32
+ [Ll]og/
33
+ [Ll]ogs/
34
+
35
+ # Visual Studio Code cache/options directory
36
+ .vscode/
37
+
38
+ # Visual Studio 2015/2017 cache/options directory
39
+ .vs/
40
+ # Uncomment if you have tasks that create the project's static files in wwwroot
41
+ #wwwroot/
42
+
43
+ # JetBrains
44
+ .idea/
45
+
46
+ # Visual Studio 2017 auto generated files
47
+ Generated\ Files/
48
+
49
+ # MSTest test Results
50
+ [Tt]est[Rr]esult*/
51
+ [Bb]uild[Ll]og.*
52
+
53
+ # NUnit
54
+ *.VisualState.xml
55
+ TestResult.xml
56
+ nunit-*.xml
57
+
58
+ # Build Results of an ATL Project
59
+ [Dd]ebugPS/
60
+ [Rr]eleasePS/
61
+ dlldata.c
62
+
63
+ # Benchmark Results
64
+ BenchmarkDotNet.Artifacts/
65
+
66
+ # .NET Core
67
+ project.lock.json
68
+ project.fragment.lock.json
69
+ artifacts/
70
+
71
+ # ASP.NET Scaffolding
72
+ ScaffoldingReadMe.txt
73
+
74
+ # StyleCop
75
+ StyleCopReport.xml
76
+
77
+ # Files built by Visual Studio
78
+ *_i.c
79
+ *_p.c
80
+ *_h.h
81
+ *.ilk
82
+ *.meta
83
+ *.obj
84
+ *.iobj
85
+ *.pch
86
+ *.pdb
87
+ *.ipdb
88
+ *.pgc
89
+ *.pgd
90
+ *.rsp
91
+ *.sbr
92
+ *.tlb
93
+ *.tli
94
+ *.tlh
95
+ *.tmp
96
+ *.tmp_proj
97
+ *_wpftmp.csproj
98
+ *.log
99
+ *.tlog
100
+ *.vspscc
101
+ *.vssscc
102
+ .builds
103
+ *.pidb
104
+ *.svclog
105
+ *.scc
106
+
107
+ # Chutzpah Test files
108
+ _Chutzpah*
109
+
110
+ # Visual C++ cache files
111
+ ipch/
112
+ *.aps
113
+ *.ncb
114
+ *.opendb
115
+ *.opensdf
116
+ *.sdf
117
+ *.cachefile
118
+ *.VC.db
119
+ *.VC.VC.opendb
120
+
121
+ # Visual Studio profiler
122
+ *.psess
123
+ *.vsp
124
+ *.vspx
125
+ *.sap
126
+
127
+ # Visual Studio Trace Files
128
+ *.e2e
129
+
130
+ # TFS 2012 Local Workspace
131
+ $tf/
132
+
133
+ # Guidance Automation Toolkit
134
+ *.gpState
135
+
136
+ # ReSharper is a .NET coding add-in
137
+ _ReSharper*/
138
+ *.[Rr]e[Ss]harper
139
+ *.DotSettings.user
140
+
141
+ # TeamCity is a build add-in
142
+ _TeamCity*
143
+
144
+ # DotCover is a Code Coverage Tool
145
+ *.dotCover
146
+
147
+ # AxoCover is a Code Coverage Tool
148
+ .axoCover/*
149
+ !.axoCover/settings.json
150
+
151
+ # Coverlet is a free, cross platform Code Coverage Tool
152
+ coverage*.json
153
+ coverage*.xml
154
+ coverage*.info
155
+
156
+ # Visual Studio code coverage results
157
+ *.coverage
158
+ *.coveragexml
159
+
160
+ # NCrunch
161
+ _NCrunch_*
162
+ .*crunch*.local.xml
163
+ nCrunchTemp_*
164
+
165
+ # MightyMoose
166
+ *.mm.*
167
+ AutoTest.Net/
168
+
169
+ # Web workbench (sass)
170
+ .sass-cache/
171
+
172
+ # Installshield output folder
173
+ [Ee]xpress/
174
+
175
+ # DocProject is a documentation generator add-in
176
+ DocProject/buildhelp/
177
+ DocProject/Help/*.HxT
178
+ DocProject/Help/*.HxC
179
+ DocProject/Help/*.hhc
180
+ DocProject/Help/*.hhk
181
+ DocProject/Help/*.hhp
182
+ DocProject/Help/Html2
183
+ DocProject/Help/html
184
+
185
+ # Click-Once directory
186
+ publish/
187
+
188
+ # Publish Web Output
189
+ *.[Pp]ublish.xml
190
+ *.azurePubxml
191
+ # Note: Comment the next line if you want to checkin your web deploy settings,
192
+ # but database connection strings (with potential passwords) will be unencrypted
193
+ *.pubxml
194
+ *.publishproj
195
+
196
+ # Microsoft Azure Web App publish settings. Comment the next line if you want to
197
+ # checkin your Azure Web App publish settings, but sensitive information contained
198
+ # in these scripts will be unencrypted
199
+ PublishScripts/
200
+
201
+ # NuGet Packages
202
+ *.nupkg
203
+ # NuGet Symbol Packages
204
+ *.snupkg
205
+ # The packages folder can be ignored because of Package Restore
206
+ **/[Pp]ackages/*
207
+ # except build/, which is used as an MSBuild target.
208
+ !**/[Pp]ackages/build/
209
+ # Uncomment if necessary however generally it will be regenerated when needed
210
+ #!**/[Pp]ackages/repositories.config
211
+ # NuGet v3's project.json files produces more ignorable files
212
+ *.nuget.props
213
+ *.nuget.targets
214
+
215
+ # Microsoft Azure Build Output
216
+ csx/
217
+ *.build.csdef
218
+
219
+ # Microsoft Azure Emulator
220
+ ecf/
221
+ rcf/
222
+
223
+ # Windows Store app package directories and files
224
+ AppPackages/
225
+ BundleArtifacts/
226
+ Package.StoreAssociation.xml
227
+ _pkginfo.txt
228
+ *.appx
229
+ *.appxbundle
230
+ *.appxupload
231
+
232
+ # Visual Studio cache files
233
+ # files ending in .cache can be ignored
234
+ *.[Cc]ache
235
+ # but keep track of directories ending in .cache
236
+ !?*.[Cc]ache/
237
+
238
+ # Others
239
+ ClientBin/
240
+ ~$*
241
+ *~
242
+ *.dbmdl
243
+ *.dbproj.schemaview
244
+ *.jfm
245
+ *.pfx
246
+ *.publishsettings
247
+ orleans.codegen.cs
248
+
249
+ # Including strong name files can present a security risk
250
+ # (https://github.com/github/gitignore/pull/2483#issue-259490424)
251
+ #*.snk
252
+
253
+ # Since there are multiple workflows, uncomment next line to ignore bower_components
254
+ # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
255
+ #bower_components/
256
+
257
+ # RIA/Silverlight projects
258
+ Generated_Code/
259
+
260
+ # Backup & report files from converting an old project file
261
+ # to a newer Visual Studio version. Backup files are not needed,
262
+ # because we have git ;-)
263
+ _UpgradeReport_Files/
264
+ Backup*/
265
+ UpgradeLog*.XML
266
+ UpgradeLog*.htm
267
+ ServiceFabricBackup/
268
+ *.rptproj.bak
269
+
270
+ # SQL Server files
271
+ *.mdf
272
+ *.ldf
273
+ *.ndf
274
+
275
+ # Business Intelligence projects
276
+ *.rdl.data
277
+ *.bim.layout
278
+ *.bim_*.settings
279
+ *.rptproj.rsuser
280
+ *- [Bb]ackup.rdl
281
+ *- [Bb]ackup ([0-9]).rdl
282
+ *- [Bb]ackup ([0-9][0-9]).rdl
283
+
284
+ # Microsoft Fakes
285
+ FakesAssemblies/
286
+
287
+ # GhostDoc plugin setting file
288
+ *.GhostDoc.xml
289
+
290
+ # Node.js Tools for Visual Studio
291
+ .ntvs_analysis.dat
292
+ node_modules/
293
+
294
+ # Visual Studio 6 build log
295
+ *.plg
296
+
297
+ # Visual Studio 6 workspace options file
298
+ *.opt
299
+
300
+ # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
301
+ *.vbw
302
+
303
+ # Visual Studio 6 auto-generated project file (contains which files were open etc.)
304
+ *.vbp
305
+
306
+ # Visual Studio 6 workspace and project file (working project files containing files to include in project)
307
+ *.dsw
308
+ *.dsp
309
+
310
+ # Visual Studio 6 technical files
311
+ *.ncb
312
+ *.aps
313
+
314
+ # Visual Studio LightSwitch build output
315
+ **/*.HTMLClient/GeneratedArtifacts
316
+ **/*.DesktopClient/GeneratedArtifacts
317
+ **/*.DesktopClient/ModelManifest.xml
318
+ **/*.Server/GeneratedArtifacts
319
+ **/*.Server/ModelManifest.xml
320
+ _Pvt_Extensions
321
+
322
+ # Paket dependency manager
323
+ .paket/paket.exe
324
+ paket-files/
325
+
326
+ # FAKE - F# Make
327
+ .fake/
328
+
329
+ # CodeRush personal settings
330
+ .cr/personal
331
+
332
+ # Python Tools for Visual Studio (PTVS)
333
+ __pycache__/
334
+ *.pyc
335
+
336
+ # Cake - Uncomment if you are using it
337
+ # tools/**
338
+ # !tools/packages.config
339
+
340
+ # Tabs Studio
341
+ *.tss
342
+
343
+ # Telerik's JustMock configuration file
344
+ *.jmconfig
345
+
346
+ # BizTalk build output
347
+ *.btp.cs
348
+ *.btm.cs
349
+ *.odx.cs
350
+ *.xsd.cs
351
+
352
+ # OpenCover UI analysis results
353
+ OpenCover/
354
+
355
+ # Azure Stream Analytics local run output
356
+ ASALocalRun/
357
+
358
+ # MSBuild Binary and Structured Log
359
+ *.binlog
360
+
361
+ # NVidia Nsight GPU debugger configuration file
362
+ *.nvuser
363
+
364
+ # MFractors (Xamarin productivity tool) working folder
365
+ .mfractor/
366
+
367
+ # Local History for Visual Studio
368
+ .localhistory/
369
+
370
+ # Visual Studio History (VSHistory) files
371
+ .vshistory/
372
+
373
+ # BeatPulse healthcheck temp database
374
+ healthchecksdb
375
+
376
+ # Backup folder for Package Reference Convert tool in Visual Studio 2017
377
+ MigrationBackup/
378
+
379
+ # Ionide (cross platform F# VS Code tools) working folder
380
+ .ionide/
381
+
382
+ # Fody - auto-generated XML schema
383
+ FodyWeavers.xsd
384
+
385
+ # VS Code files for those working on multiple tools
386
+ .vscode/*
387
+ !.vscode/settings.json
388
+ !.vscode/tasks.json
389
+ !.vscode/launch.json
390
+ !.vscode/extensions.json
391
+ *.code-workspace
392
+
393
+ # JetBrains
394
+ .idea/
395
+
396
+ # Local History for Visual Studio Code
397
+ .history/
398
+
399
+ # Windows Installer files from build outputs
400
+ *.cab
401
+ *.msi
402
+ *.msix
403
+ *.msm
404
+ *.msp
405
+
406
+ # JetBrains Rider
407
+ *.sln.iml
408
+
409
+ # version
410
+ **/_version.py
411
+
412
+ # Python Egg
413
+ *.egg-info/
414
+
415
+ # Tar
416
+ **/*.tar
417
+ **/*.tgz
418
+ **/*.txz
419
+ **/*.gz
420
+ **/*.xz
421
+
422
+ # Pickle
423
+ **/*.pickle
424
+ **/*.pkl
425
+
426
+ # Checkpoints
427
+ **/*.onnx
428
+ **/*.ckpt
429
+ **/*.safetensors
430
+ **/*.pth
431
+ **/*.pt
432
+ **/*.bin
433
+ **/*.msgpack
434
+ **/*.h5
435
+ **/*.ot
436
+
437
+ # Data
438
+ **/*.np
439
+ **/*.npy
440
+ **/*.npz
441
+ **/*.numpy
442
+ **/*.pd
443
+ **/*.pandas
444
+
445
+ # debug files
446
+ **/*debug*
447
+
448
+ # site
449
+ **/site
.pre-commit-config.yaml ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ default_language_version:
2
+ python: python3
3
+ repos:
4
+ - repo: https://github.com/PSF/black
5
+ rev: 25.12.0
6
+ hooks:
7
+ - id: black
8
+ args: [--safe, --quiet, --line-length=120]
9
+ - repo: https://github.com/PyCQA/isort
10
+ rev: 7.0.0
11
+ hooks:
12
+ - id: isort
13
+ name: isort
14
+ args: [--profile=black, --line-length=120]
15
+ - repo: https://github.com/PyCQA/flake8
16
+ rev: 7.3.0
17
+ hooks:
18
+ - id: flake8
19
+ args: [--max-line-length=120]
20
+ additional_dependencies:
21
+ - flake8-bugbear
22
+ - flake8-comprehensions
23
+ - flake8-simplify
24
+ - repo: https://github.com/asottile/pyupgrade
25
+ rev: v3.21.2
26
+ hooks:
27
+ - id: pyupgrade
28
+ args: [--keep-runtime-typing]
29
+ - repo: https://github.com/codespell-project/codespell
30
+ rev: v2.4.1
31
+ hooks:
32
+ - id: codespell
33
+ - repo: https://github.com/pre-commit/pre-commit-hooks
34
+ rev: v6.0.0
35
+ hooks:
36
+ - id: check-added-large-files
37
+ - id: check-ast
38
+ - id: check-builtin-literals
39
+ - id: check-case-conflict
40
+ - id: check-docstring-first
41
+ - id: check-json
42
+ - id: check-toml
43
+ - id: check-yaml
44
+ - id: debug-statements
45
+ - id: end-of-file-fixer
46
+ - id: fix-byte-order-marker
47
+ - id: mixed-line-ending
48
+ args: ["--fix=lf"]
49
+ - id: requirements-txt-fixer
50
+ - id: trailing-whitespace
README.md CHANGED
@@ -1,15 +1,31 @@
1
  ---
2
- title: Regulatory Signal Prediction
3
- emoji: 😻
4
  colorFrom: blue
5
- colorTo: pink
6
  sdk: gradio
7
  sdk_version: 6.14.0
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
11
  license: agpl-3.0
12
- short_description: Regulatory Signal Prediction
 
 
 
 
 
 
 
 
 
 
 
 
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
1
  ---
2
+ title: Regulatory Signal
3
+ emoji: 🧬
4
  colorFrom: blue
5
+ colorTo: indigo
6
  sdk: gradio
7
  sdk_version: 6.14.0
8
+ python_version: "3.13"
9
  app_file: app.py
10
  pinned: false
11
  license: agpl-3.0
12
+ suggested_hardware: t4-small
13
+ models:
14
+ - multimolecule/enformer
15
+ - multimolecule/basenji
16
+ - multimolecule/bpnet
17
+ - multimolecule/chrombpnet
18
+ - multimolecule/procapnet
19
+ tags:
20
+ - biology
21
+ - dna
22
+ - regulatory-genomics
23
+ - regulatory-signal
24
+ - multimolecule
25
  ---
26
 
27
+ Interactive regulatory signal scoring with MultiMolecule.
28
+
29
+ Choose track output for binned Enformer/Basenji-style genomic coverage tracks, or profile output for base-resolution BPNet-style signal profiles. Enter one DNA sequence or upload a single-record FASTA file, then inspect the signal table, run metadata, CSV/JSON downloads, and a Matplotlib line plot.
30
+
31
+ This Space intentionally stays sequence-relative: output bins and positions are plotted without genome-coordinate browser scope. IGV.js, Gosling, and pyGenomeTracks are better future options for coordinate-aware tracks once genomic intervals, references, and track metadata are part of the app.
app.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MultiMolecule
2
+ # Copyright (C) 2024-Present MultiMolecule
3
+
4
+ # This file is part of MultiMolecule.
5
+
6
+ # MultiMolecule is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # any later version.
10
+
11
+ # MultiMolecule is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ # For additional terms and clarifications, please refer to our License FAQ at:
20
+ # <https://multimolecule.danling.org/about/license-faq>.
21
+
22
+
23
+ from __future__ import annotations
24
+
25
+ import csv
26
+ import json
27
+ import tempfile
28
+ from functools import lru_cache
29
+ from pathlib import Path
30
+ from typing import Any
31
+
32
+ import gradio as gr
33
+ import matplotlib
34
+ import numpy as np
35
+ import pandas as pd
36
+ import torch
37
+ from transformers import pipeline
38
+
39
+ matplotlib.use("Agg")
40
+
41
+ import matplotlib.pyplot as plt # noqa: E402
42
+ import multimolecule # noqa: E402, F401 - registers MultiMolecule models and pipelines with Transformers
43
+ import multimolecule.io as mmio # noqa: E402
44
+
45
+ TRACK_TASK = "regulatory-track"
46
+ PROFILE_TASK = "regulatory-profile"
47
+
48
+ TASK_OPTIONS = {
49
+ "Track": TRACK_TASK,
50
+ "Profile": PROFILE_TASK,
51
+ }
52
+
53
+ TRACK_MODEL_OPTIONS = {
54
+ "Enformer": "multimolecule/enformer",
55
+ "Basenji": "multimolecule/basenji",
56
+ }
57
+ PROFILE_MODEL_OPTIONS = {
58
+ "BPNet": "multimolecule/bpnet",
59
+ "ChromBPNet": "multimolecule/chrombpnet",
60
+ "ProCapNet": "multimolecule/procapnet",
61
+ }
62
+
63
+ FASTA_SUFFIXES = {f".{suffix}" for suffix in mmio.FASTA}
64
+ DNA_ALPHABET = set("ACGTN")
65
+ DEFAULT_SEQUENCE = ("ACGT" * 529)[:2114]
66
+
67
+
68
+ def _device() -> int:
69
+ return 0 if torch.cuda.is_available() else -1
70
+
71
+
72
+ @lru_cache(maxsize=4)
73
+ def load_predictor(task: str, model_id: str):
74
+ return pipeline(task, model=model_id, device=_device())
75
+
76
+
77
+ def model_visibility(task_label: str):
78
+ is_track = TASK_OPTIONS[task_label] == TRACK_TASK
79
+ return gr.update(visible=is_track), gr.update(visible=not is_track)
80
+
81
+
82
+ def clean_sequence(sequence: str) -> str:
83
+ sequence = "".join(str(sequence).split()).upper()
84
+ if not sequence:
85
+ raise gr.Error("Sequence is empty.")
86
+ invalid = sorted(set(sequence) - DNA_ALPHABET)
87
+ if invalid:
88
+ raise gr.Error(f"DNA sequence can only contain A, C, G, T, and N. Found: {', '.join(invalid)}.")
89
+ return sequence
90
+
91
+
92
+ def load_input_file(input_file: Any):
93
+ if input_file is None:
94
+ return gr.update()
95
+
96
+ path = Path(getattr(input_file, "name", input_file))
97
+ if path.suffix.lower() not in FASTA_SUFFIXES:
98
+ raise gr.Error("Could not parse uploaded file. Supported formats: FASTA, FA, and FNA.")
99
+ try:
100
+ records = mmio.read_fasta_records(path)
101
+ except mmio.InvalidStructureFile as error:
102
+ raise gr.Error("Could not parse uploaded file. Supported formats: FASTA, FA, and FNA.") from error
103
+ if not records:
104
+ raise gr.Error(f"No FASTA records found in {path.name}.")
105
+ if len(records) > 1:
106
+ raise gr.Error(f"This demo supports one sequence at a time. Uploaded FASTA contains {len(records)} records.")
107
+ return clean_sequence(records[0].sequence)
108
+
109
+
110
+ def run_prediction(
111
+ task_label: str,
112
+ track_model_label: str,
113
+ profile_model_label: str,
114
+ sequence: str,
115
+ max_table_rows: int,
116
+ max_display_channels: int,
117
+ ):
118
+ task = TASK_OPTIONS[task_label]
119
+ model_options = TRACK_MODEL_OPTIONS if task == TRACK_TASK else PROFILE_MODEL_OPTIONS
120
+ model_label = track_model_label if task == TRACK_TASK else profile_model_label
121
+ model_id = model_options[model_label]
122
+ sequence = clean_sequence(sequence)
123
+
124
+ predictor = load_predictor(task, model_id)
125
+ result = predictor(sequence)
126
+ result = _unwrap_result(result)
127
+
128
+ rows_key = "tracks" if task == TRACK_TASK else "profile"
129
+ axis_name = "bin" if task == TRACK_TASK else "position"
130
+ signal_rows = result.get(rows_key)
131
+ if not isinstance(signal_rows, list) or not signal_rows:
132
+ raise gr.Error(f"The selected model did not return a non-empty `{rows_key}` signal table.")
133
+
134
+ output_sequence = str(result.get("sequence", sequence))
135
+ channels = _resolve_channels(result, signal_rows, axis_name)
136
+ max_table_rows = int(max_table_rows)
137
+ max_display_channels = int(max_display_channels)
138
+
139
+ table = _rows_to_table(signal_rows, channels, axis_name, max_table_rows, max_display_channels)
140
+ plot = _plot_signal(signal_rows, channels, axis_name, task_label, model_label, max_display_channels)
141
+
142
+ metadata = {
143
+ "task": task_label,
144
+ "pipeline_task": task,
145
+ "model": model_id,
146
+ "device": "cuda" if torch.cuda.is_available() else "cpu",
147
+ "input_length": len(sequence),
148
+ "output_sequence_length": len(output_sequence),
149
+ "axis": axis_name,
150
+ "signals": len(signal_rows),
151
+ "channels": len(channels),
152
+ "displayed_rows": min(max_table_rows, len(signal_rows)),
153
+ "displayed_channels": min(max_display_channels, len(channels)),
154
+ "coordinate_scope": "sequence-relative output bins/positions only",
155
+ }
156
+ csv_path, json_path = _write_result_files(
157
+ task=task,
158
+ model_id=model_id,
159
+ sequence=output_sequence,
160
+ rows_key=rows_key,
161
+ rows=signal_rows,
162
+ channels=channels,
163
+ axis_name=axis_name,
164
+ metadata=metadata,
165
+ )
166
+
167
+ return table, metadata, plot, csv_path, json_path
168
+
169
+
170
+ def _unwrap_result(result: Any) -> dict[str, Any]:
171
+ if isinstance(result, list):
172
+ if len(result) != 1:
173
+ raise gr.Error(f"Expected one prediction result, got {len(result)}.")
174
+ result = result[0]
175
+ if not isinstance(result, dict):
176
+ raise gr.Error(f"Expected a prediction dictionary, got {type(result).__name__}.")
177
+ return result
178
+
179
+
180
+ def _resolve_channels(result: dict[str, Any], rows: list[dict[str, Any]], axis_name: str) -> list[str]:
181
+ channels = result.get("channels")
182
+ if isinstance(channels, list) and channels:
183
+ return [str(channel) for channel in channels]
184
+ metadata_columns = {axis_name, "nucleotide"}
185
+ return [key for key in rows[0] if key not in metadata_columns]
186
+
187
+
188
+ def _rows_to_table(
189
+ rows: list[dict[str, Any]],
190
+ channels: list[str],
191
+ axis_name: str,
192
+ max_rows: int,
193
+ max_channels: int,
194
+ ) -> pd.DataFrame:
195
+ selected_channels = channels[:max_channels]
196
+ include_nucleotide = any("nucleotide" in row for row in rows[:max_rows])
197
+ columns = [axis_name]
198
+ if include_nucleotide:
199
+ columns.append("nucleotide")
200
+ columns.extend(selected_channels)
201
+
202
+ table = [{column: row.get(column) for column in columns} for row in rows[:max_rows]]
203
+ return pd.DataFrame.from_records(table, columns=columns)
204
+
205
+
206
+ def _plot_signal(
207
+ rows: list[dict[str, Any]],
208
+ channels: list[str],
209
+ axis_name: str,
210
+ task_label: str,
211
+ model_label: str,
212
+ max_channels: int,
213
+ ):
214
+ selected_channels = channels[:max_channels]
215
+ x = np.asarray([row.get(axis_name, index) for index, row in enumerate(rows)], dtype=float)
216
+
217
+ fig, ax = plt.subplots(figsize=(11, 4.5))
218
+ for channel in selected_channels:
219
+ y = np.asarray([row.get(channel, np.nan) for row in rows], dtype=float)
220
+ ax.plot(x, y, linewidth=1.1, label=_short_label(channel))
221
+
222
+ ax.set_title(f"{model_label} {task_label.lower()} signal")
223
+ ax.set_xlabel("Output bin (0-based)" if axis_name == "bin" else "Sequence position (0-based)")
224
+ ax.set_ylabel("Predicted signal")
225
+ ax.grid(alpha=0.25)
226
+ if selected_channels:
227
+ ax.legend(loc="upper right", fontsize="x-small", ncol=2 if len(selected_channels) > 4 else 1)
228
+ fig.tight_layout()
229
+ return fig
230
+
231
+
232
+ def _short_label(label: str, limit: int = 36) -> str:
233
+ if len(label) <= limit:
234
+ return label
235
+ return f"{label[: limit - 1]}..."
236
+
237
+
238
+ def _write_result_files(
239
+ *,
240
+ task: str,
241
+ model_id: str,
242
+ sequence: str,
243
+ rows_key: str,
244
+ rows: list[dict[str, Any]],
245
+ channels: list[str],
246
+ axis_name: str,
247
+ metadata: dict[str, Any],
248
+ ) -> tuple[str, str]:
249
+ columns = [axis_name]
250
+ if any("nucleotide" in row for row in rows):
251
+ columns.append("nucleotide")
252
+ columns.extend(channels)
253
+
254
+ csv_file = tempfile.NamedTemporaryFile("w", suffix=".csv", delete=False, newline="")
255
+ writer = csv.DictWriter(csv_file, fieldnames=columns, extrasaction="ignore")
256
+ writer.writeheader()
257
+ writer.writerows(rows)
258
+ csv_file.close()
259
+
260
+ payload = {
261
+ "task": task,
262
+ "model": model_id,
263
+ "sequence": sequence,
264
+ "channels": channels,
265
+ rows_key: rows,
266
+ "metadata": metadata,
267
+ }
268
+ json_file = tempfile.NamedTemporaryFile("w", suffix=".json", delete=False)
269
+ json.dump(payload, json_file, indent=2)
270
+ json_file.close()
271
+
272
+ return csv_file.name, json_file.name
273
+
274
+
275
+ with gr.Blocks(title="Regulatory Signal") as demo:
276
+ gr.Markdown(
277
+ "# Regulatory Signal\n"
278
+ "Run MultiMolecule DNA regulatory track and profile checkpoints and inspect sequence-relative signal outputs."
279
+ )
280
+
281
+ with gr.Row():
282
+ task = gr.Radio(
283
+ choices=list(TASK_OPTIONS.keys()),
284
+ value="Profile",
285
+ label="Task",
286
+ )
287
+ track_model = gr.Dropdown(
288
+ choices=list(TRACK_MODEL_OPTIONS.keys()),
289
+ value="Enformer",
290
+ label="Track checkpoint",
291
+ visible=False,
292
+ )
293
+ profile_model = gr.Dropdown(
294
+ choices=list(PROFILE_MODEL_OPTIONS.keys()),
295
+ value="BPNet",
296
+ label="Profile checkpoint",
297
+ )
298
+
299
+ sequence = gr.Textbox(
300
+ label="DNA sequence",
301
+ value=DEFAULT_SEQUENCE,
302
+ lines=5,
303
+ )
304
+ input_file = gr.File(
305
+ label="Upload FASTA",
306
+ file_types=[".fa", ".fasta", ".fna"],
307
+ )
308
+
309
+ with gr.Row():
310
+ max_table_rows = gr.Slider(10, 2000, value=200, step=10, label="Rows shown")
311
+ max_display_channels = gr.Slider(1, 24, value=8, step=1, label="Channels shown")
312
+ run = gr.Button("Run prediction", variant="primary")
313
+
314
+ with gr.Row():
315
+ signal_table = gr.Dataframe(label="Signal table", interactive=False, wrap=True)
316
+ metadata = gr.JSON(label="Run metadata")
317
+
318
+ signal_plot = gr.Plot(label="Signal plot")
319
+
320
+ with gr.Row():
321
+ csv_download = gr.File(label="Download CSV")
322
+ json_download = gr.File(label="Download JSON")
323
+
324
+ task.change(model_visibility, inputs=task, outputs=[track_model, profile_model])
325
+ input_file.change(load_input_file, inputs=input_file, outputs=sequence)
326
+ run.click(
327
+ run_prediction,
328
+ inputs=[task, track_model, profile_model, sequence, max_table_rows, max_display_channels],
329
+ outputs=[signal_table, metadata, signal_plot, csv_download, json_download],
330
+ )
331
+
332
+
333
+ if __name__ == "__main__":
334
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ matplotlib
2
+ multimolecule @ git+https://github.com/DLS5-Omics/multimolecule.git@master
3
+ numpy
4
+ pandas
5
+ torch
6
+ transformers