altar-borzoi 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ *.pyc
2
+ *~
3
+ **/__pycache__/*
4
+ *.swp
5
+ .vscode/
6
+ .idea/
7
+ .DS_Store
8
+ .env*
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .pytest_cache/
12
+ .coverage
13
+ htmlcov/
14
+ coverage.xml
15
+ pytest.xml
16
+ .hypothesis/
17
+ .python-version
18
+ .venv
19
+ .venv-*/
20
+ *.egg-info/
21
+ build/
22
+ dist/
23
+ site/
24
+ *.sqlite3
25
+ *.log
26
+
27
+ # Generated variant indexes and datasets. The small canonical gene table is tracked.
28
+ altar/altar/variants/data/ccres.dnatree
29
+ altar/altar/variants/data/region_annotations.parquet
30
+ altar/altar/variants/data/variants.pkl.gz
31
+ altar/altar/variants/data/region_provenance.json
32
+ altar/altar/variants/data/raw/
33
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Riya Sinha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.5
2
+ Name: altar-borzoi
3
+ Version: 0.3.0
4
+ Summary: Borzoi model binding for large-scale Altar variant-gene scoring
5
+ Project-URL: Documentation, https://kundajelab.github.io/altar/
6
+ Author: Riya Sinha
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.12
18
+ Requires-Dist: altar<0.4,>=0.3.0
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'test'
21
+ Requires-Dist: pytest>=8; extra == 'test'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Altar Borzoi binding
25
+
26
+ `altar-borzoi` is the lightweight control-plane binding for large-scale, gene-specific Borzoi variant
27
+ scoring. It registers `BORZOI` in Altar's canonical model-plugin group without installing TensorFlow or the
28
+ model's scientific runtime into the framework environment.
29
+
30
+ One model instance pins the four published human replicate weights, official prediction parameters, the
31
+ GTEx RNA target subset, GENCODE 41 gene annotation, selected output tracks, reverse-complement/shift
32
+ ensembling, an immutable runtime image, and the hg38 reference identity. Each deterministic outer variant
33
+ batch becomes one retryable GPU task. Within that task the runtime loads each replicate exactly once,
34
+ streams small ref/alt tensor batches, and averages the official per-replicate scores through a disk-backed
35
+ accumulator.
36
+
37
+ The primary table contains the gene/target pair with the largest absolute `logSED` for each variant. The
38
+ `gene_effects` detail table preserves mean `logSED` and `logD2` for every scored `(variant, gene, target)`
39
+ tuple. The `altar-borzoi-resources` command fetches and records the published resources. The TensorFlow 2.15
40
+ executor lives under `runtimes/borzoi`, and `DEFAULT_IMAGE` pins its published image by digest.
@@ -0,0 +1,17 @@
1
+ # Altar Borzoi binding
2
+
3
+ `altar-borzoi` is the lightweight control-plane binding for large-scale, gene-specific Borzoi variant
4
+ scoring. It registers `BORZOI` in Altar's canonical model-plugin group without installing TensorFlow or the
5
+ model's scientific runtime into the framework environment.
6
+
7
+ One model instance pins the four published human replicate weights, official prediction parameters, the
8
+ GTEx RNA target subset, GENCODE 41 gene annotation, selected output tracks, reverse-complement/shift
9
+ ensembling, an immutable runtime image, and the hg38 reference identity. Each deterministic outer variant
10
+ batch becomes one retryable GPU task. Within that task the runtime loads each replicate exactly once,
11
+ streams small ref/alt tensor batches, and averages the official per-replicate scores through a disk-backed
12
+ accumulator.
13
+
14
+ The primary table contains the gene/target pair with the largest absolute `logSED` for each variant. The
15
+ `gene_effects` detail table preserves mean `logSED` and `logD2` for every scored `(variant, gene, target)`
16
+ tuple. The `altar-borzoi-resources` command fetches and records the published resources. The TensorFlow 2.15
17
+ executor lives under `runtimes/borzoi`, and `DEFAULT_IMAGE` pins its published image by digest.
@@ -0,0 +1,33 @@
1
+ # Copyright 2026 Riya Sinha
2
+ """Public Borzoi binding exports."""
3
+
4
+ from altar_borzoi.plugin import (
5
+ DEFAULT_IMAGE,
6
+ GTEX_TARGET_COUNT,
7
+ GTEX_TARGET_MAX,
8
+ GTEX_TARGET_MIN,
9
+ KIND_SCORING,
10
+ OUTPUT_BIN_SIZE,
11
+ OUTPUT_BINS,
12
+ SEQUENCE_LENGTH,
13
+ BorzoiConfiguration,
14
+ BorzoiPlugin,
15
+ BorzoiRunInputs,
16
+ BorzoiShardKind,
17
+ )
18
+
19
+
20
+ __all__ = [
21
+ "DEFAULT_IMAGE",
22
+ "GTEX_TARGET_COUNT",
23
+ "GTEX_TARGET_MAX",
24
+ "GTEX_TARGET_MIN",
25
+ "KIND_SCORING",
26
+ "OUTPUT_BINS",
27
+ "OUTPUT_BIN_SIZE",
28
+ "SEQUENCE_LENGTH",
29
+ "BorzoiConfiguration",
30
+ "BorzoiPlugin",
31
+ "BorzoiRunInputs",
32
+ "BorzoiShardKind",
33
+ ]
@@ -0,0 +1,290 @@
1
+ # Copyright 2026 Riya Sinha
2
+ """Backend-neutral Borzoi binding for gene-specific variant scoring."""
3
+
4
+ from __future__ import annotations
5
+ import enum
6
+ from typing import TYPE_CHECKING, Any, cast
7
+
8
+ from altar.execution import GpuPool, ResourceRequest
9
+ from altar.models import (
10
+ ApiVersionRange,
11
+ ContainerImageDigest,
12
+ ContainerScoringPlan,
13
+ DetailSchema,
14
+ ModelConfiguration,
15
+ ModelPlugin,
16
+ OutputSelection,
17
+ PluginCapability,
18
+ PluginKind,
19
+ PluginManifest,
20
+ PrioritizationPolicy,
21
+ ReferenceGenomeRunInputs,
22
+ ResourceReference,
23
+ ResultField,
24
+ ResultSchema,
25
+ RuntimeIdentityPolicy,
26
+ RuntimeKind,
27
+ RuntimeRequirement,
28
+ ScoringRequest,
29
+ VariantClass,
30
+ VariantEligibility,
31
+ require_resource_digests,
32
+ require_strictly_increasing,
33
+ resource_transfer,
34
+ single_container_plan,
35
+ )
36
+ from altar.predicates import Abs, Col, Ge, Lit, Predicate
37
+ from pydantic import Field, model_validator
38
+
39
+ from altar_borzoi.resources import manifest_resources
40
+
41
+
42
+ if TYPE_CHECKING:
43
+ import os
44
+
45
+
46
+ SEQUENCE_LENGTH = 524_288
47
+ OUTPUT_BIN_SIZE = 32
48
+ OUTPUT_BINS = 16_352
49
+ GTEX_TARGET_MIN = 7_522
50
+ GTEX_TARGET_MAX = 7_610
51
+ GTEX_TARGET_COUNT = GTEX_TARGET_MAX - GTEX_TARGET_MIN + 1
52
+ PUBLISHED_REPLICATE_COUNT = 4
53
+ MIN_SHIFT = -3
54
+ MAX_SHIFT = 3
55
+ DEFAULT_IMAGE = "riyavsinha/altar-borzoi@sha256:12872cd13da71705857a1a00e47901c244086aefcd5f6b63ae47474f5e94ee9a"
56
+
57
+
58
+ class BorzoiShardKind(enum.StrEnum):
59
+ """Stable execution kind emitted by :class:`BorzoiPlugin`."""
60
+
61
+ SCORING = "borzoi_scoring"
62
+
63
+
64
+ KIND_SCORING = BorzoiShardKind.SCORING
65
+ _SCORE_RESOURCES = ResourceRequest(gpu=GpuPool.NORMAL_GPU, count=1, memory_gb=24)
66
+ _LOG_SED_PRIORITIZE_THRESHOLD = 0.5
67
+
68
+
69
+ class BorzoiConfiguration(ModelConfiguration):
70
+ """Immutable official Borzoi ensemble and gene-expression scoring settings."""
71
+
72
+ weights: tuple[ResourceReference, ...] = Field(min_length=PUBLISHED_REPLICATE_COUNT, max_length=4)
73
+ parameters: ResourceReference
74
+ target_metadata: ResourceReference
75
+ gene_annotation: ResourceReference
76
+ target_indices: tuple[int, ...] = Field(min_length=1)
77
+ reverse_complement: bool = True
78
+ shifts: tuple[int, ...] = (0,)
79
+ image: ContainerImageDigest = DEFAULT_IMAGE
80
+
81
+ @model_validator(mode="after")
82
+ def validate_resources_and_selection(self) -> BorzoiConfiguration:
83
+ require_resource_digests(self, owner="Borzoi")
84
+ weight_digests = [resource.digest for resource in self.weights]
85
+ if len(set(weight_digests)) != PUBLISHED_REPLICATE_COUNT:
86
+ msg = "Borzoi requires four distinct published replicate weight digests"
87
+ raise ValueError(msg)
88
+ require_strictly_increasing(
89
+ self.target_indices,
90
+ owner="Borzoi",
91
+ field="target_indices",
92
+ minimum=GTEX_TARGET_MIN,
93
+ maximum=GTEX_TARGET_MAX,
94
+ )
95
+ require_strictly_increasing(self.shifts, owner="Borzoi", field="shifts", minimum=MIN_SHIFT, maximum=MAX_SHIFT)
96
+ return self
97
+
98
+ @classmethod
99
+ def from_resource_manifest(
100
+ cls, path: str | os.PathLike[str], *, base_uri: str | None = None, **settings: Any
101
+ ) -> BorzoiConfiguration:
102
+ """Build a configuration from the ``borzoi.resources.json`` that ``altar-borzoi-resources`` wrote.
103
+
104
+ ``settings`` are the remaining configuration fields: ``target_indices``, and the optional
105
+ ``reverse_complement``, ``shifts`` and ``image``. See :func:`altar_borzoi.resources.manifest_resources`
106
+ for how ``base_uri`` locates the files.
107
+ """
108
+ resources = manifest_resources(path, base_uri=base_uri)
109
+ return cls(
110
+ weights=resources.weights,
111
+ parameters=resources.parameters,
112
+ target_metadata=resources.target_metadata,
113
+ gene_annotation=resources.gene_annotation,
114
+ **settings,
115
+ )
116
+
117
+
118
+ class BorzoiRunInputs(ReferenceGenomeRunInputs):
119
+ """Per-run Borzoi inputs, restricted to the published human assembly."""
120
+
121
+ supported_genome_builds = frozenset({"hg38"})
122
+
123
+
124
+ class BorzoiPlugin(ModelPlugin):
125
+ """Predicts how a variant changes the RNA-seq coverage of nearby genes in the tracks you select (logSED)."""
126
+
127
+ model_type = "BORZOI"
128
+ manifest = PluginManifest(
129
+ plugin_id="org.kundajelab.altar.model.borzoi",
130
+ kind=PluginKind.MODEL,
131
+ capabilities=frozenset({PluginCapability.SCORE, PluginCapability.MATERIALIZE_RESULTS}),
132
+ architecture="Borzoi",
133
+ plugin_version="0.0.2",
134
+ altar_api=ApiVersionRange("0.1.0", "0.2.0"),
135
+ configuration_schema_version="0.0.1",
136
+ result_schema=ResultSchema(
137
+ version="0.0.2",
138
+ fields=(
139
+ ResultField("top_gene_id", "str", "Top Gene", "Gene with largest absolute mean logSED"),
140
+ ResultField("top_gene_name", "str", "Top Gene Name", "GENCODE name of the top gene"),
141
+ ResultField("top_gene_strand", "str", "Top Gene Strand", "GENCODE strand of the top gene"),
142
+ ResultField("top_target_index", "int", "Top Target Index", "Original Borzoi output index"),
143
+ ResultField("top_target_id", "str", "Top Target", "Identifier of the top GTEx output"),
144
+ ResultField("top_target_label", "str", "Top Target Label", "Tissue label of the top output"),
145
+ ResultField("top_log_sed", "float", "Top logSED", "Mean signed gene expression log2 fold change"),
146
+ ResultField("top_log_d2", "float", "Top logD2", "Mean bin-level log-profile L2 distance"),
147
+ ResultField("n_genes_scored", "int", "Genes Scored", "Genes with exons in the prediction window"),
148
+ ResultField("n_effects_scored", "int", "Effects Scored", "Variant-gene-target effects emitted"),
149
+ ResultField("n_replicates", "int", "Replicates", "Published model replicates averaged"),
150
+ ),
151
+ ),
152
+ detail_schemas=(
153
+ DetailSchema(
154
+ name="gene_effects",
155
+ version="0.0.1",
156
+ description="Mean official Borzoi logSED/logD2 for each variant-gene-GTEx target tuple.",
157
+ fields=(
158
+ ResultField("gene_id", "str", "Gene ID", "GENCODE gene identifier", False),
159
+ ResultField("gene_name", "str", "Gene Name", "GENCODE gene name", False),
160
+ ResultField("gene_strand", "str", "Gene Strand", "GENCODE gene strand", False),
161
+ ResultField("target_index", "int", "Target Index", "Original Borzoi output index", False),
162
+ ResultField("target_id", "str", "Target ID", "GTEx experiment identifier", False),
163
+ ResultField("target_label", "str", "Target Label", "GTEx tissue label", False),
164
+ ResultField("log_sed", "float", "logSED", "Mean signed expression log2 fold change", False),
165
+ ResultField("log_d2", "float", "logD2", "Mean bin-level log-profile L2 distance", False),
166
+ ResultField("n_replicates", "int", "Replicates", "Published model replicates averaged", False),
167
+ ),
168
+ key_fields=("gene_id", "target_index"),
169
+ # The runtime sets n_effects_scored to the number of gene_effects rows it emits per variant, so a
170
+ # cached variant with no gene in its window (0) is complete, not a miss on every preparation.
171
+ row_count_field="n_effects_scored",
172
+ ),
173
+ ),
174
+ # The Borzoi runtime substitutes one reference base; batch preparation filters out indels and MNVs.
175
+ variant_eligibility=VariantEligibility(frozenset({VariantClass.SNV})),
176
+ prioritization=PrioritizationPolicy(name="borzoi-logsed-effect-v1", version="0.0.1"),
177
+ runtimes=(
178
+ RuntimeRequirement(
179
+ name="model_runtime",
180
+ kind=RuntimeKind.CONTAINER_IMAGE,
181
+ policy=RuntimeIdentityPolicy.DIGEST_REQUIRED,
182
+ ),
183
+ ),
184
+ )
185
+ configuration_schema_id = "org.kundajelab.altar.model.borzoi.configuration"
186
+ configuration_model = BorzoiConfiguration
187
+ run_input_model = BorzoiRunInputs
188
+ default_output = OutputSelection(outputs=("scores", "gene_effects"), reducer="official_logsed_logd2_mean_v1")
189
+
190
+ def scoring_runtime_identities(self, configuration: ModelConfiguration) -> dict[str, str]:
191
+ """Record the configured Borzoi image as the scoring plan's one runtime."""
192
+ return {"model_runtime": self.parse_configuration_as(configuration, BorzoiConfiguration).image}
193
+
194
+ def build_scoring_plan(self, request: ScoringRequest) -> ContainerScoringPlan:
195
+ configuration = self.parse_configuration_as(request.configuration, BorzoiConfiguration)
196
+ run_inputs = self.parse_run_inputs_as(request.run_inputs, BorzoiRunInputs)
197
+ layout, resolver = request.layout, request.resolver
198
+ weight_logicals = tuple(
199
+ layout.model_resource_file(request.model_id, f"borzoi.f{index}.h5")
200
+ for index in range(PUBLISHED_REPLICATE_COUNT)
201
+ )
202
+ parameters_logical = layout.model_resource_file(request.model_id, "params_pred.json")
203
+ targets_logical = layout.model_resource_file(request.model_id, "targets_gtex.txt")
204
+ genes_logical = layout.model_resource_file(request.model_id, "gencode41_basic_nort.gtf")
205
+ genome_logical = layout.genome_fasta(request.genome_label)
206
+ variants_logical = layout.model_variants_file(request.job_id, request.model_id, request.scoring_batch)
207
+ primary_logical = layout.scoring_result_file(request.job_id, request.model_id, request.scoring_batch)
208
+ details_logical = layout.detail_result_file(
209
+ request.job_id, request.model_id, "gene_effects", request.scoring_batch
210
+ )
211
+ command = ["borzoi-score", "score"]
212
+ for weight_logical, weight in zip(weight_logicals, configuration.weights, strict=True):
213
+ command += [
214
+ "--weight",
215
+ resolver.resolve(weight_logical),
216
+ "--weight-digest",
217
+ cast("str", weight.digest),
218
+ ]
219
+ command += [
220
+ "--parameters",
221
+ resolver.resolve(parameters_logical),
222
+ "--parameters-digest",
223
+ cast("str", configuration.parameters.digest),
224
+ "--target-metadata",
225
+ resolver.resolve(targets_logical),
226
+ "--target-metadata-digest",
227
+ cast("str", configuration.target_metadata.digest),
228
+ "--gene-annotation",
229
+ resolver.resolve(genes_logical),
230
+ "--gene-annotation-digest",
231
+ cast("str", configuration.gene_annotation.digest),
232
+ ]
233
+ for target_index in configuration.target_indices:
234
+ command += ["--target-index", str(target_index)]
235
+ if configuration.reverse_complement:
236
+ command.append("--reverse-complement")
237
+ for shift in configuration.shifts:
238
+ command += ["--shift", str(shift)]
239
+ command += [
240
+ "--genome",
241
+ resolver.resolve(genome_logical),
242
+ "--genome-digest",
243
+ cast("str", run_inputs.genome.digest),
244
+ "--variants",
245
+ resolver.resolve(variants_logical),
246
+ "--primary-output",
247
+ resolver.resolve(primary_logical),
248
+ "--detail-output",
249
+ resolver.resolve(details_logical),
250
+ ]
251
+ inputs = [
252
+ resource_transfer(weight, logical, locality_key=request.model_id)
253
+ for logical, weight in zip(weight_logicals, configuration.weights, strict=True)
254
+ ]
255
+ inputs.extend(
256
+ (
257
+ resource_transfer(configuration.parameters, parameters_logical, locality_key=request.model_id),
258
+ resource_transfer(configuration.target_metadata, targets_logical, locality_key=request.model_id),
259
+ resource_transfer(configuration.gene_annotation, genes_logical, locality_key=request.model_id),
260
+ resource_transfer(run_inputs.genome, genome_logical),
261
+ )
262
+ )
263
+ return single_container_plan(
264
+ self,
265
+ request,
266
+ kind=KIND_SCORING,
267
+ image=configuration.image,
268
+ command=command,
269
+ resources=_SCORE_RESOURCES,
270
+ inputs=inputs,
271
+ )
272
+
273
+ def prioritize_predicate(self) -> Predicate:
274
+ return Ge(Abs(Col("top_log_sed")), Lit(_LOG_SED_PRIORITIZE_THRESHOLD))
275
+
276
+
277
+ __all__ = [
278
+ "DEFAULT_IMAGE",
279
+ "GTEX_TARGET_COUNT",
280
+ "GTEX_TARGET_MAX",
281
+ "GTEX_TARGET_MIN",
282
+ "KIND_SCORING",
283
+ "OUTPUT_BINS",
284
+ "OUTPUT_BIN_SIZE",
285
+ "SEQUENCE_LENGTH",
286
+ "BorzoiConfiguration",
287
+ "BorzoiPlugin",
288
+ "BorzoiRunInputs",
289
+ "BorzoiShardKind",
290
+ ]
@@ -0,0 +1 @@
1
+