gain.genomic_resources.genomic_scores package
Submodules
gain.genomic_resources.genomic_scores.aggregation module
Reducing a region to one value per requested score.
The machinery the per-kind aggregating reads share –
get_scores_in_region_agg(),
get_allele_scores_in_region_agg() and
get_fragment_scores_overlapping_region_agg()
– apart from any score class: resolving a caller’s queries to
(score_id, aggregator) pairs, building a fresh aggregator for each, and
folding a stream of fetched segments into one value per request.
fold_into_bins() is the same reduction per grid bin rather than per
region: a dense block, one row a bin and one column an aggregator, with the
empty-bin rule (EMPTY_BIN_VALUES) stated there and nowhere else.
Resolving and building are separate steps here because they are separate
questions. score_def_for() and resolve_aggregator_name()
answer “is this request answerable, and by what”, which a caller may ask
without meaning to read – since gain#1131
resolve_aggregation_queries() asks exactly
that when an annotation pipeline loads, so a misconfigured attribute is
refused before any annotatable arrives. build_region_aggregator()
answers “give me somewhere to accumulate”, which only a read needs, and
which a read needs freshly every time.
Nothing here knows what a score KIND is. The one thing that differs
between kinds – how many times a record’s value counts – reaches
fold_region_segments() as weigh, the kind’s own
record_weight(). The fold applies what it is
handed and does not re-derive or cross-check it: the rule is stated once
per kind, on the kind (see
test_the_weight_rule_is_stated_once_per_kind). Whether the segments
were clipped to the query window before they got here is settled the same
way, by the kind’s own read, so the fold carries no flag saying which kind
it serves.
Every BACKEND feeds one path here – this generic weighted stream over
fetched records – and a backend-specific fast path was considered and
rejected to keep it that way; see
.out-of-scope/bigwig-stats-pushdown.md. That is a claim about
backends, not about the package: PositionScore folds
its own segments for the aggregated plane, and converging the two is
gain#1027’s remaining work, not this module’s promise.
Every refusal here is of a REQUEST, never of a definition: a score the
resource does not define, an aggregator a caller names that does not
build, a bool score asked for with no aggregator named. The
definitions a request is resolved against were held to their own rules
when the score was built (a scores: aggregator: that cannot be
built is refused there, through score_configuration_error), so by the
time a request arrives every definition is one the resource can reduce
by. That is why these refusals carry the request family’s address –
which score, of which resource – and not the configuration prefix: the
file to edit is the caller’s, and Invalid configuration: <resource>
would send the reader to a yaml that is fine. The undefined-score
sentence is not worded here: every surface that refuses that mistake
shares it, from
undefined_scores_message().
- AggregateValue = str | int | float | bool | None | list[str | int | float | bool | None] | dict[str | int | float | bool | None, int]
The value that one aggregator reduces a region to. The aggregator sets the type:
listandmost_commongive a list,value_countgives a dict that maps each value to its count, and every other aggregator gives a score value.
- EMPTY_BIN_VALUES: dict[str, float] = {'count': 0.0, 'max': nan, 'mean': nan, 'median': nan, 'min': nan, 'product': nan, 'sum': 0.0}
What a cell of
fold_into_bins()holds when its aggregator received no non-null value, per aggregator:0where nothing summed or counted IS zero,NaNwhere there is nothing to answer. The keys are also the whole set of aggregators the fold accepts – the ones whose answer is a float. This is the fragment surfaces’ rule (a bin with no fragment has nothing to fold), and it differs on purpose from the position-score binner’s, which stores NaN for an uncovered bin unless the track’snone_value_replacementsays otherwise; that key does not exist on the fragment kind. The two rules agree oncount: its aggregator answers0for no values, so the position-score binner stores 0.0 for an uncovered bin of acounttrack too.
- build_region_aggregator(score_id: str, aggregator: str, *, resource_id: str) Aggregator[source]
Build a FRESH aggregator, naming the resource if it cannot.
Fresh per call, not reused: an aggregator is a mutable accumulator and explicitly not thread-safe, and every caller in gain builds one per fold (see
Aggregator). A score holds none between reads.Aggregator.buildraises a bareKeyError('mediann')for an unknown name, saying nothing about which score asked for it. The name refused is the CALLER’s: a definition’s ownaggregator:was built once when the score was constructed (score_def.refuse_unbuildable_aggregators), so a resolved request never carries a definition-side spelling that fails here.
- build_region_aggregators(requests: Sequence[tuple[str, str]], *, resource_id: str) list[Aggregator][source]
One FRESH aggregator per request, parallel to the request list.
build_region_aggregator()over a resolved request list – the shape every folding read handsfold_region_segments(), built once per READ and never held on the score, for the reason that function gives. Fresh accumulators remove one hazard, not the class of them: two concurrent region reads of one open score still share the table’s line iterator, which is each read’s “one live region read at a time” rule and not this function’s promise.
- distinct_score_ids(score_ids: Iterable[str]) list[str][source]
The DISTINCT ids among
score_ids, in the order asked for.One fetch serves every aggregation request, so the same list must both name what is fetched and index the values that come back – which is why the ORDER is part of the answer, and why every aggregating read in this package derives it here. A second spelling that ordered the scores differently would not fail; it would have every aggregator quietly reading its neighbour’s column.
Takes the ids rather than the requests they came off, because a request is shaped differently on each surface – a pair here, a
PositionScoreAggregationQueryresolved to a triple on the position score’s plane – and none of that is what the derivation is about. Each surface projects its own shape at its call site; the request list gets a named projection,request_score_ids(), because it is the one shape TWO readers (the allele and fragment kinds’ folding reads) project, and each hands the result on to the fold.Note this is the aggregating reads’ derivation, not a package-wide one: nothing else in the package fetches one list and indexes it twice. The position annotator’s point read asks one score per attribute, a score named twice included, and pairs the answers back by position, so it has nothing to dedupe.
- fold_into_bins(records: Iterable[tuple[int, int, str | int | float | bool | None]], *, start: int, end: int, bin_size: int, aggregators: Sequence[str]) ndarray[source]
Fold position-sorted records into a dense block of global-grid bins.
recordsare(position, column, value): each value is added to a fresh aggregator of itscolumnin the bin holding itsposition.aggregatorsnames one aggregator per column, which is how a caller gives each column its meaning – one per query for a score’s binned read, one per group for a binner.The answer is a
float64array of shape(n_bins, n_columns). Rowiis bincalc_bin_index(bin_size, start) + iof the grid anchored at position 1, son_binsiscalc_bin_index(bin_size, end) - calc_bin_index(bin_size, start) + 1and blocks of adjacent regions split on a bin edge stack into the block of their union. Bins are not clipped and no bounds are returned: an edge row is a whole grid bin, holding only the records whose position lies in[start, end].The empty-bin rule. Null values are skipped, as every aggregator skips them, and a cell whose aggregator received no non-null value holds
EMPTY_BIN_VALUESfor that aggregator:0forcountandsum,NaNformean,max,min,medianandproduct. Those seven are the whole set this fold accepts; any other aggregator cannot answer a float and is refused before the first record is read, as is abin_sizebelow 1.Preconditions.
recordsare sorted by position, and every position lies in[start, end]. The fold holds only the current bin’s aggregators, so it consumes the stream once, in order; a record that breaks either precondition is refused when it is reached. Values are whatever the aggregators accept – numbers, or anything at all forcount.
- fold_region_segments(segments: Iterable[tuple[int, int, Sequence[str | int | float | bool | None]]], aggregators: list[Aggregator], requests: Sequence[tuple[str, str]], *, score_ids: Sequence[str], weigh: Callable[[int, int], int]) list[str | int | float | bool | None | list[str | int | float | bool | None] | dict[str | int | float | bool | None, int]][source]
Fold one region read into one value per request.
segmentsis a stream of(left, right, values)asfetch_region_segments_scores()yields it, withvaluespositional and parallel toscore_ids– the list the caller derived (request_score_ids()) to name what it fetched, handed on here so the fold indexes exactly the columns the fetch carried; one derivation cannot disagree with itself, where two (the fold once derived its own) had to be kept agreeing. That is how a request finds its column: two requests for one score share the fetch and keep separate accumulators. AnySequenceof values will do – the fold only ever indexesvalues[column]– so a kind that hands over tuples folds exactly as one that hands over lists.aggregatorsis parallel torequestsand built by the CALLER, which is what lets an invalid aggregator name be refused before the region is read at all: a read that refuses an unknown contig when it is called would otherwise mask a misspelled aggregator behind that complaint.weighis the caller’s per-kind weight rule, applied as handed: it turns a segment’s span into the number of times that record’s value counts. It is not second-guessed here, and neither is the stream – whether a record was first cut down to the query window is the caller’s business, settled before the segments arrive. A kind that counts a record once counts it wherever the point it collapses to falls, window or not.
- request_score_ids(requests: list[tuple[str, str]]) list[str][source]
distinct_score_ids()of a request list’s scores.Derived ONCE per aggregating read and handed to both ends of it: the reader names the scores to fetch with it and passes the same list to
fold_region_segments()asscore_ids, which says why one list serves both. The position score’s plane projects its own shape inline instead – one reader, nothing to hand on.
- resolve_aggregation_queries(queries: Sequence[ScoreAggregationQuery] | None, *, score_definitions: dict[str, GenomicScoreDef], all_scores: list[str], resource_id: str) list[tuple[str, str]][source]
Resolve kind-neutral queries to
(score_id, aggregator NAME)pairs.Asks the two questions every aggregation request asks (
score_def_for(),resolve_aggregator_name()), and expandsNoneto every score with its own default. Stops at the NAME, asresolve_aggregation_queries()does, so a caller can ask whether a query list is answerable without reading – an annotator asks exactly that as its pipeline loads. Building the accumulators is the READ’s business, per call (build_region_aggregator()).Shared by the fragment and allele folding reads, which is what promoted it here from the fragment kind: this package keeps a derivation on the one kind that needs it and moves it here when a second does. The position kind keeps its own resolver because its query carries a third field,
none_value_replacement, that has to be judged between the two questions – see that method for why the two must not be merged. A pair has nowhere to put that field, and the signature keeps its query out: aPositionScoreAggregationQueryis a sibling of the neutral query, not a subclass, so it does not type-check here – seeScoreAggregationQueryfor why (gain#1302).
- resolve_aggregator_name(aggregator: str | None, score_def: GenomicScoreDef, *, resource_id: str) str[source]
The aggregator to reduce a score with: the caller’s, else its own.
The second question, and the one statement of the rule that a score with neither is refused. Every surface takes the aggregator as a field on its query, so every refusal ends with
QUERY_AGGREGATOR_REMEDY. The neutral resolver and the position score’s resolver share the whole sentence, andtest_both_surfaces_state_the_missing_default_rule_identicallypins that.The score is named by
score_defrather than beside it:score_definitionsis keyed byscore_idat every construction path, so a separate argument would be one the caller could contradict.
- score_def_for(score_id: str, *, score_definitions: dict[str, GenomicScoreDef], resource_id: str) GenomicScoreDef[source]
The definition an aggregation request names, refusing an unknown one.
The first of the two questions every aggregation request asks, and the one place aggregation refuses when the answer is no. Whether the request arrived as a bare score id, as a
(score_id, aggregator)pair, as aScoreAggregationQueryor as aPositionScoreAggregationQuerychanges nothing about it: the resource either defines that score or it does not, and the caller is told which ones it has either way.
gain.genomic_resources.genomic_scores.allele module
AlleleScore – one value per (position, ref, alt) allele.
The kind keyed by a variant rather than a position, in either of two modes
(substitutions and alleles). Its reads widen the shared batch with
the two key columns a position row does not have – see
AlleleRecordArrays for why that widening is a slice of
the shared type rather than a separate one.
- class AlleleAggregate(values: tuple[str | int | float | bool | None | list[str | int | float | bool | None] | dict[str | int | float | bool | None, int], ...], allele_keys: tuple[str, ...] | None)[source]
Bases:
objectThe answer of a folding read: one value per query, and the allele keys.
- Variables:
values (tuple[AggregateValue, ...]) – One value for each query of the read, in the order asked. A query is a score with an aggregator, so one score asked with two aggregators gives two values. The aggregator sets the type of its value:
listgives a list andvalue_countgives a dict.allele_keys (tuple[str, ...] | None) – The distinct allele keys of the rows that were folded, in the order the rows were first seen. The value is
Nonewhen the read was not asked for keys. Seeallele_key()for the format.
- allele_keys: tuple[str, ...] | None
- values: tuple[str | int | float | bool | None | list[str | int | float | bool | None] | dict[str | int | float | bool | None, int], ...]
- class AlleleEntry(pos: int, ref: str | None, alt: str | None, values: tuple[ScoreValue, ...])[source]
Bases:
NamedTupleOne allele row of a region, as the unreduced region read yields it.
- Variables:
pos (int) – The position of the row.
ref (str | None) – The reference nucleotides of the row, as the table holds them. The value is
Nonewhen the table has no reference column.alt (str | None) – The alternative nucleotides of the row, as the table holds them. The value is
Nonewhen the table has no alternative column.values (tuple[ScoreValue, ...]) – The values of the scores that the read was asked for, in the order asked.
- alt: str | None
Alias for field number 2
- pos: int
Alias for field number 0
- ref: str | None
Alias for field number 1
- values: tuple[str | int | float | bool | None, ...]
Alias for field number 3
- exception AlleleMultiplicityError(resource_id: str, detail: str, *, allele: tuple[str, int, str | None, str | None] | None = None)[source]
Bases:
ValueErrorThe rows of an allele score disagree with its
allele_multiplicity.The message is
detailwith a prefix: the resource id and, when the refusal concerns one allele, the allele spelled byallele_key():AlleleMultiplicityError( "scores/x", "holds 2 rows but declares allele_multiplicity: one", allele=("1", 10, "A", "G")) # allele score 'scores/x', allele 1:10:A:G: holds 2 rows ...
- Variables:
resource_id (str) – The id of the resource that the error is about.
allele (tuple[str, int, str | None, str | None] | None) – The allele as
(chrom, pos, ref, alt). The value isNonewhen the refusal does not concern one allele.detail (str) – The message without the prefix.
- class AlleleScore(resource: GenomicResource)[source]
Bases:
GenomicScoreAllele-specific genomic score resource.
An AlleleScore provides scores that depend on specific alleles at genomic positions. Unlike PositionScore, AlleleScore considers both the reference and alternative alleles when computing scores. This makes it suitable for variant-specific predictions and annotations.
AlleleScore supports two operational modes:
SUBSTITUTIONS mode: Scores are specific to nucleotide substitutions (e.g., A>T, C>G). This mode is optimized for single nucleotide variants and considers the directionality of the change. Used by resources like CADD, which provide substitution-specific scores.
ALLELES mode: Scores are associated with specific alleles at positions, without considering the reference allele. This mode supports insertions, deletions, and more complex variants. The score depends on the alternative allele itself rather than the substitution pattern.
Typical use cases include: - Variant pathogenicity scores (e.g., CADD, DANN) - Functional impact predictions (e.g., PolyPhen, SIFT scores) - Splice site predictions - Regulatory variant scores - Any metric that depends on specific alleles
The score data is typically stored in VCF files or tabix-indexed tables with reference and alternative allele columns.
Example
>>> from gain.genomic_resources.repository_factory import ( ... build_genomic_resource_repository ... ) >>> repo = build_genomic_resource_repository() >>> resource = repo.get_resource("cadd_v1_6") >>> score = build_score_from_resource(resource) >>> with score.open() as score: ... # Fetch the scores of a specific variant's row ... values = score.get_allele_scores_for_allele( ... "chr1", 12345, "A", "T" ... ) ... # Iterate over the alleles in a region. The nucleotides ... # come off the record; the values come off the score. ... for record in score.fetch_records("chr1", 10000, 20000): ... values = score.get_score_values_from_record( ... record, score_defs ... ) ... print(f"{record[POS_BEGIN]} " ... f"{record[REF]}>{record[ALT]}: {values}")
Reducing those values over a region is the resource’s own job:
get_allele_scores_in_region_agg()folds a region in one streaming walk, one value perScoreAggregationQuery, with the allele keys beside them when asked, and the allele annotator’s region mode reads through it (gain.annotation.allele_score_annotator).No read consults the mode: an exact-match read answers an indel’s own line in either mode. The mode is for the caller to route on, and the allele score annotator does – on a
substitutionsresource it folds any allele other than a substitution over its span instead of matching it.- Variables:
resource (gain.genomic_resources.repository.GenomicResource) – The underlying GenomicResource object
resource_id – Unique identifier for the resource
config (dict[str, Any]) – Configuration dictionary for the score
table – GenomicPositionTable for data access (typically VCF)
score_definitions (dict[str, ScoreDefT]) – Dictionary mapping score IDs to their definitions
mode – Operating mode (SUBSTITUTIONS or ALLELES)
- Key Methods:
get_allele_scores_for_allele: Get the values of one allele’s only row, on a resource declaring one row per allele get_allele_score_for_allele: The same for one score get_allele_scores_in_region_agg: Reduce the alleles of a region to one value per query – and their keys – in one walk, telling the same two answers apart get_allele_scores_for_allele_agg: Reduce the rows of one allele to one value per query, telling an allele no row holds apart from one whose rows were all rejected get_allele_score_for_allele_agg: The same for one score get_allele_scores_for_allele_rows: Get the values of every row of one allele, unreduced get_allele_score_for_allele_rows: The same for one score get_allele_scores_in_region_rows: Stream an AlleleEntry per row of a region, unreduced, off the walk the folding read reduces get_allele_score_in_region_rows: The same for one score, as
(pos, ref, alt, value)fetch_region_segments_scores: Iterate over allele scores in a genomic region substitutions_mode: Check if operating in SUBSTITUTIONS mode alleles_mode: Check if operating in ALLELES mode- Configuration:
The resource configuration should specify:
table.filename: Path to the data file (usually VCF)
table.reference: Column/field containing reference alleles
table.alternative: Column/field containing alternative alleles
allele_score_mode: Either “substitutions” or “alleles” (optional)
allele_multiplicity: Either “one” (the default) or “many” – how many table rows one allele key may have (optional)
scores: List of score definitions with an optional aggregator specification
- DEFAULT_AGGREGATORS: ClassVar[dict[str, str | None]] = {'bool': None, 'float': 'max', 'int': 'max', 'str': 'list'}
The aggregator that a query uses when it names none, by the value type of the score:
maxfor numbers,listfor strings and none for booleans.
- HISTOGRAM_COUNT_UNIT: ClassVar[str] = 'alleles'
"alleles".- Type:
The unit that the histograms of this kind count
- class Mode(*values)[source]
Bases:
EnumThe allele score mode, as the resource declares it.
SUBSTITUTIONSis the modesubstitutionsandALLELESis the modealleles. No read consults the mode. The allele score annotator uses it to route an allele.- ALLELES = 2
- SUBSTITUTIONS = 1
- class Multiplicity(*values)[source]
Bases:
EnumHow many table rows one allele may have.
The resource declares it with the top-level key
allele_multiplicity.ONEis the default.MANYmeans that several rows may hold one(chrom, pos, ref, alt), for example one row for each transcript.- MANY = 'many'
- ONE = 'one'
- alleles_mode() bool[source]
Return whether the mode of the resource is
alleles.
- fetch_region_allele_arrays(chrom: str, pos_begin: int | None, pos_end: int | None, scores: list[str], *, batch_size: int = 100000) Generator[AlleleRecordArrays, None, None][source]
Fetch a region as column arrays, with the nucleotides.
The read widens
fetch_region_value_arrays()with thereferenceandalternativecolumns. It is meant for a caller that scans a whole region for allele content, such as the allele statistics.The nucleotides are raw and the scores are parsed. An NA sentinel of a score arrives as the non-value of the score. The two nucleotide columns go through no parsing: no upper-casing, no stripping and no sentinel handling. A caller that wants them normalised does it once, where the meaning is known.
- Parameters:
chrom (str) – The chromosome name.
pos_begin (int or None) – The first position of the region, 1-based.
Nonemeans the start of the chromosome.pos_end (int or None) – The last position of the region.
Nonemeans the end of the chromosome.scores (list[str]) – The score ids to read.
batch_size (int, optional) – The most rows in a batch. The reference and alternative columns count toward
VALUE_ARRAYS_CELL_BUDGETlike two scores, so a batch holds at mostbudget // (len(scores) + 2)rows. The default is 100000.
- Returns:
The batches, in position order. A batch has the position arrays and the value arrays of
fetch_region_value_arrays(), followed by thereferenceandalternativearrays. A key column that the table does not declare is an array ofNone.- Return type:
Generator[AlleleRecordArrays, None, None]
- Raises:
TypeError – If the score does not serve the read for
scores. Asksupports_region_allele_arrays()first.ValueError – If the score is not open, or if the contig is not in the resource.
Notes
The call checks the request. The errors above are raised when the method is called and not on the first
next().
- get_allele_score_for_allele(chrom: str, pos: int, ref: str, alt: str, *, score: str | None = None, score_filter: ScoreFilter | None = None) str | int | float | bool | None[source]
Return the value of one score at the only row of one allele.
The singular form of
get_allele_scores_for_allele().- Parameters:
chrom (str) – The chromosome name.
pos (int) – The position of the allele.
ref (str) – The reference nucleotides.
alt (str) – The alternative nucleotides.
score (str or None, optional) – The score id.
None, the default, is accepted only when the resource defines exactly one score.score_filter (ScoreFilter or None, optional) – Keep the row only if this filter accepts it.
- Returns:
The value of the score.
Nonewhen no row holds the allele, or whenscore_filterrejects the row. The value itself may also beNonewhen the row has no value for the score.- Return type:
ScoreValue or None
- Raises:
AlleleMultiplicityError – As
get_allele_scores_for_allele().ValueError – As
get_allele_scores_for_allele(). Also whenscoreisNoneand the resource defines several scores.ScoreFilterError – If
score_filterwas compiled for another resource.
- get_allele_score_for_allele_agg(chrom: str, pos: int, ref: str, alt: str, *, score: str | None = None, aggregator: str | None = None, score_filter: ScoreFilter | None = None) AlleleAggregate | None[source]
Reduce the rows of one allele for one score.
The singular form of
get_allele_scores_for_allele_agg(). It does not unwrap the answer: theAlleleAggregatehas avaluestuple of one element.- Parameters:
chrom (str) – The chromosome name.
pos (int) – The position of the allele.
ref (str) – The reference nucleotides.
alt (str) – The alternative nucleotides.
score (str or None, optional) – The score id.
None, the default, is accepted only when the resource defines exactly one score.aggregator (str or None, optional) – The aggregator name.
None, the default, is the default aggregator of the score.score_filter (ScoreFilter or None, optional) – Keep only the rows that this filter accepts.
- Returns:
As
get_allele_scores_for_allele_agg().- Return type:
AlleleAggregate or None
- Raises:
ValueError – As
get_allele_scores_for_allele_agg(). Also whenscoreisNoneand the resource defines several scores.ScoreFilterError – If
score_filterwas compiled for another resource.
- get_allele_score_for_allele_rows(chrom: str, pos: int, ref: str, alt: str, *, score: str | None = None, score_filter: ScoreFilter | None = None) list[str | int | float | bool | None][source]
Return the value of one score for every row of one allele.
The singular form of
get_allele_scores_for_allele_rows().- Parameters:
chrom (str) – The chromosome name.
pos (int) – The position of the allele.
ref (str) – The reference nucleotides.
alt (str) – The alternative nucleotides.
score (str or None, optional) – The score id.
None, the default, is accepted only when the resource defines exactly one score.score_filter (ScoreFilter or None, optional) – Keep only the rows that this filter accepts.
- Returns:
One value for each row that the filter keeps, in file order. The list is empty when no row carries the allele or when the filter rejects every row.
- Return type:
list[ScoreValue]
- Raises:
ValueError – As
get_allele_scores_for_allele_rows(). Also whenscoreisNoneand the resource defines several scores.ScoreFilterError – If
score_filterwas compiled for another resource.
- get_allele_score_in_region_rows(chrom: str, start: int, end: int, *, score: str | None = None, score_filter: ScoreFilter | None = None) Generator[tuple[int, str | None, str | None, str | int | float | bool | None], None, None][source]
Yield
(pos, ref, alt, value)for each row that overlaps a region.The singular form of
get_allele_scores_in_region_rows().- Parameters:
chrom (str) – The chromosome name.
start (int) – The first position of the region, 1-based.
end (int) – The last position of the region.
score (str or None, optional) – The score id.
None, the default, is accepted only when the resource defines exactly one score.score_filter (ScoreFilter or None, optional) – Keep only the rows that this filter accepts.
- Returns:
One tuple for each row that the filter keeps. The generator yields nothing when no row overlaps the region or when the filter rejects every row.
reforaltisNonewhen the table has no such column.- Return type:
Generator[tuple[int, str or None, str or None, ScoreValue], None, None]
- Raises:
ValueError – As
get_allele_scores_in_region_rows(). Also whenscoreisNoneand the resource defines several scores.ScoreFilterError – If
score_filterwas compiled for another resource.
Notes
The call checks the request. The table is read lazily, as in
get_allele_scores_in_region_rows().
- get_allele_scores_for_allele(chrom: str, pos: int, ref: str, alt: str, *, scores: Sequence[str] | None = None, score_filter: ScoreFilter | None = None) tuple[str | int | float | bool | None, ...] | None[source]
Return the values of the only row of one allele.
The match is exact on the position and on the nucleotides: a row that starts earlier and whose span reaches
posis not this allele. The read does not consultallele_score_mode.The read answers one row, as the
multiplicityof the resource promises. A resource that declaresallele_multiplicity: manyis refused, because one allele may hold several rows. On aoneresource, several rows that hold the allele are a data error. The rows are counted beforescore_filter, so a filter that hides one of them does not hide the error. Untilallele_multiplicity_enforced()answers true, the read warns once for each process and answers the first row. The filter applies to that row alone.- Parameters:
chrom (str) – The chromosome name.
pos (int) – The position of the allele.
ref (str) – The reference nucleotides.
alt (str) – The alternative nucleotides.
scores (Sequence[str] or None, optional) – The score ids to read.
None, the default, means every score of the resource, in definition order.score_filter (ScoreFilter or None, optional) – Keep the row only if this filter accepts it.
None, the default, accepts every row.
- Returns:
The values of the row, parallel to
scores.Nonewhen no row holds the allele, or whenscore_filterrejects the row.- Return type:
tuple[ScoreValue, …] or None
- Raises:
AlleleMultiplicityError – If the resource declares
allele_multiplicity: many. Also if several rows hold the allele on aoneresource, once the enforcement release is reached. Read every row withget_allele_scores_for_allele_rows(), or reduce the rows withget_allele_scores_for_allele_agg().ValueError – If a score id is unknown, or if the contig is not in the resource.
ScoreFilterError – If
score_filterwas compiled for another resource.
Notes
The call checks the request before it reads a row, and before the
manyrefusal: an unknown score id, an unknown contig and a foreign filter are refused on any resource.
- get_allele_scores_for_allele_agg(chrom: str, pos: int, ref: str, alt: str, *, queries: Sequence[ScoreAggregationQuery] | None = None, allele_keys: Sequence[str] | None = None, score_filter: ScoreFilter | None = None) AlleleAggregate | None[source]
Reduce the rows of one allele to one value per query.
Fold the rows at exactly
(chrom, pos, ref, alt). A row that starts earlier and whose span reachesposis not folded. The read folds the rows thatget_allele_scores_for_allele_rows()answers, with the queries, the aggregators and theAlleleAggregateofget_allele_scores_in_region_agg(). The read is allowed on both multiplicities and does not consultallele_score_mode. On aoneresource it folds the single row.- Parameters:
chrom (str) – The chromosome name.
pos (int) – The position of the allele.
ref (str) – The reference nucleotides.
alt (str) – The alternative nucleotides.
queries (Sequence[ScoreAggregationQuery] or None, optional) – What to compute.
None, the default, means every score of the resource with its default aggregator.allele_keys (Sequence[str] or None, optional) – As in
get_allele_scores_in_region_agg(). The answer has the one keychrom:pos:ref:alt, or several keys when a suffixed score differs across the rows.score_filter (ScoreFilter or None, optional) – Keep only the rows that this filter accepts.
None, the default, keeps every row.
- Returns:
Nonewhen no row holds the allele. The check happens before the filter. When the filter rejects every row, the answer is anAlleleAggregatein which each aggregator answers for no rows:maxgivesNone,listgives[]andcountgives0.- Return type:
AlleleAggregate or None
- Raises:
ValueError – If a score id is unknown, if a query has no aggregator to resolve to, if an aggregator is bad, or if the contig is not in the resource.
ScoreFilterError – If
score_filterwas compiled for another resource.
Notes
The call checks the request before it reads a row.
- get_allele_scores_for_allele_rows(chrom: str, pos: int, ref: str, alt: str, *, scores: Sequence[str] | None = None, score_filter: ScoreFilter | None = None) list[tuple[str | int | float | bool | None, ...]][source]
Return the values of every row of one allele, in file order.
Several rows may carry one allele, and all of them are answered. The read does not reduce the rows and does not consult
allele_score_mode. The match is exact on the position and on the nucleotides: a row that starts earlier and whose span reachesposis not this allele.- Parameters:
chrom (str) – The chromosome name.
pos (int) – The position of the allele.
ref (str) – The reference nucleotides.
alt (str) – The alternative nucleotides.
scores (Sequence[str] or None, optional) – The score ids to read.
None, the default, means every score of the resource.score_filter (ScoreFilter or None, optional) – Keep only the rows that this filter accepts. The filter is evaluated on every row that overlaps the position, not only on the rows of the allele.
None, the default, keeps every row.
- Returns:
One tuple for each row that the filter keeps, parallel to
scores. The list is empty both when no row carries the allele and when the filter rejects every row. The read does not tell the two cases apart.- Return type:
list[tuple[ScoreValue, …]]
- Raises:
ValueError – If a score id is unknown, or if the contig is not in the resource.
ScoreFilterError – If
score_filterwas compiled for another resource.
Notes
The call checks the request before it reads a row.
- get_allele_scores_in_region_agg(chrom: str, start: int, end: int, *, queries: Sequence[ScoreAggregationQuery] | None = None, allele_keys: Sequence[str] | None = None, score_filter: ScoreFilter | None = None) AlleleAggregate | None[source]
Reduce the alleles in a region to one value per query.
Fold the rows that overlap the region and that
score_filterkeeps, in one walk that holds no row. An allele row weighs 1.- Parameters:
chrom (str) – The chromosome name.
start (int) – The first position of the region, 1-based.
end (int) – The last position of the region.
queries (Sequence[ScoreAggregationQuery] or None, optional) – What to compute. Each query is a score with an optional aggregator, and the aggregator of the query wins over the default aggregator of the score.
None, the default, means every score of the resource with its default aggregator.allele_keys (Sequence[str] or None, optional) – The score ids that suffix each allele key.
None, the default, builds no keys. An empty sequence builds the barechrom:pos:ref:altkeys. Seeallele_key().score_filter (ScoreFilter or None, optional) – Keep only the rows that this filter accepts. Build the filter with
compile_filter()of this score.None, the default, keeps every row.
- Returns:
Nonewhen no row overlaps the region. The check happens before the filter. Otherwise anAlleleAggregate. When the filter rejects every row, each aggregator answers for an empty selection:maxgivesNoneandlistgives[]. The keys are distinct and in the order the rows were first seen.- Return type:
AlleleAggregate or None
- Raises:
ValueError – If a score id of
queriesorallele_keysis unknown, if a query has no aggregator to resolve to, or if the contig is not in the resource.ScoreFilterError – If
score_filterwas compiled for another resource.
Notes
The call checks the request before it reads a row.
- get_allele_scores_in_region_rows(chrom: str, start: int, end: int, *, scores: Sequence[str] | None = None, score_filter: ScoreFilter | None = None) Generator[AlleleEntry, None, None][source]
Yield an
AlleleEntryfor each row that overlaps a region.The entries come in the position order of the table. Rows that share an allele are each yielded. The read does not reduce the rows and does not consult
allele_score_mode.- Parameters:
chrom (str) – The chromosome name.
start (int) – The first position of the region, 1-based.
end (int) – The last position of the region.
scores (Sequence[str] or None, optional) – The score ids to read.
None, the default, means every score of the resource.score_filter (ScoreFilter or None, optional) – Keep only the rows that this filter accepts.
None, the default, keeps every row.
- Returns:
One entry for each row that the filter keeps, with
valuesparallel toscores. The generator yields nothing both when no row overlaps the region and when the filter rejects every row.get_allele_scores_in_region_agg()tells the two cases apart.- Return type:
Generator[AlleleEntry, None, None]
- Raises:
ValueError – If a score id is unknown, or if the contig is not in the resource.
ScoreFilterError – If
score_filterwas compiled for another resource.
Notes
The call checks the request, and the errors above are raised before the generator is returned. The table is read lazily, on the first
next(). Iterate one generator at a time, as with every region read of a table, and materialise what has to outlive the next read.
- static get_schema() dict[str, Any][source]
Return the schema of the resource configuration.
The schema is the
GenomicScoreschema plus the keysallele_score_mode,allele_multiplicityandmerge_vcf_scores, and thereference,alternativeandvariantcolumn definitions of the table.- Returns:
A new schema in the Cerberus format. The caller may change it.
- Return type:
dict[str, Any]
- property multiplicity: Multiplicity
The number of rows that one allele may have in the table.
Only the bare reads consult it, such as
get_allele_scores_for_allele().
- substitutions_mode() bool[source]
Return whether the mode of the resource is
substitutions.
- supports_region_allele_arrays(scores: list[str]) bool[source]
Return whether
fetch_region_allele_arrays()serves the scores.The answer needs the conditions of
supports_region_value_arrays()(the backend and the value types of the scores) and one more: the table must declare at least one of thereferenceandalternativecolumns. One column is enough. A bigWig-backed score has no such columns and is not served.The method works on a score that is not open. In one case the answer is conservative there: a table with
header_mode: filethat names its key columns only in its data file answersFalseuntil the score is opened, andTrueafter.- Parameters:
scores (list[str]) – The score ids that the caller wants to read.
- Returns:
Trueiffetch_region_allele_arrays()accepts the scores. If the answer isFalse, read per record withfetch_records(). The rows are the same.- Return type:
bool
- allele_key(chrom: str, pos: int, ref: str | None, alt: str | None, suffix: Sequence[str | int | float | bool | None] = ()) str[source]
Build the allele key,
chrom:pos[:ref:alt][:v1,v2].The key is the identity of an allele as annotation output spells it.
- Parameters:
chrom (str) – The chromosome name.
pos (int) – The position of the allele.
ref (str or None) – The reference nucleotides. The key has no
:ref:altpart when this oraltisNone.alt (str or None) – The alternative nucleotides.
suffix (Sequence[ScoreValue], optional) – The values of the scores to append, in the order asked. Each value is rendered as
stringify()renders it for output, and the values are joined with,. The suffix is part of the identity: two rows of one allele that differ in a suffixed score give two keys. The default is an empty sequence, which appends nothing.
- Returns:
The key, for example
1:10:A:Gor1:10:A:G:0.5,2.- Return type:
str
gain.genomic_resources.genomic_scores.base module
The GenomicScore base class.
Everything the three score kinds share: config parsing and score-def
construction, the open/close lifecycle over the position table, and the
record and array reads. The kinds themselves live in position,
allele and fragment, and the factories that dispatch
between them in builders.
Decomposing this class – so that a kind’s author reads the handful of hooks
their kind overrides rather than the whole base – is gain#1027. Its first
extraction (gain#1044) moved the scoredef lifecycle to
score_def and took this module under the
1500-line cap, so the file-scoped too-many-lines pragma gain#1007 added
here when it restored that cap is gone; its second (gain#1074) moved the
region-aggregation machinery to aggregation, which the per-kind
aggregating reads drive, each handing it the kind’s weight rule; its third
(gain#1114) moved the value-extraction seam – picking the per-record read,
and addressing each score def to a payload column – to
value_extraction, leaving
GenomicScore.open() calling both in the order that seam requires and
the two public per-record getters here. The remaining seams are #1027’s
other children.
A score’s defs are finished in place at open:
resolve_score_indices() writes score_index onto
the definitions this class already holds rather than handing back new ones
(it says there who reads them). The earlier half of the lifecycle differs:
finish_scoredefs runs inside GenomicScore._build_scoredefs(),
before there is a score_definitions to write onto, and so returns the
mapping.
- class GenomicScore(resource: GenomicResource)[source]
Bases:
ScoreResource[GenomicScoreDef]Base class of the position, allele and fragment scores.
A genomic score gives access to the values of a genomic resource that attaches scores to places in the genome. The class owns what the three kinds share: the score definitions, the open and close lifecycle, the filter compiler, and the record and column-array reads. A score is built closed, so call
open()before the first read.The data of the resource is a tabix file, a VCF, a bigWig or an in-memory table. A score is not safe for concurrent use.
- Variables:
resource (GenomicResource) – The genomic resource of the score.
resource_id (str) – The id of the resource.
config (dict) – The validated and normalized configuration.
table (GenomicPositionTable) – The table that holds the data.
score_definitions (dict[str, GenomicScoreDef]) – The definitions of the scores, keyed by score id.
table_loaded (bool) –
Truewhile the table is open.DEFAULT_AGGREGATORS (dict[str, str | None]) – The aggregator that each kind of score value gets when its config states none. The value types of a score are the keys. Each kind of score sets it.
See also
PositionScore, AlleleScore, FragmentScore
- DEFAULT_AGGREGATORS: ClassVar[dict[str, str | None]] = {}
- property chrom_length_source: ChromLengthSource
Return the source of chromosome lengths in the score’s own table.
The answer is known without opening the table, so it works on a score that is not open.
- property chrom_length_sources: list[ChromLengthSource]
The sources that the score can answer a length from, best first.
The sources of the stored lengths file, or the source of the table alone. Reading the property on a score that is not open raises
ValueError.
- close() None[source]
Close the underlying table and mark the score not open.
open()can run again afterwards. Do not callclosewhile another thread reads the score: a reader that passedis_open()can find the table released. A holder that shares one score across threads must serialise the reads,openandcloseitself.
- compile_filter(expression: str) ScoreFilter[source]
Compile a boolean expression into a filter over this score.
The expression names scores of this resource and relates them with
>,>=,<,<=,==,!=andin. The relations combine withnot,andandor. Pass the result to a read asscore_filter.- Parameters:
expression (str) – The filter expression, for example
"phastCons100way > 0.9".- Returns:
A filter bound to this score. The reads of another score refuse it.
- Return type:
- Raises:
ScoreFilterError – If the expression does not parse, or if it names a score that the resource does not define.
- fetch_records(chrom: str, pos_begin: int | None, pos_end: int | None, *, score_filter: ScoreFilter | None = None) Generator[tuple[Any, ...], None, None][source]
Yield the records of a region, optionally filtered.
Read the positional fields of a record from its slots (
record[CHROM],record[POS_BEGIN], …). Read a score value withget_score_value_from_record(). The generator checks nothing before the firstnext(): a closed score, an unknown contig and a filter of another score are all reported there, not from the call.- Parameters:
chrom (str) – The chromosome name. It is required. To read every record of the table, use
score.table.get_all_records().pos_begin (int | None) – The start of the region.
Nonemeans no lower bound.pos_end (int | None) – The end of the region.
Nonemeans no upper bound.score_filter (ScoreFilter | None, default None) – A filter from
compile_filter(). When it is notNone, the generator yields only the records that the filter accepts.
- Yields:
Record – The records that overlap the region, in table order. A region without records yields nothing.
- Raises:
ValueError – If the score is not open, or if the table does not hold
chrom.ScoreFilterError – If
score_filterwas compiled by another score.
- fetch_region_segments_scores(chrom: str, pos_begin: int | None = None, pos_end: int | None = None, scores: Sequence[str] | None = None, *, score_filter: ScoreFilter | None = None) Generator[tuple[int, int, list[str | int | float | bool | None]], None, None][source]
Yield
(begin, end, values)for each record that touches a region.A segment is one record at its own extent, not one value for each position. A record on the edge of the region arrives whole. To answer a question about the window, clip the segments with
gain.genomic_resources.genomic_scores.records.clip_span.- Parameters:
chrom (str) – The chromosome name.
pos_begin (int | None, default None) – The start of the region.
Nonemeans no lower bound.pos_end (int | None, default None) – The end of the region.
Nonemeans no upper bound.scores (Sequence[str] | None, default None) – The ids of the scores to read.
Nonemeans every score of the resource.score_filter (ScoreFilter | None, default None) – A filter from
compile_filter(). The filter reads the whole record, so it can name a score outsidescores.
- Returns:
The segments, in table order. The values follow the order of
scores. A region without records yields nothing.- Return type:
Generator[tuple[int, int, list[ScoreValue]], None, None]
- Raises:
ValueError – If the score is not open, if the table does not hold
chrom, or if a score id is unknown. The call raises these errors.ScoreFilterError – If
score_filterwas compiled by another score. The firstnext()raises this error, not the call.
- fetch_region_value_arrays(chrom: str, pos_begin: int | None, pos_end: int | None, scores: list[str], *, batch_size: int = 100000) Generator[tuple[ndarray, ndarray, dict[str, ndarray]], None, None][source]
Fetch a region as column arrays, without a record for each row.
The bulk form of
fetch_records(), for a caller that scans a whole region and wants columns. Callsupports_region_value_arrays()first. The method checks its arguments when it is called, not at the firstnext().The values are parsed by
parse_array(). The dtype of an array follows the value type of the score, not the backend: afloatorintscore givesfloat64withnanfor no value, and astrscore gives anobjectarray withNone. The method does not clip: a record that overlaps the start of the region arrives whole.- Parameters:
chrom (str) – The chromosome name.
pos_begin (int | None) – The start of the region.
Nonemeans no lower bound.pos_end (int | None) – The end of the region.
Nonemeans no upper bound.scores (list[str]) – The ids of the scores to read. Each id gets an array of its own.
batch_size (int, default 100000) – A hint for the rows of a batch. It is a ceiling: a batch also stays within a budget of raw cells, so a read of many scores gets fewer rows. A bigWig backend sizes its batches itself and ignores the hint.
- Returns:
Batches of
(pos_begin, pos_end, {score_id: values}). The first two items are one-based position arrays. The generator yields nothing for a region without records.- Return type:
Generator[RecordArrays, None, None]
- Raises:
TypeError – If
supports_region_value_arrays()isFalseforscores.ValueError – If the score is not open, or if the table does not hold
chrom.
- get_all_chrom_lengths(source: ChromLengthSource | str | None = None) dict[str, int][source]
Return the length of every chromosome.
- Parameters:
source (ChromLengthSource | str | None, default None) – The source to answer from, or its string value.
Nonemeans the best-ranked source for each chromosome.- Returns:
The lengths, keyed by chromosome in table order. A chromosome that the chosen view cannot answer is left out. The dict is empty if no chromosome has an answer.
- Return type:
dict[str, int]
- Raises:
ValueError – If the score is not open, if
sourceis none of the four sources, or ifsourceisreference_genomeon a score with no stored lengths.
- get_all_chromosomes() list[str][source]
Return the chromosome names that the score’s table holds.
- Returns:
The names, in table order.
- Return type:
list[str]
- Raises:
ValueError – If the score is not open.
- get_chrom_length(chrom: str, source: ChromLengthSource | str | None = None) int[source]
Return the length of a chromosome.
The best-ranked source answers by default. The source tells how far to trust the number: only the reference genome and a bigWig header are exact.
get_chrom_length_source()says which source answered.- Parameters:
chrom (str) – The chromosome name.
source (ChromLengthSource | str | None, default None) – The source to answer from, or its string value.
Nonemeans the best-ranked source.
- Returns:
The length of
chrom.- Return type:
int
- Raises:
ValueError – If the score is not open, if the table does not hold
chrom, ifsourceis none of the four sources, if the source has no answer forchrom, or if no source can measurechrom. Asourceofreference_genomeraises on a score whose repair did not store the lengths.
- get_chrom_length_source(chrom: str) ChromLengthSource[source]
Return the source that answers
get_chrom_length().- Parameters:
chrom (str) – The chromosome name.
- Returns:
The best-ranked source that has an answer for
chrom.- Return type:
ChromLengthSource
- Raises:
ValueError – For the same reasons as
get_chrom_length(), including a chromosome that no source answers.
- get_config() dict[str, Any][source]
Return the configuration of the score.
- Returns:
The configuration of the resource, as validated and normalized when the score was built. It is the object the score holds, not a copy.
- Return type:
dict[str, Any]
- get_score_value_from_record(record: tuple[Any, ...], score_id: str) str | int | float | bool | None[source]
Read one score off a record of this score’s table.
- Parameters:
record (Record) – A record from
fetch_records()of this score.score_id (str) – The id of a score of the resource.
- Returns:
The parsed value.
Noneif the cell holds no value: a missing cell, an NA sentinel of the score, or a cell that does not parse.- Return type:
ScoreValue
- Raises:
KeyError – If the resource does not define
score_id.
- has_chromosome(chrom: str) bool[source]
Report whether the score’s table carries a chromosome.
- Parameters:
chrom (str) – The chromosome name.
- Returns:
Trueif the table holdschrom,Falseotherwise.- Return type:
bool
- Raises:
ValueError – If the score is not open.
- is_open() bool[source]
Report whether the score is open.
- Returns:
Trueifopen()has run andclose()has not run since. The answer is a snapshot for the owner of the score. It does not protect against aclose()in another thread.- Return type:
bool
- open() Self[source]
Open the score for reading and return it.
Call
openbefore the first read. Entering awithblock does not open the score. Opening an open score does nothing.- Returns:
This score, so the call can chain:
score.open().- Return type:
- Raises:
ValueError – If the score definitions do not fit the table, for example when a configured column does not exist.
- supports_region_value_arrays(scores: list[str]) bool[source]
Report whether
fetch_region_value_arrays()serves these scores.The answer is
Trueonly if the backend serves the column-array read and every named score has a value type that the array parse supports. The method works on a score that is not open.- Parameters:
scores (list[str]) – The ids of the scores to read.
- Returns:
Trueiffetch_region_value_arrays()accepts these scores.Falseif the backend has no array read, if a score id is unknown, or if a score has a value type that the parse does not support. An empty list gives the answer of the backend alone.- Return type:
bool
gain.genomic_resources.genomic_scores.batch_budget module
How many rows one bulk column-array batch reads.
A bulk read (GenomicScore.fetch_region_value_arrays() and the allele read built on
the same column loop) materialises every cell of the columns it fetches for
the rows of a batch, then parses them while the raw cells are still alive.
Its peak memory therefore grows with rows x columns, not with rows alone, and
a row count on its own lets a score declaring hundreds of columns build a
batch of tens of millions of cells. The rule here bounds the cells instead.
- VALUE_ARRAYS_CELL_BUDGET = 2000000
The most raw cells – rows x the distinct columns a read fetches – one bulk batch may hold, whatever row count its caller asked for. It bounds the fetch, not the parse: score ids sharing one payload column each still get a parsed array of their own. 2M cells keeps the full 100,000-row default batch for a read of up to 20 columns, and gives a 454-column read about 4,400 rows.
- value_arrays_batch_rows(batch_size: int, column_count: int) int[source]
The rows one bulk batch reads:
batch_size, capped by the budget.The cap divides
VALUE_ARRAYS_CELL_BUDGETby the number of columns the read fetches – every one of them, a non-score column such as an allele’sreference/alternativeincluded, since each costs a raw cell per row all the same. Never below one row, so a read wider than the whole budget still makes progress.
gain.genomic_resources.genomic_scores.builders module
The factories that build a score from a resource.
One pair per kind (build_<kind>_score_from_resource and its
_from_resource_id sibling) plus the dispatching pair that reads the
resource’s type and picks the kind for you. The _from_resource_id half
falls back to the default GRR when handed no repository.
- build_allele_score_from_resource(resource: GenomicResource) AlleleScore[source]
Build an allele score from an
allele_scoreresource.Every call builds a new score. The score is closed: call
open()before the first read.- Parameters:
resource (GenomicResource) – A resource of type
allele_score.- Returns:
A new, closed score. The mode is
allelesunless the resource setsallele_score_mode.- Return type:
- Raises:
ValueError – If the resource is not of type
allele_score. A resource that still declares the removed typenp_scoreis also refused. The message names the replacement type and the mode key that keeps the old reading.
- build_allele_score_from_resource_id(resource_id: str, grr: GenomicResourceRepo | None = None) AlleleScore[source]
Build an allele score from the id of an
allele_scoreresource.Every call builds a new score. The score is closed: call
open()before the first read.- Parameters:
resource_id (str) – The id of a resource of type
allele_scorein the repository.grr (GenomicResourceRepo or None, optional) – The repository that resolves
resource_id.None, the default, builds the default repository withbuild_genomic_resource_repository().
- Returns:
A new, closed score, as
build_allele_score_from_resourcebuilds it.- Return type:
- Raises:
FileNotFoundError – If the repository has no resource with this id.
ValueError – If the resource is not of type
allele_score.
- build_fragment_score_from_resource(resource: GenomicResource) FragmentScore[source]
Build a fragment score from a fragment-score resource.
Every call builds a new score. You own the score and can close it without any effect on other scores built from the same resource. The score is closed: call
open()before the first read.- Parameters:
resource (GenomicResource) – A resource of type
fragment_score, or of the older typecnv_collection, which gives a deprecation warning.- Returns:
A new, closed fragment score.
- Return type:
- Raises:
ValueError – If the type of the resource is not a fragment score type.
- build_fragment_score_from_resource_id(resource_id: str, grr: GenomicResourceRepo | None = None) FragmentScore[source]
Build a fragment score from the id of a fragment-score resource.
- Parameters:
resource_id (str) – The id of a resource of type
fragment_score, or of the older typecnv_collection, which gives a deprecation warning.grr (GenomicResourceRepo | None, default None) – The repository that holds the resource.
Nonebuilds the default repository.
- Returns:
A new, closed fragment score. Call
open()before the first read.- Return type:
- Raises:
FileNotFoundError – If the repository has no resource with this id.
ValueError – If the type of the resource is not a fragment score type.
- build_position_score_from_resource(resource: GenomicResource) PositionScore[source]
Build a position score from a resource.
The score is closed. Call
open()before the first read. Each call returns a new score, so the caller owns it and closing it affects no other score.- Parameters:
resource (GenomicResource) – A resource of type
position_score.- Returns:
A closed position score for the resource.
- Return type:
- Raises:
ValueError – If the resource is not of type
position_score.
- build_position_score_from_resource_id(resource_id: str, grr: GenomicResourceRepo | None = None) PositionScore[source]
Build a position score from the id of a resource.
The score is closed. Call
open()before the first read. Each call returns a new score, so the caller owns it and closing it affects no other score.- Parameters:
resource_id (str) – The id of a resource of type
position_scorein the repository.grr (GenomicResourceRepo | None) – The repository that resolves
resource_id. When it isNone, the function builds the default repository withbuild_genomic_resource_repository().
- Returns:
A closed position score for the resource.
- Return type:
- Raises:
FileNotFoundError – If the repository has no resource with this id.
ValueError – If the resource is not of type
position_score.
- build_score_from_resource(resource: GenomicResource) GenomicScore[source]
Build the score of a score resource, whatever its kind.
The function reads the
typeof the resource and calls the factory of that kind. Use the factory of the kind when the type is known. Use this function when the type is not.- Parameters:
resource (GenomicResource) – A resource of type
position_score,allele_scoreorfragment_score. The legacy typecnv_collectionalso names a fragment score.- Returns:
A new
PositionScore,AlleleScoreorFragmentScore. Each call returns a new score that the caller owns, so closing it affects nothing else. The score is closed: callopen()before the first read.- Return type:
- Raises:
ValueError – If the resource is not of a score type, or if it declares a retired type such as
np_score.
- build_score_from_resource_id(resource_id: str, grr: GenomicResourceRepo | None = None) GenomicScore[source]
Build the score of the score resource with a given id.
The function finds the resource in the repository and calls
build_score_from_resource().- Parameters:
resource_id (str) – The id of a score resource in the repository.
grr (GenomicResourceRepo | None, default None) – The repository that resolves
resource_id. When it isNone, the function builds the default repository withbuild_genomic_resource_repository().
- Returns:
A new
PositionScore,AlleleScoreorFragmentScore. The score is closed: callopen()before the first read.- Return type:
- Raises:
FileNotFoundError – If the repository has no resource with this id.
ValueError – If the resource is not of a score type, or if it declares a retired type such as
np_score.
gain.genomic_resources.genomic_scores.chrom_lengths module
Chromosome lengths of a genomic score, and where each one came from.
A score’s table can usually say how long a contig is, but the backends
answer with different confidence – a bigWig header is exact, the tabix
probe brackets an upper bound, the in-memory backend only knows how far its
rows reach – and the callers that split a contig into regions also need to
know WHY there is no length when there is none (gain#509). So the record
here keeps each number under its provenance, picks the best-trusted of
them, and carries the
ContigExtent reason
the table gives when it has no number.
The ladder the epic (gain#1412) settles is genome label → bigWig header →
tabix estimate, applied per contig of the score. The resolver here asks
every rung live, and every rung answers (gain#1574): the genome rung from
the ReferenceGenome the caller hands in, the table’s own rung through
the table – a record holds each answer under its source, and best
picks by the source’s rank. It holds no repository, so resolving the
score’s reference_genome label into that genome is the caller’s job.
A repair stores what the resolver found as CHROM_LENGTHS_FILE –
one block per source, contig to length, the table’s block carrying the
reason for a contig it had no length for – together with a
DerivedFrom key: the label the genome resolved from and the manifest
md5 of every table file, which is what a later repair compares to tell a
current file from a stale one without opening the table (gain#1576).
save_chrom_lengths / load_chrom_lengths are the file’s two
seams; loading never raises, a file that cannot be read as one reads as
absent.
- CHROM_LENGTHS_FILE = 'statistics/chrom_lengths.json'
Where a repair stores the resolver’s answers, relative to the resource.
- CHROM_LENGTHS_FORMAT = 1
The layout of that file. A stored file of another format is stale: read as absent and rewritten by the next repair.
- class ChromLength(answers: Mapping[ChromLengthSource, int], extent: ContigExtent | None)[source]
Bases:
objectOne contig’s length from every source that had one.
answersis each source’s length;extentis the TABLE’s reason when it had none –EMPTYwhen it proved the contig holds no records,UNDETERMINEDwhen the probe could not answer for a contig that may well hold some – andNonewhen it answered. A record with no answer always carries the reason; one is refused without it.- answers: Mapping[ChromLengthSource, int]
- property best: ChromLengthAnswer | None
The highest-ranked answer with its source;
Nonewhen none.
- extent: ContigExtent | None
- property listed_by_genome: bool
a contig the genome lists carries its answer, whatever the table said. The one definition of “unlisted”, which the repair’s warning (gain#1575) reads.
- Type:
Whether the genome rung answered
- class ChromLengthAnswer(length: int, source: ChromLengthSource)[source]
Bases:
objectOne source’s length for a contig: the number and where it came from.
- length: int
- source: ChromLengthSource
- class DerivedFrom(reference_genome: str | None, files_md5: dict[str, str | None])[source]
Bases:
objectWhat a stored answer was computed from – the file’s freshness key.
reference_genomeis the label the genome was actually resolved from,Nonewhen there was none to resolve: no label, a label that is not a resource id, or a genome the repository could not find. So a genome that turns up later reads as a change.files_md5is the manifest md5 of every data file of the table, keyed by name – as the manifest records it, which for an entry it has not digested is none.- describes(resource: GenomicResource, manifest: Manifest, files: Collection[str], *, genome_resolves: Callable[[], bool] | None = None) bool[source]
Whether
resource, as it is now, is what this was derived from.The one freshness test of the key:
manifest’s md5 of every one of the table’sfiles(a file it does not list is a change), then thereference_genomelabel as the resource carries it today. A key derived from a genome stands while the label still names it, whether or not the genome is still there – the record is the record (gain#1573). One derived with none stands while there is still none: a genome that turns up later reads as a change, and whether a label that is now present names one isgenome_resolves’s answer – the implementation’s, which holds a repository. A caller with none passes nothing, and any label then reads as a change: conservative on purpose (gain#1419), the answer costs the live probe and nothing more.The manifest is the caller’s to hand in, so that a read path passes the stored one and never builds it.
- files_md5: dict[str, str | None]
- reference_genome: str | None
- class StoredChromLengths(lengths: dict[str, ChromLength], derived_from: DerivedFrom, table_source: ChromLengthSource)[source]
Bases:
objectEvery source’s answer for every contig, and what they derive from.
table_sourceis the source the score’s table answers under: its block is the one that names every contig, in the table’s order, and the one that carries a contig’s reason when the table had no length for it.- derived_from: DerivedFrom
- lengths: dict[str, ChromLength]
- property sources: list[ChromLengthSource]
Every source with an answer, and the table’s, best first.
- table_source: ChromLengthSource
- as_chrom_length_source(source: ChromLengthSource | str | None) ChromLengthSource | None[source]
The member
sourcenames – itself, or by its string value.NonestaysNone: the caller’s “the best-ranked one”. RaisesValueErrornaming the valid values on a string that is none of them.
- best_chrom_length(chrom: str, resolved: ChromLength, contigs: list[str]) ChromLengthAnswer[source]
resolved.best, or the table’s refusal when there is none –contigsbeing the table’s, which the refusal names.
- derive_chrom_length(score: GenomicScore, chrom: str, ref_genome: ReferenceGenome | None = None) ChromLength[source]
Resolve one contig of
scorethrough the ladder, every rung.ref_genomeis the top rung: a contig it lists is answered with its true length underREFERENCE_GENOME. The table is asked as well, genome or not, and answers under whatever source the backend declares its lengths to be, or with the reason it cannot. RaisesValueErrorwhen the score is not open or does not carrychrom– a bad question, as opposed to an absent answer – in the TABLE’s words, since it is the table that refuses; the genome rung sits behind the table’s own screen so that a contig only the genome knows is refused the same way.
- derive_chrom_lengths(score: GenomicScore, ref_genome: ReferenceGenome | None = None) dict[str, ChromLength][source]
Resolve every contig of
score, in the table’s order.The universe is the score’s contigs –
get_all_chromosomes()– never the whole genome; a whole-reference denominator is a property of the genome and belongs to coverage (gain#1041). Keyed by contig so the consumer that splits regions (the statistics build) looks up by name; the dict keeps table order.Not memoised: on a tabix score every call re-runs the probe per contig. Raises
ValueErroron a score that is not open.
- files_md5_of(manifest: Manifest, files: Collection[str]) dict[str, str | None][source]
The manifest md5 of every one of
files, keyed by name.One definition of “the same files” for every gate that asks – the statistics hash, the stored lengths’ key and the score’s own reader – so they cannot drift apart on what counts as a data file. As the manifest records it, which for an entry it has not digested is none; a file the manifest does not list is a
KeyError.
- load_chrom_lengths(resource: GenomicResource) StoredChromLengths | None[source]
Read the resource’s
CHROM_LENGTHS_FILE;Nonewhen it has none.Absence is a normal state, not an error: a resource repaired before the file existed has nothing stored until its next repair. A file that cannot be read as one – of another format, or truncated by a repair killed mid-write, the write not being atomic – reads as absent too, with a WARNING naming it: absent, the ordinary repair rewrites it; raised, the gate would fail the resource over a file the repair is about to replace.
- load_current_chrom_lengths(score: GenomicScore) StoredChromLengths | None[source]
The stored lengths, if the file describes
scoreas it is now.Four ways for it not to, all normal and none a WARNING: no file (a resource repaired before the file existed, or never – the state of every resource until its next repair, so said at DEBUG, as anything louder would fire on every open of every one of them); no stored manifest to compare the key against; a
derived_fromthat no longer matches (a label re-pointed and not yet repaired – trusted, it would answer the old genome’s lengths); and a contig list that is not the table’s (achrom_mappingchanged under it) – the last three INFO, since something is off and a repair is due. Until it runs the score’s reads resolve live. Asked of an open score.
- ranked(sources: Collection[ChromLengthSource]) list[ChromLengthSource][source]
sourcesbest first: the members are declared in ladder order.
- refuse_unanswered_source(chrom: str, source: ChromLengthSource, resolved: ChromLength, *, table_source: ChromLengthSource, contigs: list[str]) None[source]
Raise for a
sourcethat has no answer inresolved.The table’s own source is refused in the table’s words, which say whether the contig is empty or merely unmeasured; any other names the sources that do answer the contig.
- save_chrom_lengths(resource: GenomicResource, stored: StoredChromLengths) None[source]
Write
storedas the resource’sCHROM_LENGTHS_FILE.
gain.genomic_resources.genomic_scores.fragment module
FragmentScore – one value per genomic interval.
The kind whose records span a region rather than a point, and the one that
still answers to a legacy resource-type spelling; recognising that spelling
announces it through
warn_deprecated_spelling().
- class FragmentAggregate(count: int, values: tuple[str | int | float | bool | None | list[str | int | float | bool | None] | dict[str | int | float | bool | None, int], ...])[source]
Bases:
objectOne reduction of the fragments in a region, and the fragment count.
- Variables:
count (int) – The number of fragments that the read saw: the fragments that overlap the region, that pass the overlap fractions and that
score_filterkeeps. A region with no fragments gives0. A filter that rejects every fragment gives0too.values (tuple[AggregateValue, ...]) – One reduced value for each query, in the order of the queries. The same score asked twice with two aggregators gives two values. A value is
Nonewhen its aggregator has nothing to reduce. The aggregator sets the type of its value:listgives a list andvalue_countgives a dict.
- count: int
- values: tuple[str | int | float | bool | None | list[str | int | float | bool | None] | dict[str | int | float | bool | None, int], ...]
- class FragmentScore(resource: GenomicResource)[source]
Bases:
GenomicScoreA genomic score over fragments: intervals that carry attributes.
A fragment keeps its own begin and end, and fragments may overlap. A copy-number variant collection is one use of this kind. The resource type is
fragment_score. The older type namecnv_collectionis still accepted, with a deprecation warning.Build a fragment score with
build_fragment_score_from_resource_id(). The score is built closed. Callopen()before the first read.The reads come in pairs. The plural form takes a
scoreslist and returns values parallel to it. The singular form takes onescore.- Parameters:
resource (GenomicResource) – The resource of type
fragment_scoreorcnv_collection.- Raises:
ValueError – If the type of the resource is not a fragment score type.
- DEFAULT_AGGREGATORS: ClassVar[dict[str, str | None]] = {'bool': None, 'float': 'max', 'int': 'max', 'str': 'join(,)'}
The default aggregator for each value type, for a score whose configuration states no
aggregator:maxforfloatandint,join(,)forstrand none forbool.
- HISTOGRAM_COUNT_UNIT: ClassVar[str] = 'fragments'
"fragments".- Type:
The unit that the histograms of this kind count
- get_fragment_arrays_overlapping_region(chrom: str, start: int, end: int, *, scores: list[str] | None = None, batch_size: int = 100000) Generator[tuple[ndarray, ndarray, dict[str, ndarray]], None, None][source]
Yield the fragments that overlap a region, as column arrays.
The array form of
get_fragment_scores_overlapping_region(). The concatenated batches hold the same fragments, in the same order, as that read. Each fragment keeps its own extent: the read does not clip a fragment to the region, also where the fragment runs past it.The values follow the parse of
fetch_region_value_arrays(): anintor afloatscore givesfloat64withnanfor no value, and astrscore gives anobjectarray withNonefor no value.- Parameters:
chrom (str) – The contig name.
start (int) – The first position of the region (1-based, inclusive).
end (int) – The last position of the region (1-based, inclusive).
scores (list[str] | None, default None) – The score ids to read.
Nonereads every score of the resource.batch_size (int, default 100000) – The hint for the rows of a batch that
fetch_region_value_arrays()takes.
- Returns:
Batches of
(begins, ends, {score_id: values}). The keys of the mapping follow the order ofscores. The mapping holds one column for each distinct score id: a score id thatscoresrepeats gives one key, not one value for each repeat as the record read gives. A batch holds at least one fragment: the generator is empty when no fragment overlaps the region.- Return type:
Generator[RecordArrays, None, None]
- Raises:
ValueError – If
startis below 1,endis beforestart, the score is closed, or the contig or a score id is unknown.TypeError – If
supports_region_value_arrays()isFalsefor the scores.
- get_fragment_arrays_starting_in_region(chrom: str, start: int, end: int, *, scores: list[str] | None = None, batch_size: int = 100000) Generator[tuple[ndarray, ndarray, dict[str, ndarray]], None, None][source]
Yield the fragments that begin in a region, as column arrays.
The array form of
get_fragment_scores_starting_in_region(). The concatenated batches hold the same fragments, in the same order, as that read. Each fragment keeps its own extent: the read does not clip a fragment to the region.The values follow the parse of
fetch_region_value_arrays(): anintor afloatscore givesfloat64withnanfor no value, and astrscore gives anobjectarray withNonefor no value.- Parameters:
chrom (str) – The contig name.
start (int) – The first position of the region (1-based, inclusive).
end (int) – The last position of the region (1-based, inclusive).
scores (list[str] | None, default None) – The score ids to read.
Nonereads every score of the resource.batch_size (int, default 100000) – The hint for the rows of a batch that
fetch_region_value_arrays()takes. A batch of this read can hold fewer rows.
- Returns:
Batches of
(begins, ends, {score_id: values}). The keys of the mapping follow the order ofscores. The mapping holds one column for each distinct score id: a score id thatscoresrepeats gives one key, not one value for each repeat as the record read gives. A batch holds at least one fragment: the generator is empty when no fragment begins in the region.- Return type:
Generator[RecordArrays, None, None]
- Raises:
ValueError – If
startis below 1,endis beforestart, the score is closed, or the contig or a score id is unknown.TypeError – If
supports_region_value_arrays()isFalsefor the scores.
- get_fragment_score_at_position(chrom: str, pos: int, *, score: str | None = None, score_filter: ScoreFilter | None = None) Sequence[tuple[int, int, str | int | float | bool | None]][source]
Return the fragments that cover a position, with the value of one score.
This is the singular form of
get_fragment_scores_at_position().- Parameters:
chrom (str) – The contig name.
pos (int) – The position (1-based).
score (str | None, default None) – The score id.
Noneis valid only when the resource defines exactly one score.score_filter (ScoreFilter | None, default None) – A filter from
compile_filter().Nonekeeps every fragment.
- Returns:
One
(begin, end, value)tuple for each fragment. The list is empty when no fragment covers the position.- Return type:
Sequence[tuple[int, int, ScoreValue]]
- Raises:
ValueError – If
scoreisNoneand the resource defines more than one score, or for any reason thatget_fragment_scores_at_position()lists.
- get_fragment_score_overlapping_region(chrom: str, start: int, end: int, *, score: str | None = None, score_filter: ScoreFilter | None = None, min_region_overlap_fraction: float | None = None, min_fragment_overlap_fraction: float | None = None) Generator[tuple[int, int, str | int | float | bool | None], None, None][source]
Yield the fragments that overlap a region, with the value of one score.
This is the singular form of
get_fragment_scores_overlapping_region(). The same limits apply.- Parameters:
chrom (str) – The contig name.
start (int) – The first position of the region (1-based, inclusive).
end (int) – The last position of the region (1-based, inclusive).
score (str | None, default None) – The score id.
Noneis valid only when the resource defines exactly one score.score_filter (ScoreFilter | None, default None) – A filter from
compile_filter().Nonekeeps every fragment.min_region_overlap_fraction (float | None, default None) – The fragment must cover at least this fraction of the region.
Nonesets no threshold.min_fragment_overlap_fraction (float | None, default None) – At least this fraction of the fragment must fall in the region.
Nonesets no threshold.
- Returns:
One
(begin, end, value)tuple for each fragment. The value isNonewhen the fragment has no value for the score. The generator is empty when no fragment overlaps the region.- Return type:
Generator[tuple[int, int, ScoreValue], None, None]
- Raises:
ValueError – If
scoreisNoneand the resource defines more than one score, or for any reason thatget_fragment_scores_overlapping_region()lists.
- get_fragment_score_overlapping_region_agg(chrom: str, start: int, end: int, *, score: str | None = None, aggregator: str | None = None, min_region_overlap_fraction: float | None = None, min_fragment_overlap_fraction: float | None = None, score_filter: ScoreFilter | None = None) FragmentAggregate[source]
Reduce the fragments that overlap a region to one value for one score.
This is the singular form of
get_fragment_scores_overlapping_region_agg(). It returns the sameFragmentAggregate, andvalueshas one element.- Parameters:
chrom (str) – The contig name.
start (int) – The first position of the region (1-based, inclusive).
end (int) – The last position of the region (1-based, inclusive).
score (str | None, default None) – The score id.
Noneis valid only when the resource defines exactly one score.aggregator (str | None, default None) – The aggregator.
Noneuses the default aggregator of the score.min_region_overlap_fraction (float | None, default None) – The fragment must cover at least this fraction of the region.
Nonesets no threshold.min_fragment_overlap_fraction (float | None, default None) – At least this fraction of the fragment must fall in the region.
Nonesets no threshold.score_filter (ScoreFilter | None, default None) – A filter from
compile_filter().Nonekeeps every fragment.
- Returns:
The number of fragments that the read saw, and a tuple with the one reduced value.
- Return type:
- Raises:
ValueError – If
scoreisNoneand the resource defines more than one score, or for any reason thatget_fragment_scores_overlapping_region_agg()lists.
- get_fragment_score_starting_in_region(chrom: str, start: int, end: int, *, score: str | None = None, score_filter: ScoreFilter | None = None) Generator[tuple[int, int, str | int | float | bool | None], None, None][source]
Yield the fragments that begin in a region, with the value of one score.
This is the singular form of
get_fragment_scores_starting_in_region(). The same partition and the same limits apply.- Parameters:
chrom (str) – The contig name.
start (int) – The first position of the region (1-based, inclusive).
end (int) – The last position of the region (1-based, inclusive).
score (str | None, default None) – The score id.
Noneis valid only when the resource defines exactly one score.score_filter (ScoreFilter | None, default None) – A filter from
compile_filter().Nonekeeps every fragment.
- Returns:
One
(begin, end, value)tuple for each fragment. The generator is empty when no fragment begins in the region.- Return type:
Generator[tuple[int, int, ScoreValue], None, None]
- Raises:
ValueError – If
scoreisNoneand the resource defines more than one score, or for any reason thatget_fragment_scores_starting_in_region()lists.
- get_fragment_scores_at_position(chrom: str, pos: int, *, scores: list[str] | None = None, score_filter: ScoreFilter | None = None) Sequence[tuple[int, int, tuple[str | int | float | bool | None, ...]]][source]
Return the fragments that cover a position.
Each fragment is reported at its full extent, not clipped to the position.
- Parameters:
chrom (str) – The contig name.
pos (int) – The position (1-based).
scores (list[str] | None, default None) – The score ids to read.
Nonereads every score of the resource.score_filter (ScoreFilter | None, default None) – A filter from
compile_filter().Nonekeeps every fragment.
- Returns:
One
(begin, end, values)tuple for each fragment. The list is empty when no fragment covers the position. You can keep the list across a later read.- Return type:
Sequence[tuple[int, int, tuple[ScoreValue, …]]]
- Raises:
ValueError – If
posis below 1, or for any other reason thatget_fragment_scores_overlapping_region()lists.
- get_fragment_scores_overlapping_region(chrom: str, start: int, end: int, *, scores: list[str] | None = None, score_filter: ScoreFilter | None = None, min_region_overlap_fraction: float | None = None, min_fragment_overlap_fraction: float | None = None) Generator[tuple[int, int, tuple[str | int | float | bool | None, ...]], None, None][source]
Yield the fragments that overlap a region.
Each fragment is reported at its own extent. The extent is not clipped to the region, also where the fragment runs past it. The fragments come in table order.
The reads check the request when you call them, and read the data later. A closed score, an unknown contig, an unknown score id, an invalid region and an overlap fraction outside the range from 0 to 1 raise before the first
next().A score serves one region read at a time. Starting a second read invalidates a read that you still consume, and a tabix-backed table then answers with the records of the other read and no error. Close a held generator, or call
list(...)on it, before the next read.- Parameters:
chrom (str) – The contig name.
start (int) – The first position of the region (1-based, inclusive).
end (int) – The last position of the region (1-based, inclusive).
scores (list[str] | None, default None) – The score ids to read.
Nonereads every score of the resource.score_filter (ScoreFilter | None, default None) – A filter from
compile_filter(). The read drops the fragments that the filter rejects. The filter may name a score that is not inscores.Nonekeeps every fragment.min_region_overlap_fraction (float | None, default None) – The fragment must cover at least this fraction of the region.
Nonesets no threshold.min_fragment_overlap_fraction (float | None, default None) – At least this fraction of the fragment must fall in the region.
Nonesets no threshold.
- Returns:
One
(begin, end, values)tuple for each fragment.valuesis parallel toscores. A value isNonewhen the fragment has no value for that score. The generator is empty when no fragment overlaps the region.- Return type:
Generator[tuple[int, int, tuple[ScoreValue, …]], None, None]
- Raises:
ValueError – If the score is closed, the contig or a score id is unknown,
startis below 1,endis beforestart, or an overlap fraction is outside the range from 0 to 1.
- get_fragment_scores_overlapping_region_agg(chrom: str, start: int, end: int, *, queries: Sequence[ScoreAggregationQuery] | None = None, min_region_overlap_fraction: float | None = None, min_fragment_overlap_fraction: float | None = None, score_filter: ScoreFilter | None = None) FragmentAggregate[source]
Reduce the fragments that overlap a region to one value for each query.
The read selects the fragments as
get_fragment_scores_overlapping_region()does. The overlap fractions andscore_filterhave the same meaning. Each fragment counts once, however long it is. The read folds the fragments as they arrive and does not keep them. A few aggregators keep one entry for each fragment while they run:list,median,concatenateandjoin. The default aggregator of astrscore isjoin(,), which is one of them.The
countof the result counts fragments. A query with thecountaggregator counts the non-null values instead, and gives0for an empty region.- Parameters:
chrom (str) – The contig name.
start (int) – The first position of the region (1-based, inclusive).
end (int) – The last position of the region (1-based, inclusive).
queries (Sequence[ScoreAggregationQuery] | None, default None) – The scores to reduce, each with an aggregator. A query with no aggregator uses the default of the score.
Nonereduces every score of the resource with its default aggregator.min_region_overlap_fraction (float | None, default None) – The fragment must cover at least this fraction of the region.
Nonesets no threshold.min_fragment_overlap_fraction (float | None, default None) – At least this fraction of the fragment must fall in the region.
Nonesets no threshold.score_filter (ScoreFilter | None, default None) – A filter from
compile_filter().Nonekeeps every fragment.
- Returns:
The number of fragments that the read saw, and one reduced value for each query. When no fragment overlaps the region,
countis0.- Return type:
- Raises:
ValueError – If a query names an unknown score or an aggregator that the score does not accept, or for any reason that
get_fragment_scores_overlapping_region()lists.
- get_fragment_scores_starting_in_region(chrom: str, start: int, end: int, *, scores: list[str] | None = None, score_filter: ScoreFilter | None = None) Generator[tuple[int, int, tuple[str | int | float | bool | None, ...]], None, None][source]
Yield the fragments that begin in a region.
The read yields the fragments whose begin lies in
[start, end]. A set of adjacent windows answers each fragment from exactly one window, with no duplicate and no gap. Use this read for chunked and parallel work.get_fragment_scores_overlapping_region()answers a fragment from every window that it reaches into.Each fragment is reported at its own extent. The limit of one live read per score applies, as for
get_fragment_scores_overlapping_region(). The read has no overlap fractions, because a fraction filter could let a fragment fall out of every window.- Parameters:
chrom (str) – The contig name.
start (int) – The first position of the region (1-based, inclusive).
end (int) – The last position of the region (1-based, inclusive).
scores (list[str] | None, default None) – The score ids to read.
Nonereads every score of the resource.score_filter (ScoreFilter | None, default None) – A filter from
compile_filter().Nonekeeps every fragment.
- Returns:
One
(begin, end, values)tuple for each fragment.valuesis parallel toscores. The generator is empty when no fragment begins in the region.- Return type:
Generator[tuple[int, int, tuple[ScoreValue, …]], None, None]
- Raises:
ValueError – If
startis below 1,endis beforestart, the score is closed, or the contig or a score id is unknown.
- static get_schema() dict[str, Any][source]
Return the configuration schema of a fragment score resource.
- Returns:
The schema of a genomic score, with an
aggregatorentry added to each score definition.- Return type:
dict[str, Any]
- get_scores_in_bins(chrom: str, start: int, end: int, bin_size: int, queries: Sequence[ScoreAggregationQuery]) ndarray[source]
Reduce the fragments that start in each bin, one column for each query.
The grid is global and starts at position 1. Row
iis the bin with indexcalc_bin_index(bin_size, start) + i. The rows are whole bins. The read does not clip them to the region.A fragment belongs to the bin that holds its begin, however far it runs. The blocks of two adjacent calls that split on a bin edge stack into the block of their union. The fragments are those that
get_fragment_scores_starting_in_region()yields for[start, end].The fragment count of a bin is the query
ScoreAggregationQuery(<score>, "count"). It counts the fragments that start in the bin and have a value for the score. For a score that every fragment carries, it is the number of fragments that start in the bin.The read answers all queries from one read of the region. It materialises the block, so the limit of one live read applies for the duration of the call only.
- Parameters:
chrom (str) – The contig name. A contig that the resource lacks gives the block of empty cells, not an error.
start (int) – The first position of the region (1-based, inclusive).
end (int) – The last position of the region (1-based, inclusive).
bin_size (int) – The width of a bin in positions. It must be at least 1.
queries (Sequence[ScoreAggregationQuery]) – The scores to reduce, each with an aggregator. A query with no aggregator uses the default of the score.
- Returns:
A
float64array of shape(n_bins, len(queries)). Columnjreducesqueries[j]. An empty cell is0forcountandsum. It isNaNformean,max,min,medianandproduct.- Return type:
numpy.ndarray
- Raises:
ValueError – If
startis below 1,endis beforestart,bin_sizeis below 1, a score is unknown, or an aggregator is not accepted for the score. The read raises before it reads the first fragment.
gain.genomic_resources.genomic_scores.position module
PositionScore – one value per genomic position.
The kind whose records carry no reference or alternative allele, so a position is the whole key. Adds the position-run reads and the binned and aggregated region queries built on them.
- class PositionScore(resource: GenomicResource)[source]
Bases:
GenomicScorePosition-based genomic score resource.
A position score gives one or more scores for a genomic position or a run of positions. Unlike an allele score, it ignores the reference and alternative alleles. The data lives in a tabix-indexed file, a bigWig file or an in-memory table.
Typical resources are:
conservation scores, such as
phastConsandphyloPmappability scores
GC content
recombination rates
any metric that depends only on the genomic position
A score is built closed. Call
open()before the first read.Examples
import gain.grr as grr repo = grr.build_genomic_resource_repository() score = grr.build_position_score_from_resource_id( "hg38/scores/phastCons100way", repo) with score.open() as score: values = score.get_scores_at_position("chr1", 12345) for begin, end, row in score.fetch_region_segments_scores( "chr1", 10000, 20000): print(f"{begin}-{end}: {row}")
- Variables:
resource (GenomicResource) – The underlying genomic resource.
resource_id (str) – The identifier of the resource.
config (dict) – The validated configuration of the score.
score_definitions (dict[str, GenomicScoreDef]) – The definition of each score, by score id.
HISTOGRAM_COUNT_UNIT (str) – The unit of the histogram counts. For a position score it is
"base pairs".DEFAULT_AGGREGATORS (dict[str, str | None]) – The aggregator name that a region read uses for each value type, when the configuration of the score names none. A value of
Nonemeans that the type takes no default.
- DEFAULT_AGGREGATORS: ClassVar[dict[str, str | None]] = {'bool': None, 'float': 'mean', 'int': 'mean', 'str': 'list'}
- HISTOGRAM_COUNT_UNIT: ClassVar[str] = 'base pairs'
The noun for what a histogram’s
ncounts – the weighting the statistics scan gives each record of this family: base pairs for a position score (record_weight), alleles, fragments, genes. Every concrete family states its own; the info pages read it.
- static get_schema() dict[str, Any][source]
Return the configuration schema of a position score.
The schema is the one of
GenomicScore, with anaggregatorkey added to the definition of each score.- Returns:
A new Cerberus schema dictionary, which the caller may modify.
- Return type:
dict[str, Any]
- get_score_at_position(chrom: str, pos: int, score: str | None = None) str | int | float | bool | None[source]
Return the value of one score at one position.
This is the singular form of
get_scores_at_position().- Parameters:
chrom (str) – The chromosome name.
pos (int) – The 1-based position.
score (str | None) – The id of the score.
Noneis accepted only when the resource defines exactly one score.
- Returns:
The value of the score.
Nonemeans that no record covers the position, or that the record has no value for the score.- Return type:
ScoreValue | None
- Raises:
ValueError – If the score is closed, the chromosome is unknown, the score id is unknown, or
scoreisNoneand the resource defines more than one score.
- get_score_in_bins(chrom: str, start: int, end: int, bin_size: int, score: str | None = None, aggregator: str | None = None, none_value_replacement: str | int | float | bool | None = None) Generator[tuple[int, int, str | int | float | bool | None], None, None][source]
Yield the aggregated value of one score for each bin of a region.
This is the singular form of
get_scores_in_bins().- Parameters:
chrom (str) – The chromosome name.
start (int) – The first 1-based position of the region.
end (int) – The last 1-based position of the region.
bin_size (int) – The width of a bin in positions. It must be at least 1.
score (str | None) – The id of the score.
Noneis accepted only when the resource defines exactly one score.aggregator (str | None) – The name of the aggregator.
Noneselects the aggregator that the configuration of the score names, or the default one for its value type.none_value_replacement (ScoreValue | None) – The value that stands for an uncovered position.
Nonemeans that an uncovered position is skipped by the aggregator.
- Yields:
tuple[int, int, ScoreValue] – The first position, the last position and the aggregated value of a bin, in position order. The value is
Nonewhen the aggregator has no value to report for the bin.- Raises:
ValueError – If the score is closed, the region is not valid,
bin_sizeis less than 1, the score id is unknown, no aggregator can be chosen, ornone_value_replacementdoes not match the value type.
- get_score_in_region(chrom: str, start: int, end: int, score: str | None = None) Generator[str | int | float | bool | None, None, None][source]
Yield the value of one score at each position of a region.
This is the singular form of
get_scores_in_region().- Parameters:
chrom (str) – The chromosome name.
start (int) – The first 1-based position of the region.
end (int) – The last 1-based position of the region.
score (str | None) – The id of the score.
Noneis accepted only when the resource defines exactly one score.
- Yields:
ScoreValue | None – One value for each position, in position order. A
Nonevalue means that no record covers the position, or that the record has no value for the score.- Raises:
ValueError – If the score is closed, the region is not valid, the chromosome is unknown, or the score id is unknown. The function raises before the first value, not during the iteration.
- get_score_in_region_agg(chrom: str, start: int, end: int, score: str | None = None, aggregator: str | None = None, none_value_replacement: str | int | float | bool | None = None) str | int | float | bool | None | list[str | int | float | bool | None] | dict[str | int | float | bool | None, int][source]
Reduce a region to one value for one score.
This is the singular form of
get_scores_in_region_agg().- Parameters:
chrom (str) – The chromosome name.
start (int) – The first 1-based position of the region.
end (int) – The last 1-based position of the region.
score (str | None) – The id of the score.
Noneis accepted only when the resource defines exactly one score.aggregator (str | None) – The name of the aggregator.
Noneselects the aggregator that the configuration of the score names, or the default one for its value type.none_value_replacement (ScoreValue | None) – The value that stands for an uncovered position.
Nonemeans that an uncovered position is skipped by the aggregator.
- Returns:
The aggregated value. It is
Nonewhen the aggregator has no value to report, for example when no position of the region has a value. The aggregator sets the type of the value:listgives a list andvalue_countgives a dict.- Return type:
AggregateValue
- Raises:
ValueError – If the score is closed, the region is not valid, the score id is unknown, no aggregator can be chosen, or
none_value_replacementdoes not match the value type.
- get_scores_at_position(chrom: str, pos: int, scores: Sequence[str] | None = None) tuple[str | int | float | bool | None, ...][source]
Return the values of the scores at one position.
- Parameters:
chrom (str) – The chromosome name.
pos (int) – The 1-based position.
scores (Sequence[str] | None) – The ids of the scores.
Noneasks for every score that the resource defines, in definition order.
- Returns:
One value for each requested score, in the order of
scores. ANonevalue means that no record covers the position, or that the record has no value for that score. The tuple is empty whenscoresis an empty sequence.- Return type:
tuple[ScoreValue | None, …]
- Raises:
ValueError – If the score is closed, the chromosome is unknown, or a score id is unknown.
- get_scores_in_bins(chrom: str, start: int, end: int, bin_size: int, queries: Sequence[PositionScoreAggregationQuery]) Generator[tuple[int, int, tuple[str | int | float | bool | None, ...]], None, None][source]
Yield the aggregated values of each bin of a region.
The bins follow the global grid that starts at position 1, so the results of adjacent regions tile and are comparable across calls. The first and the last bin are clipped to the region. The function emits every bin of the region, also a bin that no record touches.
- Parameters:
chrom (str) – The chromosome name.
start (int) – The first 1-based position of the region.
end (int) – The last 1-based position of the region.
bin_size (int) – The width of a bin in positions. It must be at least 1.
queries (Sequence[PositionScoreAggregationQuery]) – The queries. Each names a score, an aggregator and a replacement value for the uncovered positions.
- Yields:
tuple[int, int, tuple[ScoreValue, …]] – The first position, the last position and one aggregated value for each query of a bin, in position order. A value is
Nonewhen the aggregator has no value to report.- Raises:
ValueError – If the score is closed, the region is not valid,
bin_sizeis less than 1, or a query is not valid (seeresolve_aggregation_queries()).
Notes
A chromosome that the resource never mentions is not an error. The function emits every bin of it, as for a region that no record covers.
- get_scores_in_region(chrom: str, start: int, end: int, scores: Sequence[str] | None = None) Generator[tuple[str | int | float | bool | None, ...], None, None][source]
Yield the values of the scores at each position of a region.
- Parameters:
chrom (str) – The chromosome name.
start (int) – The first 1-based position of the region.
end (int) – The last 1-based position of the region.
scores (Sequence[str] | None) – The ids of the scores.
Noneasks for every score that the resource defines, in definition order.
- Yields:
tuple[ScoreValue | None, …] – Exactly
end - start + 1tuples, in position order. A tuple has one value for each requested score. ANonevalue means that no record covers the position, or that the record has no value for that score.- Raises:
ValueError – If the score is closed, the region is not valid, the chromosome is unknown, or a score id is unknown. The function raises before the first tuple, not during the iteration.
- get_scores_in_region_agg(chrom: str, start: int, end: int, queries: Sequence[PositionScoreAggregationQuery]) tuple[str | int | float | bool | None | list[str | int | float | bool | None] | dict[str | int | float | bool | None, int], ...][source]
Reduce a region to one value for each query.
Each position of the region counts once. A record counts once for each position of the region that it covers.
- Parameters:
chrom (str) – The chromosome name.
start (int) – The first 1-based position of the region.
end (int) – The last 1-based position of the region.
queries (Sequence[PositionScoreAggregationQuery]) – The queries. Each names a score, an aggregator and a replacement value for the uncovered positions.
- Returns:
One value for each query, in the order of
queries. A value isNonewhen the aggregator has no value to report. The aggregator sets the type of its value:listgives a list andvalue_countgives a dict. The tuple is empty whenqueriesis empty.- Return type:
tuple[AggregateValue, …]
- Raises:
ValueError – If the score is closed, the region is not valid, or a query is not valid (see
resolve_aggregation_queries()).
Notes
A chromosome that the resource never mentions is not an error. It answers as a region that no record covers.
- resolve_aggregation_queries(queries: Sequence[PositionScoreAggregationQuery]) list[tuple[str, str, str | int | float | bool | None]][source]
Resolve each query to a score id, an aggregator name and a value.
Use it to find out whether a query is answerable without reading. The position score annotator calls it once when the pipeline loads.
- Parameters:
queries (Sequence[PositionScoreAggregationQuery]) – The queries. Each names a score, an aggregator, and a replacement value for the uncovered positions.
- Returns:
One tuple for each query, in the order of
queries: the id of the score, the name of the aggregator, and thenone_value_replacementof the query. The list is empty whenqueriesis empty.- Return type:
list[tuple[str, str, ScoreValue]]
- Raises:
ValueError – If a score id is unknown, a
none_value_replacementhas a type that the score cannot take, or a query names no aggregator and the value type of the score has no default one.
gain.genomic_resources.genomic_scores.records module
Batch array types and the region/record algebra over them.
The half of the score layer that knows nothing about score resources: the
shape of a read batch (RecordArrays, AlleleRecordArrays)
and the five functions that decide which part of a record a region gets,
or whether it gets it at all.
Two of those partition different things and are deliberately neighbours –
clip_span() partitions POSITIONS, owns_record() partitions
RECORDS by where they BEGIN – so that the two halves of the algebra have one
home and a caller picks the one it means. overlap_fractions_admit()
sits beside them without partitioning anything: it SELECTS records by how
much of the region, or of themselves, the two share, and a record it rejects
is answered by no region at all.
Nothing here imports a score class. That is a property of this module, not
yet a saving for its callers: the scan and the statistics layer still reach
these names through the package facade, which imports every submodule, so
GenomicScore is loaded either way. What it buys is that
those callers CAN be pointed at genomic_scores.records directly, one at
a time, without anything else moving – migrating them was out of scope for
the gain#902 split that created this module.
- class AlleleRecordArrays(pos_begin: np.ndarray, pos_end: np.ndarray, values: dict[str, np.ndarray], reference: np.ndarray, alternative: np.ndarray)[source]
Bases:
NamedTupleOne batch as
AlleleScore.fetch_region_allele_arrays()makes it.RecordArrayswidened by the two key columns an allele row has and a position row does not. The first three fields are that tuple exactly, in the same order, sobatch[:3]is aRecordArrays.That slice is required, not decorative: every consumer of the shared read unpacks three names (the scan’s array door
validate_record_arrays()and its coverage accumulator among them), and handing one of them a batch of five raisestoo many values to unpack. A caller feeding this read into machinery written for the shared one passesbatch[:3], and mypy says so too – this type is not aRecordArrays.referenceandalternativeare the cells as stored – see the fetch method for why they are the one part of a batch that is not parsed.- alternative: ndarray
Alias for field number 4
- pos_begin: ndarray
Alias for field number 0
- pos_end: ndarray
Alias for field number 1
- reference: ndarray
Alias for field number 3
- values: dict[str, ndarray]
Alias for field number 2
- RecordArrays
GenomicScore.fetch_region_value_arrays() <.base.GenomicScore.fetch_region_value_arrays> produces it: the RAW one-based begin and end columns, plus one parsed value array per requested score id. Named because the vectorized scan validators are transducers over a stream of these.
- Type:
One batch as
- Type:
meth
alias of
tuple[ndarray,ndarray,dict[str,ndarray]]
- clip_span(rec_begin: int, rec_end: int, pos_begin: int | None, pos_end: int | None) tuple[int, int] | None[source]
Clip a record’s span to a queried window: skip, clip, or refuse.
Returns the part of
[rec_begin, rec_end]inside[pos_begin, pos_end], where aNonebound means unbounded on that side, orNonefor a record with no part inside the region: one ending before it (the skip) or one starting past it (which naive clipping would turn into an inverted span, whose width as a weight is negative).
- clip_to_region(segments: Iterator[tuple[int, int, T]], pos_begin: int | None, pos_end: int | None) Generator[tuple[int, int, T], None, None][source]
Clip a segment stream to a region, dropping what falls outside.
- overlap_fractions_admit(rec_begin: int, rec_end: int, start: int, end: int, min_region_fraction: float | None, min_record_fraction: float | None) bool[source]
Whether a record overlaps a region by enough of either side.
With overlap the length of the intersection,
min_region_fractionisoverlap / region_length– “the record covers at least this much of MY region” – andmin_record_fractionisoverlap / record_length– “at least this much of the RECORD falls in my region”. The two answer different questions: a 10 bp record inside a 1 Mb region scores ~0.00001 on the first and 1.0 on the second.Every threshold supplied must hold, and each is compared with
>=, so1.0means full containment of the side it is about. BothNoneadmits everything, which is what a region read does with no thresholds at all.0.0is not the same request asNone, though both admit every record a region query actually answers:0 / length >= 0.0holds, so0.0also admits a record with NO overlap at all. A region query answering with such a row is a backend that over-returns; one cause of it – a tabix table whose index andpos_endname different columns – ADR 0008 refuses atopen(), and no threshold here is a check for the rest.A SELECTION predicate, not a reshaping one: it says whether the record is answered, never what span is reported for it.
rec_endis assumed not to precederec_begin, so that the record length it divides by is at least 1. This does not check it: the record reads refuse an inverted span at_score_segments(), before any consumer sees the record. Called with one directly, it divides by zero or worse.
- owned_records_mask(pos_begin: ndarray, start: int | None, end: int | None) ndarray[source]
owns_record()over a whole batch’s begin column.
- owns_record(begin: int, start: int | None, end: int | None) bool[source]
Whether a region owns a record, by where that record BEGINS.
The scan’s partition of RECORDS, and the one statement of it (gain#816). Ownership is total, unique and reachable: the regions tile a contig contiguously from position 1, so every record’s begin falls in exactly one of them, and the owning region’s query always returns it –
begininside[start, end]is by itself an overlap. An unbounded side owns everything on that side, which is the same rule with one region.Contrast
clip_span(), which partitions POSITIONS. A statistic that sums over records wants this; one that unions positions wants that. Both live here so the two halves of the algebra have one home.
gain.genomic_resources.genomic_scores.value_extraction module
How a record’s cell becomes a value: the two decisions taken at open.
The seam between a score and its table’s payload. Both decisions are taken
once per open, from the table’s declared payload_kind and the score
definitions, and neither needs a GenomicScore:
select_value_extractor()picks the per-record read, beforetable.open();resolve_score_indices()addresses each definition to a payload column, after it.
That order is load-bearing – each function’s docstring says what forces its
half – and it belongs to the caller, open().
The extractors themselves are not here: they live with the backends that
know what a payload IS – vcf_scores,
bigwig_scores, and
score_def for the column read, which also
owns the ValueExtractor alias. This module only chooses between
them, which is why it sits ABOVE all three rather than inside one of them:
hosting the choice in score_def, where gain#1044 put the scoredef
lifecycle, would have it import vcf_scores and bigwig_scores, both
of which import score_def – the same cycle that kept
GenomicScore._build_scoredefs on the class. resolve_score_indices
alone could have gone there; it is here so that the seam reads as one
module rather than two homes.
resolve_score_indices() mutates the definitions in place and
returns nothing; its docstring says who reads what it wrote.
- resolve_score_indices(score_definitions: dict[str, GenomicScoreDef], *, table: GenomicPositionTable, resource_id: str) None[source]
Resolve each score’s configured address to a payload column.
Runs after
table.open(), because the by-NAME case is the one thing here that has to consult the table’s header. What a definition’s address means – an INFO key, nothing, or a column – is the table’sPayloadKind, read off the table.Writes
score_indexonto the definitions it is handed, and returns nothing: “the defs are finished in place at open” is the contractbasestates. Two paths read what it wrote –extract_column_value(), on every record of the tabular per-record read, and so the very extractor this module’s other half binds; andfetch_region_value_arrays, which the statistics scan reaches it through. Handing back a mapping would only mean every caller writes it back.A definition it cannot resolve – no address, two addresses, a name over a table with no header, a name the header does not have, a VCF definition with no INFO key – is refused through
score_configuration_error(), so the refusal names the resource and the score in the shape every other definition refusal shares and falls insidecli_errors.RESOURCE_ERRORSby type. Every check is an explicit test, never anassert(python -Ostrips those) and never a lookup left to raise for itself: a resource config is data, and bad data is reported. For a tabular table,validate_scoredefsholds the CONFIG to the address rules before this runs, but for a headerless table it checks only that no name is stated – so a definition with no address at all reaches this from a real config, while the other refusals are reached throughopen()only by a definition edited after its config passed.
- select_value_extractor(*, score_definitions: dict[str, GenomicScoreDef], table: GenomicPositionTable) ValueExtractor[source]
Pick the per-record value read for this table’s payload.
ONE decision, per table, taken at open rather than per line. What it turns on is what a record’s PAYLOAD is, which the backend declares in its
PayloadKind:a VARIANT payload carries the variant and the pysam INFO proxies, and its score is an INFO field addressed by name –
extract_vcf_value();a VALUE payload IS the interval’s value, so the read is an identity (
extract_bigwig_value()) – or, for the rare resource that configures NA sentinels, an identity plus one membership test (extract_bigwig_value_na()). Which of the two is settled here, from the score definitions, and never per record: the sentinel set is fixed for the life of the open score. A bigWig declares exactly one score (validate_bigwig_scoredefsrefuses more), so there is a single answer to give;anystates that without depending on it;a ROW payload is a raw row, read by integer column –
extract_column_value().
The declaration is the ONE claim read, and it is read first, because every later decision depends on it: a backend that has not made one is refused with the
AttributeErrorthe undefaulted ClassVar raises, before any other check. That a backend’s records are what its kind says – a ROW backend really yields six-slot records with a raw row in the payload slot – is pinned statically, over every backend in the tree, by test_backend_record_contract.py, so the fetch path pays nothing for it.
Module contents
Genomic score resources: the base class, its three kinds, and the algebra.
This package was one 3128-line module until gain#902 split it along the seams the code already had:
records– the batch array types and the region/record algebra over them; imports no score class, so the scan and the statistics layer can use it without pullingGenomicScorein behind itbase–GenomicScore, everything the kinds shareposition,allele,fragment– one module per kindbuilders– the eight factories, and the dispatch between kinds
and decomposing the class itself (gain#1027) has since added two more:
aggregation(gain#1074) – the machinery the per-kind aggregating reads share (get_scores_in_region_agg(),get_allele_scores_in_region_agg()andget_fragment_scores_overlapping_region_agg()); knows no score class, and is handed the per-kind weight rule rather than reading itvalue_extraction(gain#1114) – the two decisionsopen()takes about how a record’s cell becomes a value: which extractor reads the payload, and which payload column each score def is addressed tochrom_lengths(gain#1413) – the chromosome-length resolver a caller with a genome hands it to: per contig, every source’s length with the best by rank, and theContigExtentreason when the table has none; the storedstatistics/chrom_lengths.jsona repair writes (gain#1576), and the load-on-open, source normalisation and refusal wording behind the score’s own length reads –get_chrom_length(),get_all_chrom_lengths(),get_chrom_length_source()andchrom_length_sources(gain#1577)
This module is a permanent facade, not a deprecation shim. It
re-exports every name the pre-split module DEFINED, so each
from gain.genomic_resources.genomic_scores import <name> written before
the split keeps working, verbatim and indefinitely. Deep imports from the
submodules are allowed and equivalent, but no caller is expected to migrate
to them: gpf imports through here, and so do out-of-tree callers this
repository cannot see or fix (grr_bench, demo repositories). The
promise is pinned by test_genomic_scores_facade.py, which spells the
surface out rather than deriving it from __all__.
What it deliberately does not carry is the ~70 names the pre-split module
merely IMPORTED and so leaked as incidental re-exports – np, copy,
Record, GenomicScoreDef, ScoreValue and the like. Reaching
through this module for one of those worked by accident, never by
intention, and an AST scan of gain, gpf and grr_bench (2026-08-31)
found no caller that did. Import them from the module that defines them.
What has since moved OUT is the pair of scan validation rules.
validate_records and validate_record_arrays were
@abstractmethod on GenomicScore with one body per kind
here; they are now singledispatch functions in
gain.genomic_resources.statistics.record_validation, registered per
kind, because the statistics scan is their only caller and a read class
should not carry a rule only its consumer applies (gain#1269, ADR 0027).
They were never part of the pre-split module’s re-exported surface – they
were instance methods – so the facade promise above is untouched.
What did NOT move here is the resource implementation –
genomic_scores_impl, which gain#1007 split into scan and classes and
gain#1210 then laid out as this package is, one module per kind – nor
the decomposition of the GenomicScore class itself, which
is gain#1027 and is deliberately sequenced after this split.