uchrom.fea

Geometric / statistical features over chromatin traces.

class uchrom.fea.DistanceMap[source]

Bases: dict

Result of distance_map() (a dict with attribute access): matrix (n_bins × n_bins), count (traces observing each pair; the diagonal counts the traces observing the bin), bins (bin_id, chrom, start, end of the rows), n_traces, stat, streaming, n_passes (reads of the chromosome’s traces) and n_bands.

uchrom.fea.add_annotation_features(cdata, gtf: str | Path | DataFrame, *, features: Sequence[str] | None = None, prefix: str = 'gtf', result_key: str = 'bin_features', project: bool = True, store: bool = True, overwrite: bool = False, promoter_window: tuple[int, int] = (-2000, 500), hash_source: bool = False) → DataFrame[source]

Compute annotation features for a ChromData object.

uchrom.fea.add_peak_features(cdata, peaks: DataFrame, *, name: str = 'peak', prefix: str = 'peak', result_key: str = 'bin_features', project: bool = True, store: bool = True, overwrite: bool = False, source_peak_key: str | None = None) → DataFrame[source]

Project an existing peak table into interval and spot features.

uchrom.fea.add_sequence_features(cdata, fasta: str | Path, *, features: Sequence[str] | None = None, prefix: str = 'seq', result_key: str = 'bin_features', project: bool = True, store: bool = True, overwrite: bool = False, hash_source: bool = False, g4_pattern: str = 'G{3,}[ACGTN]{1,7}G{3,}[ACGTN]{1,7}G{3,}[ACGTN]{1,7}G{3,}') → DataFrame[source]

Compute sequence features for a ChromData object.

The canonical interval table is returned. When store=True it is also merged into cdata.results[result_key]. When project=True feature columns are projected onto cdata.bin_tracks using prefix.

uchrom.fea.aggregate_track_by_interval(cdata, track: str, *, agg: str = 'mean', signal_col: str = 'signal') → DataFrame[source]

Aggregate a spot-aligned track to unique genomic intervals.

Multiple spots can share the same genomic bin across cells or traces. Peak callers should operate on the genomic bin signal rather than individual spot rows, so this helper collapses duplicate intervals first.

uchrom.fea.aggregate_tracks_by_interval(cdata, tracks: Sequence[str], *, agg: str = 'mean') → DataFrame[source]

Aggregate multiple spot-aligned tracks to unique genomic intervals.

uchrom.fea.append_feature_registry_entry(cdata, entry: Mapping[str, Any], *, key: str = 'feature_registry') → dict[str, Any][source]

Append a provenance record to cdata.uns[key].

The registry is stored as a list of JSON-like dicts so it round-trips through the existing uns serializer.

uchrom.fea.axis_variance_cube(cd, chrom: str, device: str = 'auto') → dict[source]

Compute per-axis pairwise variance + sample-count cubes.

Returns a dict with var, count, mean (all (3, B, B)) plus bin_ids, n_traces, chrom, and — for downstream filter_normalize() — the (T, B, 3) coordinate tensor on the torch device under key "coords". Pairwise differences are formed block by block, so memory stays near _BLOCK_BYTES whatever B.

uchrom.fea.axis_weight(cd, chrom: str | None = None, device: str = 'auto') → ndarray[source]

Compute per-axis weights w ∝ 1 / median(trace_variance).

For each axis we centre every trace at its own mean (bins with NaN excluded) and take the median across traces of each trace’s variance. The inverse of that median is the axis weight, normalised to sum 1. Consistent with ArcFISH’s axis_weight routine.

uchrom.fea.call_macs_bdgpeaks_from_bedgraph(path: str | Path, *, signal_col: str = 'score', cutoff: float = 5.0, min_length: int = 200, max_gap: int = 30, name: str = 'MACS', name_prefix: str | None = None) → DataFrame[source]

Read a bedGraph file and call MACS3 bdgpeakcall-compatible peaks.

uchrom.fea.call_macs_bdgpeaks_from_signal(signal_table: DataFrame, *, signal_col: str = 'score', cutoff: float = 5.0, min_length: int = 200, max_gap: int = 30, name: str = 'MACS', name_prefix: str | None = None) → DataFrame[source]

Call peaks using a Python port of MACS3 bdgpeakcall.

This mirrors MACS3’s bedGraphTrackI.call_peaks behavior for score tracks: regions with values at or above cutoff are merged when the intervening gap is at most max_gap bases, peaks shorter than min_length bases are discarded, and the summit follows MACS3’s tie-breaking rule. The port follows MACS3’s BSD-licensed bdgpeakcall and narrowPeak semantics without vendoring the MACS3 package at runtime.

uchrom.fea.call_macs_peaks_from_signal(signal_table: DataFrame, *, signal_col: str = 'signal', control_col: str | None = None, qvalue: float | None = 0.05, pvalue: float | None = None, genome_size: int | float | None = None, small_local_window_bins: int = 10, large_local_window_bins: int = 100, fragment_size_bins: int = 1, nolambda: bool = False, min_width_bins: int = 1, max_gap_bins: int = 0, signal_scale: float = 1.0, control_scale: float | None = None, negative: str = 'clip', name: str = 'peak') → DataFrame[source]

Call peaks with U-Chrom’s legacy dynamic Poisson approximation.

Observed binned signal is tested against a dynamic local lambda using Poisson tail probabilities, p-values are Benjamini-Hochberg adjusted, and significant adjacent bins are merged into peak intervals. Use call_macs_bdgpeaks_from_signal for the MACS3 bdgpeakcall port.

uchrom.fea.call_peaks_from_signal(signal_table: DataFrame, *, signal_col: str = 'signal', threshold: float | None = None, quantile: float = 0.95, min_width_bins: int = 1, max_gap_bins: int = 0, name: str = 'peak') → DataFrame[source]

Call simple contiguous high-signal peaks from an interval signal table.

The input table should have chrom, start, end, and signal_col. If threshold is omitted it is estimated from the requested quantile of finite signal values.

uchrom.fea.call_peaks_from_track(cdata, track: str, *, method: str = 'auto', control_track: str | None = None, agg: str = 'mean', threshold: float | None = None, quantile: float = 0.95, cutoff: float | None = None, min_length: int = 200, max_gap: int = 30, qvalue: float | None = 0.05, pvalue: float | None = None, genome_size: int | float | None = None, small_local_window_bins: int = 10, large_local_window_bins: int = 100, fragment_size_bins: int = 1, nolambda: bool = False, min_width_bins: int = 1, max_gap_bins: int = 0, signal_scale: float = 1.0, control_scale: float | None = None, negative: str = 'clip', peaks_key: str | None = None, feature_name: str | None = None, prefix: str = 'peak', result_key: str = 'bin_features', project: bool = True, store: bool = True, overwrite: bool = False) → DataFrame[source]

Aggregate a track, call peaks, and project peak features to bins.

uchrom.fea.compute_annotation_features(intervals: DataFrame, gtf: str | Path | DataFrame, *, features: Sequence[str] | None = None, promoter_window: tuple[int, int] = (-2000, 500), gene_feature: str = 'gene', exon_feature: str = 'exon') → DataFrame[source]

Compute GTF-derived features for genomic intervals.

The returned table preserves the input interval order and uses 0-based half-open coordinates.

uchrom.fea.compute_peak_features(intervals: DataFrame, peaks: DataFrame, *, name: str = 'peak') → DataFrame[source]

Compute overlap/count/distance features from peak intervals.

uchrom.fea.compute_sequence_features(intervals: DataFrame, fasta: str | Path, *, features: Sequence[str] | None = None, g4_pattern: str = 'G{3,}[ACGTN]{1,7}G{3,}[ACGTN]{1,7}G{3,}[ACGTN]{1,7}G{3,}', missing: str = 'raise') → DataFrame[source]

Compute FASTA-derived features for genomic intervals.

Parameters:
  • intervals – DataFrame with chrom, start, and end columns.

  • fasta – FASTA file path. Plain text and .gz files are supported.

  • features – Sequence feature names. Defaults to DEFAULT_SEQUENCE_FEATURES.

  • g4_pattern – Regular expression used for G-quadruplex motif counting.

  • missing – "raise" to reject intervals whose chromosomes are absent from the FASTA, or "ignore" to leave their feature values as NaN.

Returns:

Interval columns plus requested feature columns, preserving the input row order.

Return type:

DataFrame

uchrom.fea.contact_frequency(df: DataFrame, threshold: float, chrom=None)[source]

Fraction of traces where a pair of bins are within threshold.

NaN distances (missing spots) are excluded from both numerator and denominator — each bin pair’s frequency is over the set of traces that have both endpoints detected.

Parameters:
  • df (DataFrame with spots + coords.)

  • threshold (distance threshold in the same units as x/y/z.)

  • chrom (optional chromosome filter.)

Returns:

  • frequency (ndarray (n_bins, n_bins) in [0, 1], NaN where no) – trace had both endpoints detected.

  • bin_ids (list of (start, end))

  • n_traces (int)

uchrom.fea.distance_map(cd, chrom, stat: str = 'median', *, streaming=None, batch='auto', memory_budget=None, observed: bool = True) → DistanceMap[source]

Population median / mean 3-D distance of every bin pair of one chromosome — exact, in memory or streaming over the traces.

Same definition as mean_distance_matrix() (per trace, the Euclidean distance of its two spots; NaN / missing spots skipped; a trace observing a bin twice keeps its last spot), but without the dense (n_traces, n_bins, n_bins) tensor, so it runs on genome-wide data:

  • stat="mean" accumulates per-pair sums and counts in trace order (np.add.at) — the additions the dense np.nanmean makes, in the same order, so the result is bitwise identical (float64); memory O(n_bins²).

  • stat="median" is an exact median (the mean of the two middle values for an even count, as np.nanmedian). An exact median needs all values of a pair at once, so the observations are gathered for one row band of the matrix at a time — the rows i whose pairs (i, j > i) fit memory_budget (BYTES_PER_OBS bytes each) — and reduced with a grouped median (pandas). A band costs one pass over the chromosome’s traces; memory is the band plus the n_bins² output, independent of the number of traces. No approximation.

Parameters:
  • cd (ChromData, in memory or backed)

  • chrom (chromosome name)

  • stat ("median" or "mean")

  • streaming (read the traces in batches (``iter_traces(chrom=…,) – columns=”coords”)``) and band the median; default True for a backed object. False gathers every observation at once (the in-memory baseline).

  • batch (traces per batch, or "auto" (from the memory budget))

  • memory_budget (default uchrom.settings.memory_budget)

  • observed (rows / columns = the chromosome's observed bins sorted by) – start (the mean_distance_matrix() convention), or every bin of the chromosome in cd.bins

Return type:

DistanceMap

uchrom.fea.filter_normalize(cube: dict, k_sigma: float = 4.0, frac: float = 0.1) → dict[source]

ArcFISH-style per-trace LOWESS filter + normalise.

Rebuilds the (3, n_traces, n_bins, n_bins) pairwise differences block by block from cube['coords']. Two passes:

  1. Per-pair raw_var = nanmedian(trace_diff²) → LOWESS over log(genomic_distance) → strata_std. Individual trace observations where |diff - median(diff)| > k_sigma × strata_std are dropped.

  2. After filtering, per-pair filtered_var = nanmean((diff - mean)²) and per-pair count = n_valid recomputed. LOWESS again over log(d1d) → expected; normalised variance = filtered / expected.

Output (numpy, on CPU): var, count (refreshed after filter), norm_var, expected, raw_var, genomic_distance. The device tensor under "coords" is dropped from the output.

uchrom.fea.macs_bdgpeaks_to_narrowpeak(peaks: DataFrame, *, name: str = 'MACS', name_prefix: str | None = None, trackline: bool = False, score_column: str = 'score') → str[source]

Render MACS bdgpeakcall-compatible peaks as narrowPeak text.

uchrom.fea.mean_distance_matrix(df: DataFrame, chrom=None, reduce: str = 'median')[source]

Population-level mean/median pairwise distance matrix.

For each pair of genomic bins (i, j), the distance is computed per-trace and then reduced across traces with np.nanmedian (the Bintu 2018 convention) or np.nanmean.

Parameters:
  • df (DataFrame with spots + coords.)

  • chrom (optional chromosome filter.)

  • reduce ('median' (default) or 'mean'.)

Returns:

  • matrix (ndarray (n_bins, n_bins))

  • bin_ids (list of (start, end))

  • n_traces (int)

uchrom.fea.project_interval_features_to_bins(cdata_or_bins, features: DataFrame, *, prefix: str | None = None, value_columns: Sequence[str] | None = None, into: DataFrame | None = None, overwrite: bool = False) → DataFrame[source]

Project interval-level features onto the bin axis.

Parameters:
  • cdata_or_bins – A ChromData (its bins are used) or a bin table with chrom, start, end.

  • features – DataFrame with one row per interval (matched to bins by exact chrom, start, end) and feature columns.

  • prefix – Optional namespace prefix, e.g. "seq" → seq.gc_fraction.

  • value_columns – Feature columns to project (default: all non-interval columns).

  • into – Existing bin-aligned table to extend (usually cd.bin_tracks).

  • overwrite – If False, raise when a projected column already exists in into.

Returns:

n_bins rows indexed by bin_id: the into columns plus the projected ones (NaN for bins without a feature row).

Return type:

DataFrame

uchrom.fea.project_interval_features_to_spots(cdata_or_spots, features: DataFrame, *, prefix: str | None = None, value_columns: Sequence[str] | None = None, into: DataFrame | None = None, overwrite: bool = False) → DataFrame[source]

Project interval-level features onto the spot axis.

Parameters:
  • cdata_or_spots – A ChromData-like object or a spot DataFrame with chrom, start, and end columns.

  • features – DataFrame with one row per interval and feature columns.

  • prefix – Optional namespace prefix for projected columns. For example, prefix="seq" maps gc_fraction to seq.gc_fraction.

  • value_columns – Feature columns to project. By default all non-interval columns are used.

  • into – Optional existing spot-aligned DataFrame to extend.

  • overwrite – If False, raise when projected columns already exist in into.

Returns:

  • DataFrame – A spot-aligned DataFrame containing the existing into columns plus projected feature columns.

  • .. deprecated:: format 2.0 – Per-locus features belong on the bin axis: use project_interval_features_to_bins() (cd.bin_tracks).

uchrom.fea.radius_of_gyration(df: DataFrame, chrom=None) → Series[source]

Per-trace radius of gyration.

Rg = sqrt(mean over spots of ||r - centroid||²). Traces with fewer than 2 spots contribute NaN.

uchrom.fea.read_bedgraph(path: str | Path, *, signal_col: str = 'score') → DataFrame[source]

Read a MACS-compatible bedGraph score track into an interval table.

uchrom.fea.read_gtf(path: str | Path) → DataFrame[source]

Read a GTF/GFF-like file into a normalized annotation table.

Coordinates are converted from 1-based inclusive GTF convention to 0-based half-open intervals.

uchrom.fea.unique_spot_intervals(cdata_or_spots) → DataFrame[source]

Return the unique chrom/start/end intervals of the data.

For a ChromData these are its bins (in bin_id order). For a spot DataFrame the first-occurrence order is preserved.

Distance-based aggregates

Distance-based aggregate statistics over a population of traces.

Input convention: a flat DataFrame with columns chrom, start, end, x, y, z, trace_id (what ChromData.to_dataframe() produces, or what the browser’s ChromatinLayer.df stores).

The core helper _bin_coord_cube() pivots the flat table into a (n_traces, n_bins, 3) array with NaN for missing spots, which lets every aggregate statistic be computed as a straightforward NaN-aware reduction.

class uchrom.fea.distance.DistanceMap[source]

Bases: dict

Result of distance_map() (a dict with attribute access): matrix (n_bins × n_bins), count (traces observing each pair; the diagonal counts the traces observing the bin), bins (bin_id, chrom, start, end of the rows), n_traces, stat, streaming, n_passes (reads of the chromosome’s traces) and n_bands.

uchrom.fea.distance.contact_frequency(df: DataFrame, threshold: float, chrom=None)[source]

Fraction of traces where a pair of bins are within threshold.

NaN distances (missing spots) are excluded from both numerator and denominator — each bin pair’s frequency is over the set of traces that have both endpoints detected.

Parameters:
  • df (DataFrame with spots + coords.)

  • threshold (distance threshold in the same units as x/y/z.)

  • chrom (optional chromosome filter.)

Returns:

  • frequency (ndarray (n_bins, n_bins) in [0, 1], NaN where no) – trace had both endpoints detected.

  • bin_ids (list of (start, end))

  • n_traces (int)

uchrom.fea.distance.distance_map(cd, chrom, stat: str = 'median', *, streaming=None, batch='auto', memory_budget=None, observed: bool = True) → DistanceMap[source]

Population median / mean 3-D distance of every bin pair of one chromosome — exact, in memory or streaming over the traces.

Same definition as mean_distance_matrix() (per trace, the Euclidean distance of its two spots; NaN / missing spots skipped; a trace observing a bin twice keeps its last spot), but without the dense (n_traces, n_bins, n_bins) tensor, so it runs on genome-wide data:

  • stat="mean" accumulates per-pair sums and counts in trace order (np.add.at) — the additions the dense np.nanmean makes, in the same order, so the result is bitwise identical (float64); memory O(n_bins²).

  • stat="median" is an exact median (the mean of the two middle values for an even count, as np.nanmedian). An exact median needs all values of a pair at once, so the observations are gathered for one row band of the matrix at a time — the rows i whose pairs (i, j > i) fit memory_budget (BYTES_PER_OBS bytes each) — and reduced with a grouped median (pandas). A band costs one pass over the chromosome’s traces; memory is the band plus the n_bins² output, independent of the number of traces. No approximation.

Parameters:
  • cd (ChromData, in memory or backed)

  • chrom (chromosome name)

  • stat ("median" or "mean")

  • streaming (read the traces in batches (``iter_traces(chrom=…,) – columns=”coords”)``) and band the median; default True for a backed object. False gathers every observation at once (the in-memory baseline).

  • batch (traces per batch, or "auto" (from the memory budget))

  • memory_budget (default uchrom.settings.memory_budget)

  • observed (rows / columns = the chromosome's observed bins sorted by) – start (the mean_distance_matrix() convention), or every bin of the chromosome in cd.bins

Return type:

DistanceMap

uchrom.fea.distance.mean_distance_matrix(df: DataFrame, chrom=None, reduce: str = 'median')[source]

Population-level mean/median pairwise distance matrix.

For each pair of genomic bins (i, j), the distance is computed per-trace and then reduced across traces with np.nanmedian (the Bintu 2018 convention) or np.nanmean.

Parameters:
  • df (DataFrame with spots + coords.)

  • chrom (optional chromosome filter.)

  • reduce ('median' (default) or 'mean'.)

Returns:

  • matrix (ndarray (n_bins, n_bins))

  • bin_ids (list of (start, end))

  • n_traces (int)

uchrom.fea.distance.radius_of_gyration(df: DataFrame, chrom=None) → Series[source]

Per-trace radius of gyration.

Rg = sqrt(mean over spots of ||r - centroid||²). Traces with fewer than 2 spots contribute NaN.

Axis-wise preprocessing

ArcFISH-style axis-wise preprocessing for chromatin tracing data.

References

Yu H. et al. Accurate and robust 3D genome feature discovery from multiplexed DNA FISH, bioRxiv 2025.11.26.690837v1.

Independent implementation in uchrom — not derived from the GPL-3.0 ArcFISH source.

Pipeline (per chromosome)

  1. axis_variance_cube Builds (3, n_bins, n_bins) per-axis pairwise variance + count cubes from ChromData spots. Each trace contributes a rank-1 outer difference for each axis; aggregation is NaN-aware.

  2. filter_normalize Two-pass LOWESS stratification on log(1D genomic distance):

    • first pass: flag entries whose per-pair squared deviation is more than k_sigma × stratified std as outliers and NaN them;

    • second pass: refit LOWESS on the cleaned variances to give each entry a genome-distance-matched expectation, then normalise.

  3. axis_weight Returns a 3-vector of weights (sum 1) inversely proportional to the per-axis trace-variance median — the exact weighting used by the ACAT combination step in the loop / tad / comp callers.

All tensor-heavy computation runs on a user-selected torch device ('auto' | 'cpu' | 'cuda' | 'mps'). LOWESS stays on CPU via statsmodels because it’s a non-vectorised kernel smoother whose input size is O(n_bins²) (typically ≤ 10 k).

uchrom.fea.arc.axis_variance_cube(cd, chrom: str, device: str = 'auto') → dict[source]

Compute per-axis pairwise variance + sample-count cubes.

Returns a dict with var, count, mean (all (3, B, B)) plus bin_ids, n_traces, chrom, and — for downstream filter_normalize() — the (T, B, 3) coordinate tensor on the torch device under key "coords". Pairwise differences are formed block by block, so memory stays near _BLOCK_BYTES whatever B.

uchrom.fea.arc.axis_weight(cd, chrom: str | None = None, device: str = 'auto') → ndarray[source]

Compute per-axis weights w ∝ 1 / median(trace_variance).

For each axis we centre every trace at its own mean (bins with NaN excluded) and take the median across traces of each trace’s variance. The inverse of that median is the axis weight, normalised to sum 1. Consistent with ArcFISH’s axis_weight routine.

uchrom.fea.arc.filter_normalize(cube: dict, k_sigma: float = 4.0, frac: float = 0.1) → dict[source]

ArcFISH-style per-trace LOWESS filter + normalise.

Rebuilds the (3, n_traces, n_bins, n_bins) pairwise differences block by block from cube['coords']. Two passes:

  1. Per-pair raw_var = nanmedian(trace_diff²) → LOWESS over log(genomic_distance) → strata_std. Individual trace observations where |diff - median(diff)| > k_sigma × strata_std are dropped.

  2. After filtering, per-pair filtered_var = nanmean((diff - mean)²) and per-pair count = n_valid recomputed. LOWESS again over log(d1d) → expected; normalised variance = filtered / expected.

Output (numpy, on CPU): var, count (refreshed after filter), norm_var, expected, raw_var, genomic_distance. The device tensor under "coords" is dropped from the output.

Interval projection

Projection helpers for genomic interval features.

The functions in this module are deliberately independent of uchrom_discovery. They map canonical interval-level feature tables (one row per chrom, start, end) onto the bin axis of a ChromData (cd.bins → cd.bin_tracks), once per locus rather than once per spot. The spot-axis projection of format 1.x is kept as a deprecated helper.

uchrom.fea.project.project_interval_features_to_bins(cdata_or_bins, features: DataFrame, *, prefix: str | None = None, value_columns: Sequence[str] | None = None, into: DataFrame | None = None, overwrite: bool = False) → DataFrame[source]

Project interval-level features onto the bin axis.

Parameters:
  • cdata_or_bins – A ChromData (its bins are used) or a bin table with chrom, start, end.

  • features – DataFrame with one row per interval (matched to bins by exact chrom, start, end) and feature columns.

  • prefix – Optional namespace prefix, e.g. "seq" → seq.gc_fraction.

  • value_columns – Feature columns to project (default: all non-interval columns).

  • into – Existing bin-aligned table to extend (usually cd.bin_tracks).

  • overwrite – If False, raise when a projected column already exists in into.

Returns:

n_bins rows indexed by bin_id: the into columns plus the projected ones (NaN for bins without a feature row).

Return type:

DataFrame

uchrom.fea.project.project_interval_features_to_spots(cdata_or_spots, features: DataFrame, *, prefix: str | None = None, value_columns: Sequence[str] | None = None, into: DataFrame | None = None, overwrite: bool = False) → DataFrame[source]

Project interval-level features onto the spot axis.

Parameters:
  • cdata_or_spots – A ChromData-like object or a spot DataFrame with chrom, start, and end columns.

  • features – DataFrame with one row per interval and feature columns.

  • prefix – Optional namespace prefix for projected columns. For example, prefix="seq" maps gc_fraction to seq.gc_fraction.

  • value_columns – Feature columns to project. By default all non-interval columns are used.

  • into – Optional existing spot-aligned DataFrame to extend.

  • overwrite – If False, raise when projected columns already exist in into.

Returns:

  • DataFrame – A spot-aligned DataFrame containing the existing into columns plus projected feature columns.

  • .. deprecated:: format 2.0 – Per-locus features belong on the bin axis: use project_interval_features_to_bins() (cd.bin_tracks).

uchrom.fea.project.unique_spot_intervals(cdata_or_spots) → DataFrame[source]

Return the unique chrom/start/end intervals of the data.

For a ChromData these are its bins (in bin_id order). For a spot DataFrame the first-occurrence order is preserved.

Sequence features

Sequence-derived genomic interval features.

uchrom.fea.sequence.add_sequence_features(cdata, fasta: str | Path, *, features: Sequence[str] | None = None, prefix: str = 'seq', result_key: str = 'bin_features', project: bool = True, store: bool = True, overwrite: bool = False, hash_source: bool = False, g4_pattern: str = 'G{3,}[ACGTN]{1,7}G{3,}[ACGTN]{1,7}G{3,}[ACGTN]{1,7}G{3,}') → DataFrame[source]

Compute sequence features for a ChromData object.

The canonical interval table is returned. When store=True it is also merged into cdata.results[result_key]. When project=True feature columns are projected onto cdata.bin_tracks using prefix.

uchrom.fea.sequence.compute_sequence_features(intervals: DataFrame, fasta: str | Path, *, features: Sequence[str] | None = None, g4_pattern: str = 'G{3,}[ACGTN]{1,7}G{3,}[ACGTN]{1,7}G{3,}[ACGTN]{1,7}G{3,}', missing: str = 'raise') → DataFrame[source]

Compute FASTA-derived features for genomic intervals.

Parameters:
  • intervals – DataFrame with chrom, start, and end columns.

  • fasta – FASTA file path. Plain text and .gz files are supported.

  • features – Sequence feature names. Defaults to DEFAULT_SEQUENCE_FEATURES.

  • g4_pattern – Regular expression used for G-quadruplex motif counting.

  • missing – "raise" to reject intervals whose chromosomes are absent from the FASTA, or "ignore" to leave their feature values as NaN.

Returns:

Interval columns plus requested feature columns, preserving the input row order.

Return type:

DataFrame

Annotation features

Genome annotation-derived interval features.

uchrom.fea.annotation.add_annotation_features(cdata, gtf: str | Path | DataFrame, *, features: Sequence[str] | None = None, prefix: str = 'gtf', result_key: str = 'bin_features', project: bool = True, store: bool = True, overwrite: bool = False, promoter_window: tuple[int, int] = (-2000, 500), hash_source: bool = False) → DataFrame[source]

Compute annotation features for a ChromData object.

uchrom.fea.annotation.compute_annotation_features(intervals: DataFrame, gtf: str | Path | DataFrame, *, features: Sequence[str] | None = None, promoter_window: tuple[int, int] = (-2000, 500), gene_feature: str = 'gene', exon_feature: str = 'exon') → DataFrame[source]

Compute GTF-derived features for genomic intervals.

The returned table preserves the input interval order and uses 0-based half-open coordinates.

uchrom.fea.annotation.read_gtf(path: str | Path) → DataFrame[source]

Read a GTF/GFF-like file into a normalized annotation table.

Coordinates are converted from 1-based inclusive GTF convention to 0-based half-open intervals.

Peak features

Peak calling and peak-derived interval features.

uchrom.fea.peaks.add_peak_features(cdata, peaks: DataFrame, *, name: str = 'peak', prefix: str = 'peak', result_key: str = 'bin_features', project: bool = True, store: bool = True, overwrite: bool = False, source_peak_key: str | None = None) → DataFrame[source]

Project an existing peak table into interval and spot features.

uchrom.fea.peaks.aggregate_track_by_interval(cdata, track: str, *, agg: str = 'mean', signal_col: str = 'signal') → DataFrame[source]

Aggregate a spot-aligned track to unique genomic intervals.

Multiple spots can share the same genomic bin across cells or traces. Peak callers should operate on the genomic bin signal rather than individual spot rows, so this helper collapses duplicate intervals first.

uchrom.fea.peaks.aggregate_tracks_by_interval(cdata, tracks: Sequence[str], *, agg: str = 'mean') → DataFrame[source]

Aggregate multiple spot-aligned tracks to unique genomic intervals.

uchrom.fea.peaks.call_macs_bdgpeaks_from_bedgraph(path: str | Path, *, signal_col: str = 'score', cutoff: float = 5.0, min_length: int = 200, max_gap: int = 30, name: str = 'MACS', name_prefix: str | None = None) → DataFrame[source]

Read a bedGraph file and call MACS3 bdgpeakcall-compatible peaks.

uchrom.fea.peaks.call_macs_bdgpeaks_from_signal(signal_table: DataFrame, *, signal_col: str = 'score', cutoff: float = 5.0, min_length: int = 200, max_gap: int = 30, name: str = 'MACS', name_prefix: str | None = None) → DataFrame[source]

Call peaks using a Python port of MACS3 bdgpeakcall.

This mirrors MACS3’s bedGraphTrackI.call_peaks behavior for score tracks: regions with values at or above cutoff are merged when the intervening gap is at most max_gap bases, peaks shorter than min_length bases are discarded, and the summit follows MACS3’s tie-breaking rule. The port follows MACS3’s BSD-licensed bdgpeakcall and narrowPeak semantics without vendoring the MACS3 package at runtime.

uchrom.fea.peaks.call_macs_peaks_from_signal(signal_table: DataFrame, *, signal_col: str = 'signal', control_col: str | None = None, qvalue: float | None = 0.05, pvalue: float | None = None, genome_size: int | float | None = None, small_local_window_bins: int = 10, large_local_window_bins: int = 100, fragment_size_bins: int = 1, nolambda: bool = False, min_width_bins: int = 1, max_gap_bins: int = 0, signal_scale: float = 1.0, control_scale: float | None = None, negative: str = 'clip', name: str = 'peak') → DataFrame[source]

Call peaks with U-Chrom’s legacy dynamic Poisson approximation.

Observed binned signal is tested against a dynamic local lambda using Poisson tail probabilities, p-values are Benjamini-Hochberg adjusted, and significant adjacent bins are merged into peak intervals. Use call_macs_bdgpeaks_from_signal for the MACS3 bdgpeakcall port.

uchrom.fea.peaks.call_peaks_from_signal(signal_table: DataFrame, *, signal_col: str = 'signal', threshold: float | None = None, quantile: float = 0.95, min_width_bins: int = 1, max_gap_bins: int = 0, name: str = 'peak') → DataFrame[source]

Call simple contiguous high-signal peaks from an interval signal table.

The input table should have chrom, start, end, and signal_col. If threshold is omitted it is estimated from the requested quantile of finite signal values.

uchrom.fea.peaks.call_peaks_from_track(cdata, track: str, *, method: str = 'auto', control_track: str | None = None, agg: str = 'mean', threshold: float | None = None, quantile: float = 0.95, cutoff: float | None = None, min_length: int = 200, max_gap: int = 30, qvalue: float | None = 0.05, pvalue: float | None = None, genome_size: int | float | None = None, small_local_window_bins: int = 10, large_local_window_bins: int = 100, fragment_size_bins: int = 1, nolambda: bool = False, min_width_bins: int = 1, max_gap_bins: int = 0, signal_scale: float = 1.0, control_scale: float | None = None, negative: str = 'clip', peaks_key: str | None = None, feature_name: str | None = None, prefix: str = 'peak', result_key: str = 'bin_features', project: bool = True, store: bool = True, overwrite: bool = False) → DataFrame[source]

Aggregate a track, call peaks, and project peak features to bins.

uchrom.fea.peaks.compute_peak_features(intervals: DataFrame, peaks: DataFrame, *, name: str = 'peak') → DataFrame[source]

Compute overlap/count/distance features from peak intervals.

uchrom.fea.peaks.macs_bdgpeaks_to_narrowpeak(peaks: DataFrame, *, name: str = 'MACS', name_prefix: str | None = None, trackline: bool = False, score_column: str = 'score') → str[source]

Render MACS bdgpeakcall-compatible peaks as narrowPeak text.

uchrom.fea.peaks.read_bedgraph(path: str | Path, *, signal_col: str = 'score') → DataFrame[source]

Read a MACS-compatible bedGraph score track into an interval table.

Feature registry

Feature registry helpers for ChromData.uns.

uchrom.fea.registry.append_feature_registry_entry(cdata, entry: Mapping[str, Any], *, key: str = 'feature_registry') → dict[str, Any][source]

Append a provenance record to cdata.uns[key].

The registry is stored as a list of JSON-like dicts so it round-trips through the existing uns serializer.

uchrom.fea.registry.file_sha256(path: str | Path, *, chunk_size: int = 1048576) → str[source]

Return the SHA-256 digest for a local source file.

uchrom.fea.registry.table_provenance(table, *, value_columns=None) → dict[str, Any][source]

Small JSON-friendly summary for an enrichment output table.