diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index 5cac669..907168e 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -7,7 +7,7 @@ jobs: - uses: actions/checkout@v4 - uses: chartboost/ruff-action@v1 with: - version: 0.6.8 + version: 0.15.12 # TODO: Is it really necessary for these to be separate jobs? This seems redundant. ruff-format-check: runs-on: ubuntu-latest @@ -15,5 +15,5 @@ jobs: - uses: actions/checkout@v4 - uses: chartboost/ruff-action@v1 with: - version: 0.6.8 + version: 0.15.12 args: 'format --check' diff --git a/.gitignore b/.gitignore index f6236ed..2566728 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,19 @@ dimelo/test/output # Checkpoint files .ipynb_checkpoints/ +*.executed.ipynb +# Local worktrees +.worktrees/ + +# Local analysis artifacts and caches +artifacts/ +cache/ +docs/benchmarks/tmp_bench/ + +# Local benchmark/output files +modkit_test_results.json +pileup_0.2.4.bed.gz +pileup_0.2.4_test.bed +pileup_0.2.4_test.bed.gz +pileup_0.6.1.bed.gz diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5194a15..9d00577 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.8 + rev: v0.15.12 hooks: # Run the linter. - id: ruff diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3e99ede --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "." + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/README.md b/README.md index 5c19de4..4bac90f 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,10 @@ This README document contains installation instructions and documentation for va -[2.4 Load values from processed files](#load-values-from-processed-files) +-[2.5 Clustering scaffolding](#clustering-scaffolding) + +-[2.6 Analysis guides](#analysis-guides) + [3.0 Known Issues](#known-issues) -[3.1 No progress bars](#no-progress-bars) @@ -79,6 +83,32 @@ conda env create -f environment.yml *If you want to handle environment creation yourself, see [the alternative installation instructions](#alternative-installations).* +### Recommended one-command bootstrap (dev + notebook) + +For local development and notebook work, prefer the bootstrap helper. It creates/updates the conda env, installs the package in editable mode with clustering extras, registers a Jupyter kernel, and runs a full environment health check. + +``` +bash scripts/bootstrap_dimelo_env.sh +``` + +Defaults: +- conda env: `dimelo-toolkit` +- kernel name: `dimelo-test` + +Notes: +- The bootstrap uses **flexible** conda channel priority for environment solve stability with `nanoporetech::modkit`. +- Python scientific/user packages are installed via `pip` inside the conda env (`-e .[clustering]`) to avoid channel pin conflicts. + +Optional overrides: + +``` +bash scripts/bootstrap_dimelo_env.sh "" +``` + +Examples: +- Use the project default (`0.6.1`): `bash scripts/bootstrap_dimelo_env.sh` +- Install a different explicit version: `bash scripts/bootstrap_dimelo_env.sh dimelo-toolkit dimelo-test "Python (dimelo-test)" 0.6.1` + ### Install pip dependencies and core dimelo-toolkit package Activate your conda environment, which should now contain python 3.11 and a modkit executable on the path and executable on your system. @@ -93,9 +123,15 @@ Ensure that you are still in the top-level dimelo-toolkit directory. Install the pip install . ``` +To verify the active environment at any time: + +``` +python scripts/ensure_dimelo_kernel.py --expected-env dimelo-toolkit +``` + ## Google Colab Installation -Run the following code in the first cell of your notebook to grab `modkit v0.2.4` from conda and install the `dimelo-toolkit main` branch. This will have to be run whenever you make a new Colab instance, unless you have a better way of managing this, in which case please reach out. The tutorial notebook runs equivalent code blocks to set up your environment, so if you are trying to run the tutorial you can skip to [Basic Use](#basic-use). +Run the following code in the first cell of your notebook to grab `modkit v0.6.1` from conda and install the `dimelo-toolkit main` branch. This will have to be run whenever you make a new Colab instance, unless you have a better way of managing this, in which case please reach out. The tutorial notebook runs equivalent code blocks to set up your environment, so if you are trying to run the tutorial you can skip to [Basic Use](#basic-use). ``` from google.colab import drive @@ -103,18 +139,26 @@ drive.mount('/content/drive') !pip install -q condacolab import condacolab condacolab.install() -!conda install nanoporetech::modkit==0.2.4 +!conda install nanoporetech::modkit==0.6.1 !git clone https://github.com/streetslab/dimelo-toolkit !cd dimelo-toolkit && pip install ipywidgets==7.7.1 . import dimelo ``` +`dimelo-toolkit` defaults to `modkit 0.6.1` for parsing/DMR workflows and notebook reproducibility. + +To switch versions in an existing env: + +``` +bash scripts/update_modkit_version.sh dimelo-toolkit 0.6.1 +``` + ## Alternative Installations -Alternatively, you can install modkit into any conda environment you like. If you want to, you can install modkit some other way, and then add it to the path of your notebook or script. *NOTE: if you are creating the environment yourself, be sure to use python 3.10 or greater. Some dimelo-toolkit features require relatively new python releases.* +Alternatively, you can install modkit into any conda environment you like. If you want to, you can install modkit some other way, and then add it to the path of your notebook or script. Recommended/validated series is `0.6.x`. *NOTE: if you are creating the environment yourself, be sure to use python 3.10 or greater. Some dimelo-toolkit features require relatively new python releases.* ``` -conda install nanoporetech::modkit==0.2.4 +conda install nanoporetech::modkit=0.6 ``` OR ``` @@ -156,6 +200,12 @@ pytest See the [tutorial](tutorial.ipynb) as a starting point. +For deterministic local verification (no external network dependency), run [tutorial_offline.ipynb](tutorial_offline.ipynb) and use: + +``` +python scripts/run_tutorial_offline.py +``` + For local operation on Mac or Linux, you will already have cloned the repo to disk in the installation step. Activate your conda environment, make sure you have jupyter installed, and then launch a jupyter notebook server and navigate to `tutorial.ipynb`. You can also use other tools to open the jupyter notebook or you can simply reference it as an example. ``` @@ -189,13 +239,15 @@ def pileup( output_directory: str | Path = None, regions: str | Path | list[str | Path] = None, motifs: list = ['A,0','CG,0'], - thresh: float = None, + thresh: float | dict[str, float] | None = None, window_size: int = None, cores: int = None, log: bool = False, cleanup: bool = True, + overwrite: bool = True, quiet: bool = False, - override_checks: bool = False,) -> Path, Path: + override_checks: bool = False, + modkit_executable: str | Path | None = None,) -> Path, Path: ``` `parse_bam.extract` creates an hdf5 file with datasets for different aspects of single read data, which can then be passed to plot single reads. @@ -207,15 +259,83 @@ def extract( output_directory: str | Path = None, regions: str | Path | list[str | Path] = None, motifs: list = ['A,0','CG,0','GCH,1'], - thresh: float = None, + thresh: float | dict[str, float] | None = None, window_size: int = None, cores: int = None, log: bool = False, cleanup: bool = True, + overwrite: bool = True, quiet: bool = False, - override_checks: bool = False,) -> Path, Path: + override_checks: bool = False, + modkit_executable: str | Path | None = None,) -> Path, Path: ``` +## Analysis Guides + +Three higher-level analysis guides now sit on top of the existing parsing layer: + +- [docs/global-analysis.md](docs/global-analysis.md) for pileup-backed global summaries and normalization factors +- [docs/region-discovery.md](docs/region-discovery.md) for de novo and paired locus discovery from tiled pileup scans +- [docs/shared-clustering.md](docs/shared-clustering.md) for shared-boundary clustering workflows, with read-window clustering as the default path and region-anchored analysis as an exploratory option +- [docs/region-contrasts.md](docs/region-contrasts.md) for known-region motif-abundance and cluster-occupancy contrasts +- [docs/modkit-dmr.md](docs/modkit-dmr.md) for native `modkit dmr` pair/multi workflows and HMM segmentation +- native `modkit dmr` wrappers in `dimelo.dmr` and `dimelo.workflows` for pairwise beta-binomial DMR testing and optional HMM segmentation (`modkit_dmr_pair_workflow`) plus multi-sample regional DMR (`modkit_dmr_multi_workflow`) + +Use `parse_bam.extract()` when you want read-level clustering or single-read follow-up. The recommended path is to cluster read windows first, then summarize those read-cluster labels at the region level with `SharedClusterResult.region_summaries` and `region_contrasts.score_regions(..., analysis_unit="cluster_occupancy")`. Use `parse_bam.pileup()` when you want defined-region abundance testing, or when you intentionally want optional region-anchored/profile clustering for exploratory shape-based analysis. + +For an end-to-end discovery-to-clustering run, use `workflows.discovery_cluster_workflow()`. It keeps discovery output in BED-style `selected_regions`, then derives the serializable region spec that `shared_cluster_distribution(..., matched_regions=...)` consumes. + +For discovery-to-clustering-to-contrast follow-up, use `workflows.discovery_cluster_contrast_workflow()`. It keeps discovery output in BED-style `selected_regions`, passes the derived serializable region spec into clustering, normalizes clustering-side `region_id` values to the same `chr:start-end,strand` key used by contrasts, scores the same selected loci by default, honors an explicit `contrasts["regions"]` override when provided, and stores the full discovery scan in `metadata["full_scan_windows"]`. + +For production region discovery and DMR calling across samples, prefer the native `modkit dmr` wrappers: +- `workflows.modkit_dmr_pair_workflow(...)` for pairwise site-level DMR with optional HMM segmentation (`segment_path=...`) +- `workflows.modkit_dmr_multi_workflow(...)` or `workflows.modkit_dmr_multi_from_samples_workflow(...)` for all-vs-all multi-sample regional DMR + +`dimelo_test.ipynb` stays single-sample focused. See `dmr_multi_sample.ipynb` for a multi-sample DMR template notebook. + +For external regulatory-context enrichment, use `dimelo.chip_atlas.run_enrichment(...)` directly on BED-like region inputs, or use `workflows.chip_atlas_cluster_enrichment_workflow(...)` to query ChIP-Atlas from read-cluster region associations (`SharedClusterResult.region_summaries`) either per cluster or as a combined region set. + +If your regions are in a different assembly than the requested ChIP-Atlas genome (for example CHM13 regions against `hg38`/`hg19` catalogs), pass `regions_genome=...` and the ChIP-Atlas interface can convert coordinates inline using CrossMap before submission. The helper `dimelo.chip_atlas.cache_chain_files(source_genome="chm13", target_genomes=("hg38","hg19"))` pre-fetches and caches chain files under `cache/chains/` so repeated queries reuse local chain assets. + +The ChIP-Atlas API currently requires class/threshold fields to be populated. `dimelo.chip_atlas` now defaults to `antigen_class="TFs and others"`, `cell_type_class="No description"`, and `threshold="100"`; override them when you want narrower biological context. + +For mixed-provider regulatory follow-up (e.g. SCREEN + UniBind), use `workflows.resolve_regulatory_enrichment_spec(...)` to normalize species/provider configuration before running provider-specific queries. The spec defaults to human (`homo_sapiens`) and `target_genome="hg38"`, supports explicit `reference_genome` / `target_genome` / CrossMap chain settings for arbitrary assemblies, validates species against the UniBind-supported species set, and automatically filters out `screen` requests when species is not human or mouse. + +For dataset-level ChIP-Atlas BED retrieval (antigen-first), use `chip_atlas.search_peak_datasets(...)` / `workflows.chip_atlas_search_peak_datasets_workflow(...)` with required `antigen` plus optional `genome`, `cell_type`, `antigen_class`, and `cell_type_class` filters. Then pass the returned table to `chip_atlas.download_peak_datasets(...)` / `workflows.chip_atlas_download_peak_datasets_workflow(...)` to generate complete sorted BEDs, top/bottom subsets (default top/bottom 3,000), optional quantile splits (`quartiles`, `quintiles`, `deciles`, or integer bin count), and optional CrossMap-converted BED6 outputs to a target assembly. + +For UniBind track inputs, `workflows.resolve_unibind_track_paths(...)` supports either explicit local track paths (`track_paths=[...]`) or Track Hub discovery/download to cache (`trackhub_url` or named `collection="robust"|"permissive"`), with optional `search_terms`, `max_tracks`, and `convert_bigbed_to_bed=True` (requires `bigBedToBed` on PATH). + +For UniBind web-tool workflows (TFBS extraction and enrichment), use `dimelo.regulatory_enrichment.run_unibind_tfbs_extraction(...)` and `run_unibind_enrichment(...)` or the workflow wrappers `workflows.unibind_tfbs_extraction_workflow(...)` / `workflows.unibind_enrichment_workflow(...)`. These functions submit CSRF-protected UniBind forms programmatically, poll job status (`Queued`/`Running`/`Completed`), expose downloadable result URLs, and can optionally cache downloads under `cache/unibind_jobs/`. + +`region_discovery` also exposes renderer-neutral plotting prep helpers that consume `RegionDiscoveryResult`: `prepare_region_discovery_scan_data(...)` prepares per-contig scan and hit tables by default, and `prepare_region_discovery_hit_context_data(...)` prepares local hit-context tables for small-multiple follow-up views. + +`global_analysis` now exposes renderer-neutral plotting prep helpers that consume `GlobalAnalysisResult`: `prepare_global_analysis_summary_data(...)` prepares sample-level summaries, optional condition-level views, and normalization tables, while `prepare_global_analysis_window_data(...)` prepares broad-window payloads that stay per-contig by default. + +`region_contrasts` uses the same data-prep-first plotting layer: `prepare_region_contrast_profile_data(...)` and `prepare_region_contrast_heatmap_data(...)` consume a `RegionContrastResult` plus an explicit positional table, and shared clustering now exposes the same style of renderer-neutral prep helpers on top of its canonical result tables. + +For users who want built-in figures instead of only payload tables, `dimelo.plotting_matplotlib` provides an optional Matplotlib renderer layer on top of those prepared payloads, plus `save_figure(...)` for PNG/PDF export. + +`shared_clustering` also supports built-in Matplotlib rendering through +`dimelo.plotting_matplotlib`, including cluster-distribution, profile, and +region-occupancy views on top of the prepared clustering payloads. + +`region_contrasts` also now supports `analysis_unit="single_read"` for +extract-backed defined-region comparison. Use `representation="read_mod_fraction"` +for per-read motif fractions or `representation="read_window_features"` for +feature-vector summaries, with sample-aware summaries rather than pooled-read +inference. + +Shared clustering also has renderer-neutral plotting prep in `dimelo.plotting`. Use `prepare_shared_cluster_distribution_data(...)` for sample/condition cluster fractions, `prepare_shared_cluster_profile_data(...)` for cluster feature summaries, and `prepare_shared_cluster_region_data(...)` for region-level occupancy tables. The older lightweight `result.plot_data["cluster_distribution_bar"]` and `result.plot_data["cluster_distribution_heatmap"]` payloads remain supported. + +Global shared-cluster composition inference is available through +`dimelo.shared_cluster_tests.shared_cluster_tests(...)`, which consumes a +`SharedClusterResult` plus a `ContrastSpec`. Use this for global composition +shifts (`pairwise`, `matched_pairwise`, `group_vs_group`, `time_course`), and +keep per-region occupancy inference in +`region_contrasts.score_regions(..., analysis_unit="cluster_occupancy")`. +Pooled `chi_squared` and `g_test` modes are screening-oriented and should not +replace replicate-aware permutation analyses when replicates are available. + For human-readable pileups (bedmethyl files, .bed) and extracted reads (.txt tab-separated values), run with `cleanup=False`. `cleanup=True` will clear these outputs because they can take up a lot of space. ### Parsing outputs @@ -225,7 +345,7 @@ There should not be such issues for command line operation. See below an example ``` (dimelo-toolkit) % python dimelo_cmd.py -modkit found with expected version 0.2.4 +modkit detected and command capabilities loaded No specified number of cores requested. 8 available on machine, allocating all. Modification threshold of 0.9 will be treated as coming from range 0-1. ████████████████████| Preprocessing complete for motifs ['A,0'] in chm13.draft_v1.1.fasta: 100% | 00:30 @@ -237,6 +357,18 @@ You should see no outputs at all if `quiet=True`. ## Plotting +Legacy plotting entry points remain supported, but they now share a common plotting-axis layer under the hood. The compatibility rules are: + +- `regions_5to3prime=True` maps to a shared 5'->3' orientation mode for region-aligned plots. +- `relative=True` continues to mean fixed-window plotting around a common anchor or region center. +- Aggregate profile-style plots may opt into normalized segment layouts later, but that behavior is additive rather than a changed default. +- Single-read plots remain coordinate-preserving. They can use fixed-window coordinates and 5'->3' orientation, but they do not stretch variable-length regions onto a synthetic continuous axis. +- Built-in Matplotlib plotting is still available, while plot-ready tables remain the stable contract for users who prefer seaborn, Plotly, Altair, or custom renderers. + +For legacy API compatibility, `by_regions(...)` helpers now also accept `regions=` as an alias to `regions_list=` across enrichment/depth/profile plotting modules. + +Newer region-contrast plotting helpers accept a `RegionContrastResult` plus an explicit `position_table` from the parsing/loading layer. This keeps region scoring and positional extraction separate while still using the shared plotting-axis system. The current helper contract expects `region_id`, `position`, `anchor`, `value`, `region_strand`, and either `condition` or `sample_id` in that positional table. + `plot_enrichment_profile` module for pileup line plot profiles across one or more region ``` def plot_enrichment_profile( @@ -757,6 +889,8 @@ def read_vectors_from_hdf5( cores: int | None = None, # currently unused subset_parameters: dict | None = None, span_full_window: bool = False, + random_sample_n_reads: int | None = None, + min_read_length_bp: int | None = None, ) -> tuple[list[tuple], list[str], dict | None]: """ User-facing function. @@ -806,6 +940,8 @@ def read_vectors_from_hdf5( reads to be returned. If not None, at least one of n or frac must be provided. The array parameter should not be provided here. span_full_window: If True, only load reads that fully span the window defined by region_start-region_end + random_sample_n_reads: Optional global random sample size by unique read name. + min_read_length_bp: Optional minimum read length filter (bp) applied before loading vectors. Returns: a list of tuples, each tuple containing all datasets corresponding to an individual read that @@ -850,6 +986,123 @@ def regions_to_list( """ ``` +## Clustering scaffolding + +`dimelo.cluster` wraps the loading utilities above so you can quickly build matrices for read-window clustering, dimensionality reduction, or optional region-anchored exploratory analysis. + +For the higher-level shared-boundary workflows added on top of these loaders, see [docs/shared-clustering.md](docs/shared-clustering.md). The short version is: + +- recommended: run `parse_bam.extract()` and cluster read windows first, then feed the resulting read-cluster labels into region-level occupancy follow-up +- optional: run `parse_bam.pileup()` and `mode="region_anchored"` when you want region-aligned exploratory clustering on shape-like summaries +- run both when you want region-level summaries plus read-level follow-up in the same analysis + +```python +from dimelo import workflows +from dimelo.models import SampleSpec + +result = workflows.shared_cluster_distribution( + samples=[ + SampleSpec(sample_id="s1", condition="NS", extract_h5="output/s1.extract.h5"), + SampleSpec(sample_id="s2", condition="15min", extract_h5="output/s2.extract.h5"), + ], + mode="read_global", + motifs=["A,0"], + n_clusters=8, +) +``` + +```python +from dimelo import cluster + +# Recommended read-window clustering pipeline using extract outputs. +read_windows = cluster.extract_read_windows( + hdf5_file="output/extract.h5", + motifs=["A,0", "CG,0"], + regions="regions.bed", + config=cluster.ReadWindowExtractionConfig(window_bp=2000, orientation_aware=True), +) +feature_matrix, feature_names = cluster.read_window_feature_matrix(read_windows) +read_clusters = cluster.cluster_read_windows(feature_matrix, method="kmeans", n_clusters=8) + +# Use the resulting read-cluster labels as the bridge into region-level occupancy or contrasts. +# For example, pass SharedClusterResult.region_summaries into region_contrasts.score_regions(..., analysis_unit="cluster_occupancy"). + +# Optional region-anchored exploratory analysis from pileup-derived region vectors. +pileup_matrix, region_info = cluster.region_feature_matrix_from_pileup( + bedmethyl_file="output/pileup.sorted.bed.gz", + motif="A,0", + regions="regions.bed", + window_size=500, + regions_5to3prime=True, +) +labels, model = cluster.cluster_features(pileup_matrix, n_clusters=4) + +# Optional visualization/export +cluster.plot_region_cluster_profiles(pileup_matrix, labels, window_bp=1000) +cluster.export_region_clusters_to_bed(region_info, labels, "region_clusters.bed") +cluster.plot_cluster_karyotype("region_clusters.bed", "ref.fasta.fai") +# If pileup_matrix concatenates multiple motifs, pass motif_index to select which slice to visualize. + +# Strict two-site fixed-offset raster: window 1 is centered at 0 and window 2 +# is centered at the requested distance along the same reads. +# fig, stats = cluster.plot_two_site_read_raster( +# read_windows, +# second_site_offset_bp=2000, +# window_width_bp=2000, +# motif_count=2, +# plot_all_motifs=True, +# max_rows=500, +# downsample_method="auto", +# downsample_seed=42, +# ) + +# Co-occurring multi-site raster: pick one site-set per read from all candidate +# regions on the read. Each panel can use its own local coordinate system. +# fig, stats = cluster.plot_multisite_read_raster( +# read_windows, +# site_selection={ +# "mode": "cooccurring", +# "n_windows": 2, +# "min_distance_bp": "window_width", +# "max_distance_bp": None, +# "choose": "first", # or "random", "longest_span", "shortest_span" +# "selection_seed": 42, +# "strand_relation": "any", # or "same", "opposite" +# "orientation": "genomic", # or "anchor_strand" +# }, +# coordinate_mode="local_window", +# window_widths_bp=[2000, 2000], +# motif_count=2, +# plot_all_motifs=True, +# ml_score_thresholds=[0.65, 0.50], +# downsample_method="random", +# downsample_seed=42, +# ) + +# Binary classification of read features across two samples +clf_result = cluster.classify_read_features_binary( + feature_matrix, + sample_labels=my_sample_labels, # e.g., ["sampleA", "sampleA", "sampleB", ...] + classifier="xgboost", # or logreg/sgd/random_forest/svc/knn + random_state=42, +) +print(clf_result["metrics"]) +# QC plots +cluster.plot_confusion_matrices(clf_result["predictions"]) +# If you have per-read windows aligned to predictions for this split: +# cluster.plot_classification_profiles(read_windows.data_matrix, clf_result["predictions"], split="test") + +# Optional: sample rows for faster exploratory runs +sampled_features, sampled_labels, idx = cluster.sample_rows(feature_matrix, labels, frac=0.2, stratify=True) +cluster.cluster_read_windows(sampled_features, method="kmeans", n_clusters=4) +``` + +`region_feature_matrix_from_pileup` delegates to `load_processed.regions_to_list`, so you inherit the same controls for multi-core execution and windowing. Treat it as an optional exploratory path for region-anchored/profile clustering rather than the default workflow. For read-level work you can either keep using `read_mod_fraction_table` (motif-wide fractions) or run the read-window pipeline above: `extract_read_windows` builds a matrix of per-base modification calls (optionally enforcing strand orientation and dropping reads spanning multiple regions), `read_window_feature_matrix` augments that matrix with PCA, autocorrelation, density, and summary statistics, and `cluster_read_windows` exposes the same clustering options demonstrated in the exploratory notebook (k-means, GMM, spectral, OPTICS/HDBSCAN, etc.). + +`cluster_features` currently offers a lightweight k-means wrapper powered by `scikit-learn`, while `cluster_read_windows` can switch between `kmeans`, `minibatch_kmeans`, `gmm`, `agglomerative`, `spectral`, `birch`, `dbscan`, `optics`, `hdbscan`, or `umap_kmeans`. These dependencies (`scikit-learn`, `scipy`, `hdbscan`, `umap-learn`) live in the `clustering` extra (`pip install dimelo[clustering]`), or feed the matrices above into your own estimator. + +**Motif semantics:** `extract.h5` stores one row per motif per read. When passing multiple motifs, you will see multiple rows per read; use `build_multimotif_read_windows` to group and concatenate motifs per read (set `require_all_motifs=True` to drop reads missing motifs), or filter by `metadata["motif"]` for per-motif analysis. Visualization helpers take a `motif_index` argument to choose which motif slice to display when windows are concatenated. + ## Parameters and what they mean Many of the parsing, loading, and plotting functions share parameters. The common ones and their meanings/defaults are listed below. @@ -868,17 +1121,21 @@ Many of the parsing, loading, and plotting functions share parameters. The commo `regions` and `regions_list` are used for specifying subsets of the genome to parse, load, or plot. A `region` is defined as a range of genomic coordinates, and `regions` can refer to any number of `region` specifications. Thus for the `regions` parameter one can pass a single region specified as a string, `chrX:XXX-XXX`, many regions defined in a .bed tab-separated-value file with each line containing at miniimum chromosome, start, and end coordinates (plus optionally a strand, + or -), or a list of strings specifiers or bed files. The entire list will be rolled into a single `regions` set to be passed down for subsequent processing. In the case of regions-wise comparisons in plotting functions, `regions_list` is a *list of regions objects*, where each element of the list is a string, Path, or list of strings or Paths. -`regions_5to3prime` is used by loader/plotter functions that align data from many loci. It ensures that all loaded data is oriented in the right way with respect to functional elements by flipping region data to all orient 5' to 3' based on the strand specified in the region str or bed file line. +`regions_5to3prime` is used by loader/plotter functions that align data from many loci. It ensures that all loaded data is oriented in the right way with respect to functional elements by flipping region data to all orient 5' to 3' based on the strand specified in the region str or bed file line. In the newer shared plotting layer, this is the compatibility wrapper for the canonical `orientation="region_5to3"` axis mode. `single_strand` limits loaded/plotted data to only the strand specified in the region str or bed file line. Any modification on a given nucleotide has a different set of possible locations on the forward vs reverse strand which can be relevant for many biological phenomena. `motif` and `motifs` are used to specify what base modifications you are interested in and what their sequence context is for parse, load, and plot functions. A single `motif` is a string containing several canonical bases (using the [IUPAC nucleic acid notation](https://en.wikipedia.org/wiki/Nucleic_acid_notation), e.g. **H** refers to "not a G"), followed by a comma, and then an integer specifying which coordinate in the string is your modified base. For example, 6mA is denoted "A,0" and CpG is denoted "CG,0" whereas GpC *excluding CpGs* is denoted "GCH,1". `motifs` is a list of such strings for functions that can work on multiple base modifications at once. -`thresh` for parsing and some loading/plotting functions refers to a base modification probability threshold, used to transform the the output of most basecalling pipelines into a binary call for any given read position. For parsing pileup calls, this defaults to `None` which allows `modkit` to pick its own threshold based on the data. For other calls, the parameter is mandatory. The normal use is specifying between 0 and 1, but 1-255 is also supported to make the inputs more backwards compatible with old `dimelo` package versions and with examination of the raw .bam file contents. A value between and 255 will simply be converted into a 0-1 probability before being handed down to subsequent processing. +`thresh` for parsing and some loading/plotting functions refers to a base modification probability threshold, used to transform the output of most basecalling pipelines into a binary call for any given read position. For parse functions, it accepts `None`, a single numeric threshold, or a per-motif dictionary (e.g. `{"A,0": 0.70, "CG,0": 0.80, "default": 0.75}`). `None` keeps adaptive/inferred thresholding behavior where applicable. Numeric values can be in [0,1] or [1,255] (the latter is converted to probability scale for backward compatibility). + +`modkit_executable` on `parse_bam.pileup()` / `parse_bam.extract()` allows pinning parsing to a specific modkit binary path/name (for example a side-by-side `modkit-0.2.4` and `modkit-0.6.1` install). You can also set `DIMELO_MODKIT_EXECUTABLE` globally. + +`window_size` for parsing and most loading and plotting functions is a *modification to your regions* that will redefine them to be all the same size, i.e. `2 x window_size`, centered around the centers of your original regions. This is important for parsing/loading/plotting operations that aggregate many loci at once, but should be left blank if you do not want your regions modified. The default is `None` for functions where the parameter is optional. -`window_size` for parsing and most loading and plotting functions is a *modification to your regions* that will redefine them to be all the same size, i.e. 2 x window_size, centered around the centers of your original regions. This is important for the parsing and plotting applications that show many genomic regions at once, but should be left blank if you don't want your regions modified. The default is `None` for functions where the parameter is optional. +For **clustering read-window extraction APIs** (`cluster.extract_read_windows`, `cluster.build_multimotif_read_windows`, and `cluster.ReadWindowExtractionConfig`) the preferred parameter is `window_bp`, which is the **full extracted span in bp**. `window_size` is still accepted there only as a backward-compatible alias. -`relative` is a boolean input that specifies whether loading and plotting operations adjust coordinates to be relative to some center point or simple plot in absolute genomic coordinates. This is not currently implemented for some methods, but exists as an interface to allow future implementation. +`relative` is a boolean input that specifies whether loading and plotting operations adjust coordinates to be relative to some center point or simple plot in absolute genomic coordinates. In the newer shared plotting layer, `relative=True` maps to fixed-window plotting around a common anchor. Aggregate plots may grow into richer segment-based normalization later, but single-read plotting still preserves coordinate geometry rather than stretching variable-length features. `sort_by` for plot_reads will sort reads by any of `region_start`, `region_end`, `read_name`, `read_start`, `read_end`, `chromosome`, `strand`, and `MOTIF_mod_fraction` for any extracted motif. New sorting options are planned in the future. The default is `shuffle`, which will put the reads in a random order. `sort_by` can be passed as one string or as a list of strings. If a list is passed, the reads will be sorted hierarchically i.e. first by the first list element, then the second, and so on. The exception is that if any of the list elements are `shuffle`, the reads will *first* be shuffled and then sorted by the rest of the elements in order of priority. @@ -892,6 +1149,8 @@ Many of the parsing, loading, and plotting functions share parameters. The commo `cleanup` will keep the (often large) human-readable outputs that are inefficient for plotting and vector extraction but may be helpful for other use cases. +`overwrite` controls whether parse outputs for the target `output_name` are regenerated. When `True` (default), existing outputs are replaced. When `False`, complete existing outputs are reused; if partial/conflicting artifacts exist, parsing raises `FileExistsError`. + `quiet` suppressed progress bars and other outputs. `override_checks` lets you run modkit even if the bam format checking and reference alignment checking are anomalous. @@ -902,7 +1161,12 @@ Many of the parsing, loading, and plotting functions share parameters. The commo ## No progress bars The most common culprit for progress bar issues in notebooks (Jupyter or Colab) is an incompatibility between your notebooks interfaces and your `ipywidgets` version. The latest jupyter notebooks or jupyter lab install and the latest ipywidgets should work together, but on Google Colab, VS Code, Open On Demand, and other jupyter interfaces this may not be the case. [setup.py](setup.py) contains details on which versions you can try downgrading to for different platforms. The following code run in your activated conda environment will downgrade `ipywidgets` to your specified version. **Our Colab instructions in the [Colab Installation](#google-colab-installation) section and the [tutorial](tutorial.ipynb) already handle this for you.** +Live parse/extract progress bars now auto-disable in notebook/non-TTY contexts to avoid mangled carriage-return output. You can force behavior with: +- `DIMELO_PROGRESS_MODE=off` to disable live bars everywhere +- `DIMELO_PROGRESS_MODE=on` to force live bars + +In notebooks, you can also pass `quiet=True` to `parse_bam.pileup(...)` and `parse_bam.extract(...)`. + ``` pip install ipywidgets==X.XX.X ``` - diff --git a/dimelo/__init__.py b/dimelo/__init__.py index 4906a85..f732dac 100644 --- a/dimelo/__init__.py +++ b/dimelo/__init__.py @@ -1,6 +1,12 @@ from . import ( + chip_atlas, + cluster, + distribution, + dmr, export, + global_analysis, load_processed, + models, parse_bam, plot_depth_histogram, plot_depth_profile, @@ -8,16 +14,54 @@ plot_enrichment_profile, plot_read_browser, plot_reads, + plotting, + plotting_matplotlib, + region_analysis, + region_contrasts, + region_discovery, + regulatory_enrichment, + shared_cluster_tests, + workflows, +) +from .models import ( + ChipAtlasEnrichmentResult, + ModkitDMRMultiResult, + ModkitDMRPairResult, + RegionDiscoveryClusterContrastResult, + RegionDiscoveryClusterResult, + SharedClusterContrastResult, + UniBindJobResult, ) __all__ = [ + "chip_atlas", + "cluster", + "dmr", + "distribution", "export", + "global_analysis", "load_processed", + "models", "parse_bam", + "plotting", + "plotting_matplotlib", "plot_depth_histogram", "plot_depth_profile", "plot_enrichment", "plot_enrichment_profile", "plot_read_browser", "plot_reads", + "regulatory_enrichment", + "region_discovery", + "region_analysis", + "region_contrasts", + "shared_cluster_tests", + "ChipAtlasEnrichmentResult", + "ModkitDMRMultiResult", + "ModkitDMRPairResult", + "RegionDiscoveryClusterContrastResult", + "RegionDiscoveryClusterResult", + "SharedClusterContrastResult", + "UniBindJobResult", + "workflows", ] diff --git a/dimelo/artifacts.py b/dimelo/artifacts.py new file mode 100644 index 0000000..5c99fb5 --- /dev/null +++ b/dimelo/artifacts.py @@ -0,0 +1,168 @@ +from __future__ import annotations + +import hashlib +import json +from collections.abc import Iterable + +from dimelo.models import DatasetArtifact + +COMPATIBILITY_PROVENANCE_KEYS = frozenset( + { + "normalization_mode", + "feature_scaling", + "cluster_basis", + "motifs", + "window_size", + "region_source", + "region_digest", + "pipeline", + } +) + + +def _params_hash(params: dict[str, object]) -> str: + payload = json.dumps(params, sort_keys=True, separators=(",", ":")).encode() + return hashlib.sha256(payload).hexdigest() + + +def _normalize_sequence(values: object) -> tuple[object, ...]: + if values is None: + return None + if isinstance(values, str): + return (values,) + return tuple(values) + + +def _normalize_source_files(values: object) -> tuple[str, ...] | None: + normalized = _normalize_sequence(values) + if normalized is None: + return None + return tuple(sorted(str(value) for value in normalized)) + + +def _normalize_source_fingerprints( + values: object, +) -> tuple[dict[str, object], ...] | None: + if values is None: + return None + normalized = [dict(value) for value in values] + normalized.sort( + key=lambda value: json.dumps(value, sort_keys=True, separators=(",", ":")) + ) + return tuple(normalized) + + +def _mapping_subset_matches( + requested: dict[str, object], + candidate: dict[str, object], +) -> bool: + return all(item in candidate.items() for item in requested.items()) + + +def _requested_params_hash_matches( + requested: dict[str, object], + candidate: dict[str, object], +) -> bool: + if not _mapping_subset_matches(requested, candidate): + return False + candidate_subset = {key: candidate[key] for key in requested} + return _params_hash(requested) == _params_hash(candidate_subset) + + +def _compatibility_provenance(requested: dict[str, object]) -> dict[str, object]: + return { + key: value + for key, value in requested.items() + if key in COMPATIBILITY_PROVENANCE_KEYS + } + + +def artifact_fingerprint(artifact: DatasetArtifact) -> dict[str, object]: + return { + "schema_version": artifact.metadata.get("schema_version"), + "package_version": artifact.metadata.get("package_version"), + "source_files": _normalize_source_files( + artifact.provenance.get( + "source_files", artifact.metadata.get("source_files") + ) + ), + "source_fingerprints": _normalize_source_fingerprints( + artifact.provenance.get( + "source_fingerprints", artifact.metadata.get("source_fingerprints") + ) + ), + "upstream_lineage": _normalize_sequence( + artifact.provenance.get( + "upstream_lineage", artifact.metadata.get("upstream_lineage") + ) + ), + "params_hash": _params_hash(artifact.params), + } + + +def _has_required_fingerprint_fields(fingerprint: dict[str, object]) -> bool: + required_fields = ( + "schema_version", + "package_version", + "source_files", + "source_fingerprints", + "upstream_lineage", + ) + if any(fingerprint[field] is None for field in required_fields): + return False + return bool(fingerprint["source_files"]) and bool( + fingerprint["source_fingerprints"] + ) + + +def artifact_is_compatible( + requested: DatasetArtifact, + candidate: DatasetArtifact, +) -> bool: + requested_fingerprint = artifact_fingerprint(requested) + candidate_fingerprint = artifact_fingerprint(candidate) + if not _has_required_fingerprint_fields(requested_fingerprint): + return False + if not _has_required_fingerprint_fields(candidate_fingerprint): + return False + if requested.sample_id != candidate.sample_id: + return False + if requested.artifact_type != candidate.artifact_type: + return False + if any( + requested_fingerprint[field] != candidate_fingerprint[field] + for field in ( + "schema_version", + "package_version", + "source_files", + "source_fingerprints", + "upstream_lineage", + ) + ): + return False + if not _requested_params_hash_matches(requested.params, candidate.params): + return False + return _mapping_subset_matches( + _compatibility_provenance(requested.provenance), + candidate.provenance, + ) + + +def resolve_artifact( + requested: DatasetArtifact, + candidates: Iterable[DatasetArtifact], + artifact_policy: str = "prefer_cached", +) -> DatasetArtifact | None: + if artifact_policy == "rebuild": + return None + + if artifact_policy not in {"prefer_cached", "require_cached"}: + raise ValueError(f"Unknown artifact_policy: {artifact_policy}") + + for candidate in candidates: + if artifact_is_compatible(requested, candidate): + return candidate + + if artifact_policy == "prefer_cached": + return None + raise FileNotFoundError("No compatible cached artifact found for require_cached") diff --git a/dimelo/chip_atlas.py b/dimelo/chip_atlas.py new file mode 100644 index 0000000..22f309f --- /dev/null +++ b/dimelo/chip_atlas.py @@ -0,0 +1,1327 @@ +from __future__ import annotations + +import csv +import io +import json +import re +import shutil +import subprocess +import tempfile +import time +import zipfile +from collections.abc import Iterable, Mapping +from pathlib import Path +from typing import Any +from urllib import error, parse, request + +import numpy as np +import pandas as pd + +from .models import ChipAtlasEnrichmentResult + +DEFAULT_SUBMIT_URL = "https://dtn1.ddbj.nig.ac.jp/wabi/chipatlas/" +DEFAULT_STATUS_URL = DEFAULT_SUBMIT_URL +DEFAULT_RESULT_URL = DEFAULT_SUBMIT_URL +DEFAULT_CHAIN_CACHE_DIR = Path("cache/chains") +DEFAULT_METADATA_CACHE_DIR = Path("cache/chip_atlas") +DEFAULT_EXPERIMENT_LIST_URL = "https://dbarchive.biosciencedbc.jp/data/chip-atlas/LATEST/chip_atlas_experiment_list.zip" + +_SUCCESS_STATUSES = {"finished"} +_TERMINAL_STATUSES = {"finished", "error", "cancelled", "unknown"} +_STATUS_ALIASES = { + "finished": "finished", + "complete": "finished", + "completed": "finished", + "done": "finished", + "success": "finished", + "ok": "finished", + "error": "error", + "failed": "error", + "failure": "error", + "cancelled": "cancelled", + "canceled": "cancelled", + "running": "running", + "preparing": "running", + "queued": "queued", + "pending": "queued", + "submitted": "queued", +} + +_GENOME_ALIASES = { + "chm13": "hs1", + "t2t-chm13v2.0": "hs1", + "t2t_chm13v2.0": "hs1", + "t2t_chm13v2": "hs1", + "hs1": "hs1", + "hg38": "hg38", + "grch38": "hg38", + "hg19": "hg19", + "grch37": "hg19", + "mm10": "mm10", + "mm39": "mm39", +} + +_CHAIN_URL_OVERRIDES = { + ( + "hs1", + "hg38", + ): "https://hgdownload.soe.ucsc.edu/goldenPath/hs1/liftOver/hs1ToHg38.over.chain.gz", + ( + "hs1", + "hg19", + ): "https://hgdownload.soe.ucsc.edu/goldenPath/hs1/liftOver/hs1ToHg19.over.chain.gz", +} + +_BED_THRESHOLD_COLUMN_MAP = { + "05": "Peak-call (BED) (q < 1E-05)", + "5": "Peak-call (BED) (q < 1E-05)", + "q05": "Peak-call (BED) (q < 1E-05)", + "1e-05": "Peak-call (BED) (q < 1E-05)", + "10": "Peak-call (BED) (q < 1E-10)", + "q10": "Peak-call (BED) (q < 1E-10)", + "1e-10": "Peak-call (BED) (q < 1E-10)", + "20": "Peak-call (BED) (q < 1E-20)", + "q20": "Peak-call (BED) (q < 1E-20)", + "1e-20": "Peak-call (BED) (q < 1E-20)", +} + +_STRATIFY_TO_BIN_COUNT = { + "quartile": 4, + "quartiles": 4, + "quintile": 5, + "quintiles": 5, + "decile": 10, + "deciles": 10, +} + + +def _http_request( + *, + url: str, + method: str = "GET", + data: bytes | None = None, + timeout_seconds: float = 60.0, + headers: Mapping[str, str] | None = None, +) -> tuple[int, str, str]: + request_headers = {"Accept": "application/json,text/plain,text/html,*/*"} + if headers: + request_headers.update(dict(headers)) + if method.upper() == "POST": + request_headers.setdefault("Content-Type", "application/x-www-form-urlencoded") + + req = request.Request( + url=url, + data=data, + method=method.upper(), + headers=request_headers, + ) + try: + with request.urlopen(req, timeout=timeout_seconds) as response: + status_code = int(response.getcode()) + content_type = response.headers.get("Content-Type", "") + body = response.read().decode("utf-8", errors="replace") + return status_code, content_type, body + except error.HTTPError as exc: + detail = exc.read().decode("utf-8", errors="replace") + hint = "" + lowered = detail.lower() + if exc.code == 404 and ( + "chip-atlas: 404" in lowered or "" in lowered + ): + hint = ( + " The endpoint may be outdated. The current API base URL is " + "'https://dtn1.ddbj.nig.ac.jp/wabi/chipatlas/'." + ) + raise RuntimeError( + f"ChIP-Atlas API request failed with HTTP {exc.code}: {detail[:400]}{hint}" + ) from exc + except error.URLError as exc: + raise RuntimeError(f"ChIP-Atlas API request failed: {exc.reason}") from exc + + +def _normalize_genome_name(genome: str) -> str: + token = str(genome).strip() + if not token: + raise ValueError("Genome name cannot be empty.") + lowered = token.lower() + return _GENOME_ALIASES.get(lowered, lowered) + + +def _chain_url_for_pair(source_genome: str, target_genome: str) -> str: + source = _normalize_genome_name(source_genome) + target = _normalize_genome_name(target_genome) + override = _CHAIN_URL_OVERRIDES.get((source, target)) + if override: + return override + target_token = target[0].upper() + target[1:] if target else target + return ( + f"https://hgdownload.soe.ucsc.edu/goldenPath/{source}/liftOver/" + f"{source}To{target_token}.over.chain.gz" + ) + + +def _resolve_chain_file( + *, + source_genome: str, + target_genome: str, + chain_file: str | Path | None = None, + chain_url: str | None = None, + chain_cache_dir: str | Path | None = None, + timeout_seconds: float = 120.0, +) -> Path: + if chain_file is not None: + resolved = Path(chain_file).expanduser().resolve() + if not resolved.exists(): + raise FileNotFoundError(f"CrossMap chain file not found: {resolved}") + return resolved + + source = _normalize_genome_name(source_genome) + target = _normalize_genome_name(target_genome) + cache_root = ( + Path(chain_cache_dir) + if chain_cache_dir is not None + else DEFAULT_CHAIN_CACHE_DIR + ) + cache_root.mkdir(parents=True, exist_ok=True) + cached_path = cache_root / f"{source}_to_{target}.over.chain.gz" + if cached_path.exists() and cached_path.stat().st_size > 0: + return cached_path + + url = chain_url if chain_url is not None else _chain_url_for_pair(source, target) + request_obj = request.Request(url, headers={"User-Agent": "dimelo-toolkit/1.0"}) + try: + with ( + request.urlopen(request_obj, timeout=timeout_seconds) as response, + cached_path.open("wb") as handle, + ): + shutil.copyfileobj(response, handle) + except Exception as exc: + raise RuntimeError( + "Failed to download CrossMap chain file for " + f"{source_genome!r} -> {target_genome!r} from {url!r}. " + "Provide an explicit chain_file path to continue." + ) from exc + return cached_path + + +def cache_chain_files( + *, + source_genome: str = "chm13", + target_genomes: Iterable[str] = ("hg38", "hg19"), + chain_cache_dir: str | Path | None = None, + timeout_seconds: float = 120.0, +) -> dict[str, Path]: + cached: dict[str, Path] = {} + for target in target_genomes: + cached[str(target)] = _resolve_chain_file( + source_genome=source_genome, + target_genome=str(target), + chain_cache_dir=chain_cache_dir, + timeout_seconds=timeout_seconds, + ) + return cached + + +def _resolve_crossmap_executable(preferred: str | None = None) -> str: + candidates = [] + if preferred: + candidates.append(str(preferred)) + for fallback in ("CrossMap.py", "CrossMap"): + if fallback not in candidates: + candidates.append(fallback) + for candidate in candidates: + if shutil.which(candidate): + return candidate + raise RuntimeError( + "CrossMap executable was not found on PATH. Install CrossMap and ensure " + "either 'CrossMap.py' or 'CrossMap' is available." + ) + + +def _normalize_text_token(value: str | None) -> str: + if value is None: + return "" + return str(value).strip().lower() + + +def _normalize_bed_threshold(threshold: str = "05") -> str: + token = _normalize_text_token(threshold) + if token not in _BED_THRESHOLD_COLUMN_MAP: + supported = ", ".join(sorted(_BED_THRESHOLD_COLUMN_MAP)) + raise ValueError( + f"Unsupported bed threshold {threshold!r}. Supported aliases: {supported}." + ) + return token + + +def _resolve_stratification_bins(stratify: str | int | None) -> int | None: + if stratify is None: + return None + if isinstance(stratify, int): + if stratify < 2: + raise ValueError("stratify must be >= 2 when provided as an integer.") + return int(stratify) + token = _normalize_text_token(stratify) + if token in _STRATIFY_TO_BIN_COUNT: + return _STRATIFY_TO_BIN_COUNT[token] + if token.isdigit(): + parsed = int(token) + if parsed < 2: + raise ValueError("stratify must be >= 2.") + return parsed + allowed = ", ".join(sorted(_STRATIFY_TO_BIN_COUNT)) + raise ValueError( + "Unsupported stratify value " + f"{stratify!r}. Use an integer >=2 or one of: {allowed}." + ) + + +def _download_bytes( + *, + url: str, + timeout_seconds: float = 120.0, + headers: Mapping[str, str] | None = None, +) -> bytes: + req_headers = {"User-Agent": "dimelo-toolkit/1.0"} + if headers: + req_headers.update(dict(headers)) + req = request.Request(url, headers=req_headers) + try: + with request.urlopen(req, timeout=timeout_seconds) as response: + return response.read() + except error.HTTPError as exc: + detail = exc.read().decode("utf-8", errors="replace") + raise RuntimeError( + f"ChIP-Atlas download failed with HTTP {exc.code}: {url}\n{detail[:300]}" + ) from exc + except error.URLError as exc: + raise RuntimeError( + f"ChIP-Atlas download failed for {url}: {exc.reason}" + ) from exc + + +def _ensure_experiment_list_zip( + *, + experiment_list_url: str, + cache_dir: str | Path, + allow_cached: bool = True, + timeout_seconds: float = 120.0, +) -> Path: + cache_root = Path(cache_dir).expanduser().resolve() + cache_root.mkdir(parents=True, exist_ok=True) + output_path = cache_root / "chip_atlas_experiment_list.zip" + if allow_cached and output_path.exists() and output_path.stat().st_size > 0: + return output_path + payload = _download_bytes(url=experiment_list_url, timeout_seconds=timeout_seconds) + tmp_path = output_path.with_suffix(output_path.suffix + ".tmp") + tmp_path.write_bytes(payload) + tmp_path.replace(output_path) + return output_path + + +def _iter_experiment_rows(experiment_list_zip: Path) -> Iterable[dict[str, str]]: + with zipfile.ZipFile(experiment_list_zip) as archive: + member = next( + (name for name in archive.namelist() if name.lower().endswith(".csv")), None + ) + if member is None: + raise RuntimeError( + "ChIP-Atlas experiment-list archive did not contain a CSV file." + ) + with archive.open(member, "r") as handle: + reader = csv.DictReader( + io.TextIOWrapper(handle, encoding="utf-8", errors="replace") + ) + for row in reader: + yield {str(k): ("" if v is None else str(v)) for k, v in row.items()} + + +def _score_series_for_bed(table: pd.DataFrame) -> pd.Series: + if table.shape[1] > 4: + return pd.to_numeric(table.iloc[:, 4], errors="coerce") + return pd.Series(np.zeros(table.shape[0]), index=table.index, dtype=float) + + +def _sorted_bed_by_score(table: pd.DataFrame) -> pd.DataFrame: + scores = _score_series_for_bed(table).fillna(float("-inf")) + order = scores.sort_values(ascending=False, kind="stable").index + return table.loc[order].reset_index(drop=True) + + +def _coerce_bed6(table: pd.DataFrame) -> pd.DataFrame: + if table.shape[1] < 3: + raise ValueError("BED table requires at least 3 columns.") + out = pd.DataFrame( + { + "chrom": table.iloc[:, 0].astype(str), + "start": pd.to_numeric(table.iloc[:, 1], errors="coerce") + .fillna(-1) + .astype(int), + "end": pd.to_numeric(table.iloc[:, 2], errors="coerce") + .fillna(-1) + .astype(int), + "name": table.iloc[:, 3].astype(str) if table.shape[1] > 3 else ".", + "score": table.iloc[:, 4] if table.shape[1] > 4 else 0, + "strand": table.iloc[:, 5].astype(str) if table.shape[1] > 5 else ".", + } + ) + out = out.loc[out["end"] > out["start"]].reset_index(drop=True) + return out + + +def _extract_request_id(body: str) -> str: + stripped = body.strip() + if not stripped: + raise ValueError("ChIP-Atlas submit response was empty; no request id found.") + + try: + parsed = json.loads(stripped) + except json.JSONDecodeError: + parsed = None + + if isinstance(parsed, dict): + for key in ("id", "requestId", "request_id"): + value = parsed.get(key) + if value: + return str(value) + + if "?" in stripped and "id=" in stripped: + parsed_url = parse.urlparse(stripped) + query = parse.parse_qs(parsed_url.query) + request_ids = query.get("id") + if request_ids: + return str(request_ids[0]) + + id_match = re.search( + r"\b(?:id|requestId|request_id)\s*[:=]\s*['\"]?([A-Za-z0-9._-]+)", stripped + ) + if id_match: + return id_match.group(1) + + if re.fullmatch(r"[A-Za-z0-9._-]+", stripped): + return stripped + + raise ValueError( + "Could not parse ChIP-Atlas request id from submit response. " + f"Response snippet: {stripped[:160]}" + ) + + +def _normalize_status(status: str | None) -> str: + if status is None: + return "unknown" + normalized = str(status).strip().lower() + if not normalized: + return "unknown" + return _STATUS_ALIASES.get(normalized, normalized) + + +def _extract_status_value(body: str, parsed_body: Any) -> str | None: + if isinstance(parsed_body, dict): + for key in ("status", "state", "job_status", "phase"): + value = parsed_body.get(key) + if value is not None: + return str(value) + + if isinstance(parsed_body, str) and parsed_body.strip(): + return parsed_body.strip().splitlines()[0].strip() + + lowered = body.lower() + for token in sorted(_STATUS_ALIASES, key=len, reverse=True): + if re.search(rf"\b{re.escape(token)}\b", lowered): + return token + return None + + +def _parse_region_id(region_id: str) -> tuple[str, int, int, str]: + raw = region_id.strip() + if not raw: + raise ValueError("Empty region id is not allowed.") + if ":" not in raw or "-" not in raw: + raise ValueError( + "Region ids must look like 'chr:start-end' or 'chr:start-end,strand'. " + f"Received: {region_id!r}" + ) + + chrom, remainder = raw.split(":", 1) + strand = "." + if "," in remainder: + core, strand_token = remainder.rsplit(",", 1) + if strand_token in {"+", "-", "."}: + strand = strand_token + remainder = core + elif remainder.count(":") >= 1: + core, suffix = remainder.rsplit(":", 1) + if suffix in {"+", "-", "."}: + strand = suffix + remainder = core + + start_text, end_text = remainder.split("-", 1) + start = int(start_text) + end = int(end_text) + if start < 0 or end < 0 or end <= start: + raise ValueError(f"Invalid region coordinates in {region_id!r}") + return chrom, start, end, strand + + +def region_ids_to_bed_dataframe(region_ids: Iterable[str]) -> pd.DataFrame: + rows = [] + for index, region_id in enumerate(region_ids): + chrom, start, end, strand = _parse_region_id(str(region_id)) + rows.append( + { + "chrom": chrom, + "start": start, + "end": end, + "name": f"region_{index}", + "score": 0, + "strand": strand, + } + ) + return pd.DataFrame( + rows, columns=["chrom", "start", "end", "name", "score", "strand"] + ) + + +def _regions_dataframe_from_input( + regions: pd.DataFrame | str | Path | Iterable[str], +) -> pd.DataFrame: + if isinstance(regions, pd.DataFrame): + source = regions.copy() + elif isinstance(regions, Path): + source = pd.read_csv(regions, sep="\t", header=None, comment="#") + elif isinstance(regions, str): + candidate = Path(regions) + if "\n" not in regions and "\t" not in regions and candidate.exists(): + source = pd.read_csv(candidate, sep="\t", header=None, comment="#") + else: + lines = [line.strip() for line in regions.splitlines() if line.strip()] + return _regions_dataframe_from_lines(lines) + else: + lines = [str(line).strip() for line in regions if str(line).strip()] + return _regions_dataframe_from_lines(lines) + + if not isinstance(source, pd.DataFrame): + raise TypeError("Could not coerce ChIP-Atlas regions to a DataFrame.") + if source.empty: + return pd.DataFrame( + columns=["chrom", "start", "end", "name", "score", "strand"] + ) + + if source.columns.dtype == object and {"start", "end"}.issubset(source.columns): + chrom_column = "chrom" if "chrom" in source.columns else "chromosome" + if chrom_column not in source.columns: + raise ValueError( + "Region DataFrame must include 'start', 'end', and 'chrom' or 'chromosome'." + ) + normalized = pd.DataFrame( + { + "chrom": source[chrom_column].astype(str), + "start": source["start"].astype(int), + "end": source["end"].astype(int), + "name": source.get("name", pd.Series(".", index=source.index)).astype( + str + ), + "score": source.get("score", pd.Series(0, index=source.index)), + "strand": source.get( + "strand", pd.Series(".", index=source.index) + ).astype(str), + } + ) + return normalized + + if len(source.columns) < 3: + raise ValueError("BED-like input requires at least 3 columns.") + normalized = pd.DataFrame( + { + "chrom": source.iloc[:, 0].astype(str), + "start": source.iloc[:, 1].astype(int), + "end": source.iloc[:, 2].astype(int), + "name": source.iloc[:, 3].astype(str) if len(source.columns) > 3 else ".", + "score": source.iloc[:, 4] if len(source.columns) > 4 else 0, + "strand": source.iloc[:, 5].astype(str) if len(source.columns) > 5 else ".", + } + ) + return normalized + + +def _regions_dataframe_from_lines(lines: Iterable[str]) -> pd.DataFrame: + rows = [] + for index, line in enumerate(lines): + if "\t" in line: + tokens = line.split("\t") + chrom = tokens[0] + start = int(tokens[1]) + end = int(tokens[2]) + name = tokens[3] if len(tokens) > 3 else f"region_{index}" + score = tokens[4] if len(tokens) > 4 else 0 + strand = tokens[5] if len(tokens) > 5 else "." + else: + chrom, start, end, strand = _parse_region_id(line) + name = f"region_{index}" + score = 0 + rows.append( + { + "chrom": chrom, + "start": start, + "end": end, + "name": name, + "score": score, + "strand": strand if strand in {"+", "-", "."} else ".", + } + ) + return pd.DataFrame( + rows, columns=["chrom", "start", "end", "name", "score", "strand"] + ) + + +def _regions_to_bed_text(regions: pd.DataFrame | str | Path | Iterable[str]) -> str: + region_frame = _regions_dataframe_from_input(regions) + if region_frame.empty: + raise ValueError("ChIP-Atlas enrichment requires at least one region.") + lines = [] + for row in region_frame.itertuples(index=False): + lines.append( + "\t".join( + [ + str(row.chrom), + str(int(row.start)), + str(int(row.end)), + str(row.name), + str(row.score), + str(row.strand if row.strand in {"+", "-", "."} else "."), + ] + ) + ) + return "\n".join(lines) + + +def convert_regions_with_crossmap( + *, + regions: pd.DataFrame | str | Path | Iterable[str], + source_genome: str, + target_genome: str, + chain_file: str | Path | None = None, + chain_url: str | None = None, + chain_cache_dir: str | Path | None = None, + crossmap_executable: str | None = "CrossMap.py", + timeout_seconds: float = 300.0, +) -> pd.DataFrame: + source = _normalize_genome_name(source_genome) + target = _normalize_genome_name(target_genome) + region_frame = _regions_dataframe_from_input(regions) + if source == target: + return region_frame + if region_frame.empty: + return region_frame + + resolved_chain = _resolve_chain_file( + source_genome=source, + target_genome=target, + chain_file=chain_file, + chain_url=chain_url, + chain_cache_dir=chain_cache_dir, + timeout_seconds=min(timeout_seconds, 120.0), + ) + with tempfile.TemporaryDirectory(prefix="dimelo-crossmap-") as tmpdir: + tmp_path = Path(tmpdir) + input_bed = tmp_path / "input.bed" + output_bed = tmp_path / "mapped.bed" + region_frame.to_csv( + input_bed, + sep="\t", + index=False, + header=False, + columns=["chrom", "start", "end", "name", "score", "strand"], + ) + resolved_executable = _resolve_crossmap_executable(crossmap_executable) + cmd = [ + resolved_executable, + "bed", + str(resolved_chain), + str(input_bed), + str(output_bed), + ] + try: + proc = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=timeout_seconds, + check=False, + ) + except FileNotFoundError as exc: + raise RuntimeError( + f"CrossMap executable {resolved_executable!r} was not found. " + "Install CrossMap and ensure it is available on PATH." + ) from exc + except subprocess.TimeoutExpired as exc: + raise RuntimeError( + "CrossMap conversion timed out while converting regions from " + f"{source_genome!r} to {target_genome!r}." + ) from exc + if proc.returncode != 0: + raise RuntimeError( + "CrossMap conversion failed with exit code " + f"{proc.returncode}. stderr: {proc.stderr.strip()[:500]}" + ) + if not output_bed.exists() or output_bed.stat().st_size == 0: + raise RuntimeError( + "CrossMap conversion produced no mapped regions. " + f"source_genome={source_genome!r}, target_genome={target_genome!r}" + ) + + mapped = pd.read_csv(output_bed, sep="\t", header=None, comment="#") + if mapped.empty: + raise RuntimeError( + "CrossMap conversion returned an empty mapped region table." + ) + normalized = pd.DataFrame( + { + "chrom": mapped.iloc[:, 0].astype(str), + "start": mapped.iloc[:, 1].astype(int), + "end": mapped.iloc[:, 2].astype(int), + "name": mapped.iloc[:, 3].astype(str) + if len(mapped.columns) > 3 + else ".", + "score": mapped.iloc[:, 4] if len(mapped.columns) > 4 else 0, + "strand": mapped.iloc[:, 5].astype(str) + if len(mapped.columns) > 5 + else ".", + } + ) + normalized = normalized[normalized["end"] > normalized["start"]].reset_index( + drop=True + ) + if normalized.empty: + raise RuntimeError( + "CrossMap conversion filtered all regions after coordinate validation." + ) + return normalized + + +def _build_url(base_url: str, params_dict: Mapping[str, Any]) -> str: + query = parse.urlencode( + {key: value for key, value in params_dict.items() if value is not None} + ) + return f"{base_url}?{query}" if query else base_url + + +def _request_url_with_request_id(base_url: str, request_id: str) -> str: + base = str(base_url).strip() + if not base: + raise ValueError("ChIP-Atlas URL cannot be empty.") + rid = parse.quote(str(request_id).strip(), safe="") + if not rid: + raise ValueError("request_id cannot be empty.") + if "{request_id}" in base: + return base.format(request_id=rid) + if base.rstrip("/").endswith(str(request_id).strip()): + return base + return f"{base.rstrip('/')}/{rid}" + + +def submit_enrichment( + *, + regions: pd.DataFrame | str | Path | Iterable[str], + genome: str = "hg38", + regions_genome: str | None = None, + antigen_class: str | None = "TFs and others", + antigen: str | None = None, + cell_type_class: str | None = "No description", + cell_type: str | None = None, + distance: int | None = None, + threshold: str | None = "100", + crossmap_chain_file: str | Path | None = None, + crossmap_chain_url: str | None = None, + crossmap_chain_cache_dir: str | Path | None = None, + crossmap_executable: str | None = "CrossMap.py", + params: Mapping[str, Any] | None = None, + submit_url: str = DEFAULT_SUBMIT_URL, + timeout_seconds: float = 60.0, +) -> dict[str, Any]: + region_payload: pd.DataFrame | str | Path | Iterable[str] = regions + conversion_metadata: dict[str, Any] = {"applied": False} + if regions_genome is not None: + input_build = _normalize_genome_name(regions_genome) + atlas_build = _normalize_genome_name(genome) + if input_build != atlas_build: + resolved_chain = _resolve_chain_file( + source_genome=input_build, + target_genome=atlas_build, + chain_file=crossmap_chain_file, + chain_url=crossmap_chain_url, + chain_cache_dir=crossmap_chain_cache_dir, + timeout_seconds=max(timeout_seconds, 120.0), + ) + region_payload = convert_regions_with_crossmap( + regions=regions, + source_genome=input_build, + target_genome=atlas_build, + chain_file=resolved_chain, + chain_url=crossmap_chain_url, + chain_cache_dir=crossmap_chain_cache_dir, + crossmap_executable=crossmap_executable, + timeout_seconds=max(timeout_seconds, 120.0), + ) + conversion_metadata = { + "applied": True, + "source_genome": input_build, + "target_genome": atlas_build, + "chain_file": str(resolved_chain), + } + else: + conversion_metadata = { + "applied": False, + "source_genome": input_build, + "target_genome": atlas_build, + } + + bed_text = _regions_to_bed_text(region_payload) + payload: dict[str, Any] = { + "format": "text", + "result": "www", + "genome": genome, + "typeA": "bed", + "bedAFile": bed_text, + "typeB": "rnd", + "bedBFile": "empty", + "permTime": 10, + "descriptionA": "query_regions", + "descriptionB": "random_permutation_background", + "title": "dimelo_toolkit_enrichment", + } + if antigen_class is not None: + payload["antigenClass"] = antigen_class + if antigen is not None: + payload["antigen"] = antigen + if cell_type_class is not None: + payload["cellClass"] = cell_type_class + payload["celltypeClass"] = cell_type_class + if cell_type is not None: + payload["cellType"] = cell_type + payload["celltype"] = cell_type + if distance is not None: + payload["distanceUp"] = int(distance) + payload["distanceDown"] = int(distance) + if threshold is not None: + payload["threshold"] = threshold + if params: + payload.update(dict(params)) + encoded = parse.urlencode( + { + key: ",".join(map(str, value)) + if isinstance(value, (list, tuple, set)) + else str(value) + for key, value in payload.items() + if value is not None + } + ).encode("utf-8") + + status_code, content_type, body = _http_request( + url=submit_url, + method="POST", + data=encoded, + timeout_seconds=timeout_seconds, + ) + request_id = _extract_request_id(body) + return { + "request_id": request_id, + "status_code": status_code, + "content_type": content_type, + "response": body, + "submit_url": submit_url, + "query": { + "genome": genome, + "regions_genome": regions_genome, + "antigen_class": antigen_class, + "antigen": antigen, + "cell_type_class": cell_type_class, + "cell_type": cell_type, + "distance": distance, + "threshold": threshold, + "crossmap": conversion_metadata, + "params": dict(params or {}), + }, + } + + +def get_status( + request_id: str, + *, + status_url: str = DEFAULT_STATUS_URL, + params: Mapping[str, Any] | None = None, + timeout_seconds: float = 30.0, +) -> dict[str, Any]: + url = _request_url_with_request_id(status_url, request_id) + query_params: dict[str, Any] = {} + if params: + query_params.update(dict(params)) + if query_params: + url = _build_url(url, query_params) + status_code, content_type, body = _http_request( + url=url, + method="GET", + timeout_seconds=timeout_seconds, + ) + try: + parsed_body = json.loads(body) + except json.JSONDecodeError: + parsed_body = None + + raw_status = _extract_status_value(body, parsed_body) + status = _normalize_status(raw_status) + return { + "request_id": request_id, + "status": status, + "raw_status": raw_status, + "status_code": status_code, + "content_type": content_type, + "response": parsed_body if parsed_body is not None else body, + "status_url": url, + } + + +def poll_request( + request_id: str, + *, + status_url: str = DEFAULT_STATUS_URL, + poll_interval_seconds: float = 5.0, + timeout_seconds: float = 600.0, + params: Mapping[str, Any] | None = None, +) -> dict[str, Any]: + history: list[dict[str, Any]] = [] + deadline = time.monotonic() + timeout_seconds + while True: + snapshot = get_status( + request_id, + status_url=status_url, + params=params, + timeout_seconds=min(30.0, max(poll_interval_seconds, 1.0)), + ) + history.append( + { + "time_epoch_s": time.time(), + "status": snapshot["status"], + "raw_status": snapshot.get("raw_status"), + "status_code": snapshot.get("status_code"), + } + ) + if snapshot["status"] in _TERMINAL_STATUSES: + return { + "request_id": request_id, + "status": snapshot["status"], + "history": history, + "last_snapshot": snapshot, + } + if time.monotonic() >= deadline: + raise TimeoutError( + f"Timed out waiting for ChIP-Atlas request {request_id!r} after {timeout_seconds} seconds." + ) + time.sleep(max(poll_interval_seconds, 0.0)) + + +def fetch_result( + request_id: str, + *, + result_url: str = DEFAULT_RESULT_URL, + params: Mapping[str, Any] | None = None, + timeout_seconds: float = 120.0, + as_dataframe: bool = True, + sep: str = "\t", +) -> pd.DataFrame | str: + url = _request_url_with_request_id(result_url, request_id) + query_params: dict[str, Any] = {"info": "result", "format": "tsv"} + if params: + query_params.update(dict(params)) + url = _build_url(url, query_params) + _, _, body = _http_request( + url=url, + method="GET", + timeout_seconds=timeout_seconds, + ) + if not as_dataframe: + return body + return pd.read_csv(io.StringIO(body), sep=sep) + + +def run_enrichment( + *, + regions: pd.DataFrame | str | Path | Iterable[str], + genome: str = "hg38", + regions_genome: str | None = None, + antigen_class: str | None = "TFs and others", + antigen: str | None = None, + cell_type_class: str | None = "No description", + cell_type: str | None = None, + distance: int | None = None, + threshold: str | None = "100", + crossmap_chain_file: str | Path | None = None, + crossmap_chain_url: str | None = None, + crossmap_chain_cache_dir: str | Path | None = None, + crossmap_executable: str | None = "CrossMap.py", + params: Mapping[str, Any] | None = None, + wait: bool = True, + fetch_results: bool = True, + raise_on_failure: bool = True, + submit_url: str = DEFAULT_SUBMIT_URL, + status_url: str = DEFAULT_STATUS_URL, + result_url: str = DEFAULT_RESULT_URL, + poll_interval_seconds: float = 5.0, + timeout_seconds: float = 600.0, +) -> ChipAtlasEnrichmentResult: + submission = submit_enrichment( + regions=regions, + genome=genome, + regions_genome=regions_genome, + antigen_class=antigen_class, + antigen=antigen, + cell_type_class=cell_type_class, + cell_type=cell_type, + distance=distance, + threshold=threshold, + crossmap_chain_file=crossmap_chain_file, + crossmap_chain_url=crossmap_chain_url, + crossmap_chain_cache_dir=crossmap_chain_cache_dir, + crossmap_executable=crossmap_executable, + params=params, + submit_url=submit_url, + ) + request_id = str(submission["request_id"]) + history: list[dict[str, Any]] = [] + final_status = "queued" + + if wait: + poll = poll_request( + request_id, + status_url=status_url, + poll_interval_seconds=poll_interval_seconds, + timeout_seconds=timeout_seconds, + ) + final_status = poll["status"] + history = poll["history"] + + if raise_on_failure and wait and final_status not in _SUCCESS_STATUSES: + raise RuntimeError( + f"ChIP-Atlas request {request_id!r} finished with status {final_status!r}." + ) + + result_table: pd.DataFrame | None = None + if fetch_results and wait and final_status in _SUCCESS_STATUSES: + fetched = fetch_result( + request_id, + result_url=result_url, + timeout_seconds=timeout_seconds, + as_dataframe=True, + ) + result_table = fetched if isinstance(fetched, pd.DataFrame) else None + + return ChipAtlasEnrichmentResult( + request_id=request_id, + status=final_status, + results=result_table, + query=submission["query"], + status_history=history, + submit_url=submit_url, + status_url=status_url, + result_url=result_url, + metadata={ + "submission": {k: v for k, v in submission.items() if k != "response"} + }, + ) + + +def search_peak_datasets( + *, + antigen: str, + genome: str = "hg38", + cell_type: str | None = None, + antigen_class: str | None = None, + cell_type_class: str | None = None, + threshold: str = "05", + match_mode: str = "contains", + max_results: int | None = None, + experiment_list_url: str = DEFAULT_EXPERIMENT_LIST_URL, + cache_dir: str | Path = DEFAULT_METADATA_CACHE_DIR, + allow_cached_metadata: bool = True, + timeout_seconds: float = 120.0, +) -> pd.DataFrame: + """ + Search ChIP-Atlas experiment-level peak datasets by antigen and optional metadata filters. + + This reads the published ChIP-Atlas experiment metadata table and returns candidate datasets + with direct BED URLs for the requested peak threshold. + """ + + antigen_query = str(antigen).strip() + if not antigen_query: + raise ValueError("antigen is required.") + if match_mode not in {"contains", "exact"}: + raise ValueError("match_mode must be 'contains' or 'exact'.") + if max_results is not None and int(max_results) <= 0: + raise ValueError("max_results must be positive when provided.") + + threshold_key = _normalize_bed_threshold(threshold) + bed_column = _BED_THRESHOLD_COLUMN_MAP[threshold_key] + experiment_zip = _ensure_experiment_list_zip( + experiment_list_url=experiment_list_url, + cache_dir=cache_dir, + allow_cached=allow_cached_metadata, + timeout_seconds=timeout_seconds, + ) + + genome_query = _normalize_text_token(genome) + antigen_class_query = _normalize_text_token(antigen_class) + cell_type_class_query = _normalize_text_token(cell_type_class) + cell_type_query = _normalize_text_token(cell_type) + antigen_norm = _normalize_text_token(antigen_query) + + def _matches(value: str, query: str) -> bool: + value_norm = _normalize_text_token(value) + if not query: + return True + if match_mode == "exact": + return value_norm == query + return query in value_norm + + rows: list[dict[str, Any]] = [] + for raw in _iter_experiment_rows(experiment_zip): + row_genome = _normalize_text_token(raw.get("Genome assembly", "")) + if genome_query and row_genome != genome_query: + continue + if not _matches(raw.get("Antigen", ""), antigen_norm): + continue + if antigen_class_query and not _matches( + raw.get("Antigen class", ""), antigen_class_query + ): + continue + if cell_type_class_query and not _matches( + raw.get("Cell type class", ""), cell_type_class_query + ): + continue + if cell_type_query and not _matches(raw.get("Cell type", ""), cell_type_query): + continue + + bed_url = str(raw.get(bed_column, "")).strip() + if not bed_url: + continue + rows.append( + { + "dataset_id": str(raw.get("Experimental ID", "")).strip(), + "genome_assembly": str(raw.get("Genome assembly", "")).strip(), + "antigen_class": str(raw.get("Antigen class", "")).strip(), + "antigen": str(raw.get("Antigen", "")).strip(), + "cell_type_class": str(raw.get("Cell type class", "")).strip(), + "cell_type": str(raw.get("Cell type", "")).strip(), + "cell_type_description": str( + raw.get("Cell type description", "") + ).strip(), + "processing_logs": str(raw.get("Processing logs", "")).strip(), + "title": str(raw.get("Title", "")).strip(), + "metadata": str(raw.get("Meta data", "")).strip(), + "bigwig_url": str(raw.get("BigWig", "")).strip(), + "bed_url": bed_url, + "bigbed_url": str( + raw.get( + bed_column.replace("Peak-call (BED)", "Peak-call (BigBed)"), + "", + ) + ).strip(), + "threshold_key": threshold_key, + "threshold_column": bed_column, + } + ) + if max_results is not None and len(rows) >= int(max_results): + break + + return pd.DataFrame(rows) + + +def download_peak_datasets( + *, + datasets: pd.DataFrame, + dataset_ids: Iterable[str] | None = None, + output_dir: str | Path = "cache/chip_atlas/peak_sets", + include_complete_sorted: bool = True, + include_top_n: int | None = 3000, + include_bottom_n: int | None = 3000, + stratify: str | int | None = None, + allow_cached: bool = True, + timeout_seconds: float = 180.0, + crossmap_target_genome: str | None = None, + crossmap_chain_file: str | Path | None = None, + crossmap_chain_url: str | None = None, + crossmap_chain_cache_dir: str | Path | None = None, + crossmap_executable: str | None = "CrossMap.py", +) -> pd.DataFrame: + """ + Download selected ChIP-Atlas BED datasets and optionally generate sorted/stratified subsets. + + Outputs are written as BED files under ``output_dir//`` and returned in a manifest. + """ + + if not isinstance(datasets, pd.DataFrame): + raise TypeError("datasets must be a pandas DataFrame.") + required = {"dataset_id", "bed_url", "genome_assembly"} + missing = required - set(datasets.columns) + if missing: + raise ValueError( + f"datasets is missing required columns: {', '.join(sorted(missing))}" + ) + if include_top_n is not None and int(include_top_n) < 0: + raise ValueError("include_top_n must be non-negative.") + if include_bottom_n is not None and int(include_bottom_n) < 0: + raise ValueError("include_bottom_n must be non-negative.") + + stratify_bins = _resolve_stratification_bins(stratify) + selected = datasets.copy() + if dataset_ids is not None: + wanted = { + str(dataset_id).strip() + for dataset_id in dataset_ids + if str(dataset_id).strip() + } + selected = selected.loc[selected["dataset_id"].astype(str).isin(wanted)].copy() + if selected.empty: + return pd.DataFrame( + columns=[ + "dataset_id", + "variant", + "path", + "n_rows", + "source_genome", + "target_genome", + "crossmapped", + "bed_url", + ] + ) + + output_root = Path(output_dir).expanduser().resolve() + output_root.mkdir(parents=True, exist_ok=True) + manifest_rows: list[dict[str, Any]] = [] + + def _write_variant(path: Path, frame: pd.DataFrame) -> Path: + path.parent.mkdir(parents=True, exist_ok=True) + frame.to_csv(path, sep="\t", index=False, header=False) + return path + + for row in selected.itertuples(index=False): + dataset_id = str(row.dataset_id) + bed_url = str(row.bed_url) + source_genome = str(row.genome_assembly) + dataset_dir = output_root / dataset_id + raw_path = dataset_dir / "raw.bed" + + if not (allow_cached and raw_path.exists() and raw_path.stat().st_size > 0): + payload = _download_bytes(url=bed_url, timeout_seconds=timeout_seconds) + raw_path.parent.mkdir(parents=True, exist_ok=True) + tmp_path = raw_path.with_suffix(".bed.tmp") + tmp_path.write_bytes(payload) + tmp_path.replace(raw_path) + + bed_table = pd.read_csv(raw_path, sep="\t", header=None, comment="#") + if bed_table.empty: + continue + sorted_table = _sorted_bed_by_score(bed_table) + variants: list[tuple[str, pd.DataFrame]] = [] + if include_complete_sorted: + variants.append(("full_sorted", sorted_table)) + if include_top_n is not None and int(include_top_n) > 0: + variants.append( + ( + f"top_{int(include_top_n)}", + sorted_table.head(int(include_top_n)).copy(), + ) + ) + if include_bottom_n is not None and int(include_bottom_n) > 0: + variants.append( + ( + f"bottom_{int(include_bottom_n)}", + sorted_table.tail(int(include_bottom_n)).copy(), + ) + ) + if stratify_bins is not None and len(sorted_table) > 0: + chunks = np.array_split(np.arange(len(sorted_table)), stratify_bins) + for i, idx in enumerate(chunks, start=1): + if len(idx) == 0: + continue + variants.append( + ( + f"quantile_{i}_of_{stratify_bins}", + sorted_table.iloc[idx].copy(), + ) + ) + + target_genome = source_genome + if crossmap_target_genome is not None: + target_genome = str(crossmap_target_genome) + + for variant_name, variant_table in variants: + variant_path = dataset_dir / f"{variant_name}.bed" + _write_variant(variant_path, variant_table) + manifest_rows.append( + { + "dataset_id": dataset_id, + "variant": variant_name, + "path": str(variant_path), + "n_rows": int(len(variant_table)), + "source_genome": source_genome, + "target_genome": source_genome, + "crossmapped": False, + "bed_url": bed_url, + } + ) + if crossmap_target_genome is None: + continue + if _normalize_genome_name(source_genome) == _normalize_genome_name( + target_genome + ): + continue + bed6 = _coerce_bed6(variant_table) + if bed6.empty: + continue + mapped = convert_regions_with_crossmap( + regions=bed6, + source_genome=source_genome, + target_genome=target_genome, + chain_file=crossmap_chain_file, + chain_url=crossmap_chain_url, + chain_cache_dir=crossmap_chain_cache_dir, + crossmap_executable=crossmap_executable, + ) + mapped_path = dataset_dir / f"{variant_name}.{target_genome}.bed" + mapped.to_csv(mapped_path, sep="\t", index=False, header=False) + manifest_rows.append( + { + "dataset_id": dataset_id, + "variant": f"{variant_name}_crossmapped", + "path": str(mapped_path), + "n_rows": int(len(mapped)), + "source_genome": source_genome, + "target_genome": target_genome, + "crossmapped": True, + "bed_url": bed_url, + } + ) + + return pd.DataFrame(manifest_rows) + + +__all__ = [ + "DEFAULT_EXPERIMENT_LIST_URL", + "DEFAULT_CHAIN_CACHE_DIR", + "DEFAULT_METADATA_CACHE_DIR", + "DEFAULT_RESULT_URL", + "DEFAULT_STATUS_URL", + "DEFAULT_SUBMIT_URL", + "cache_chain_files", + "convert_regions_with_crossmap", + "download_peak_datasets", + "fetch_result", + "get_status", + "poll_request", + "region_ids_to_bed_dataframe", + "run_enrichment", + "search_peak_datasets", + "submit_enrichment", +] diff --git a/dimelo/cluster.py b/dimelo/cluster.py new file mode 100644 index 0000000..7042b52 --- /dev/null +++ b/dimelo/cluster.py @@ -0,0 +1,6065 @@ +from __future__ import annotations + +import contextlib +import math +import warnings +from collections import Counter, defaultdict +from collections.abc import Sequence +from dataclasses import dataclass +from functools import partial +from itertools import combinations +from pathlib import Path +from typing import Any + +import numpy as np +import pandas as pd +from tqdm.auto import tqdm + +from . import load_processed, utils + +try: # Optional SciPy dependency for advanced peak metrics + from scipy.signal import find_peaks +except Exception: # pragma: no cover - SciPy is optional + find_peaks = None + +try: # Optional dependency for density-based clustering + import hdbscan as _hdbscan_lib + + _HAS_HDBSCAN = True +except Exception: # pragma: no cover - HDBSCAN is optional + _HAS_HDBSCAN = False + +RegionMetadata = tuple[str, int, int, str] +# Read metadata fields pulled alongside feature matrices by default +DEFAULT_METADATA_FIELDS: tuple[str, ...] = ( + "read_name", + "chromosome", + "region_start", + "region_end", + "motif", + "region_strand", + "read_length", +) + +DEFAULT_AUTOCORR_LAGS = (20, 75, 100, 150, 250) +DEFAULT_DENSITY_WINDOWS = ( + ("density_pm50", -50, 50), + ("density_pm75", -75, 75), + ("density_pm100", -100, 100), + ("density_pm300", -300, 300), + ("density_pm500", -500, 500), + ("density_plus240", 140, 340), + ("density_minus240", -340, -140), + ("density_plus320", 220, 420), + ("density_minus320", -420, -220), + ("density_plus800", 700, 900), + ("density_minus800", -900, -700), + ("density_plus150", 50, 250), + ("density_minus150", -250, -50), +) + +DEFAULT_RICH_FEATURE_SPEC: dict[str, Any] = { + "motif_mode": "per_motif", + "motif_count": 1, + "motif_labels": None, + "pooled": True, + "pca": {"enabled": True, "n_components": 6, "scope": "pooled"}, + "summary": {"enabled": True}, + "densities": {"enabled": True, "windows": DEFAULT_DENSITY_WINDOWS}, + "asymmetry": {"enabled": True, "spans": (100, 300, 500)}, + "center_edge": {"enabled": True, "center_bp": 100, "edge_bp": 200}, + "autocorr": {"enabled": True, "lags": DEFAULT_AUTOCORR_LAGS}, + "fft": {"enabled": True, "periods_bp": (10, 50, 100, 150, 200)}, + "peaks": {"enabled": False, "prominence": 0.005}, + "cross_motif": {"enabled": True}, +} + + +@dataclass +class ReadWindowExtractionConfig: + # Legacy-style half-window around region center (full span = 2 * window_size) + window_size: int | None = None + orientation_aware: bool = True + filter_multi_region_reads: bool = False + # Require thresholded/binary read vectors for downstream clustering/classification. + # If raw probabilities are detected, apply this threshold automatically unless set to None. + auto_threshold_if_raw: float | int | None = 190 + enforce_thresholded_vectors: bool = True + warn_on_auto_threshold: bool = True + + +@dataclass +class ReadWindowExtractionResult: + data_matrix: np.ndarray + val_matrix: np.ndarray | None + metadata: list[dict[str, Any]] + datasets: list[str] + regions_dict: dict | None + + +@dataclass(frozen=True) +class _RasterSiteSelection: + mode: str + n_windows: int + min_distance_bp: int + max_distance_bp: int | None + selection_multiplicity: str + choose: str + selection_seed: int | None + anchor: dict[str, Any] + strand_relation: str + exclude: dict[str, Any] | None + orientation: str + window_offsets_bp: tuple[int, ...] | None + primary_window_index: int + + +@dataclass(frozen=True) +class _RasterSiteCandidate: + row_idx: int + center_bp: float + chrom: str | None + start_bp: int | None + end_bp: int | None + strand: str | None + read_key: tuple[Any, Any] + + +@dataclass +class _RasterSiteSelectionResult: + window_indices: list[np.ndarray] + panel_centers: list[np.ndarray] + stats: dict[str, Any] + + +@dataclass +class ClusterResult: + labels_raw: np.ndarray + labels_size_ordered: np.ndarray + model: Any + metrics: dict[str, float | None] + + +def region_feature_matrix_from_pileup( + bedmethyl_file: str | Path, + motif: str, + regions: str | Path | list[str | Path], + window_size: int | None = None, + single_strand: bool = False, + regions_5to3prime: bool = True, + *, + pseudo_count: float = 1e-3, + chunk_size: int = load_processed.DEFAULT_CHUNK_SIZE, + split_large_regions: bool = False, + quiet: bool = False, + cores: int | None = None, + regions_executor=None, +) -> tuple[np.ndarray, list[RegionMetadata]]: + """ + Convert every region in a pileup into a vector of modification fractions suitable for clustering. + Uses pileup loaders so we never touch raw BAMs here. + + Args: + bedmethyl_file: path to the processed bedmethyl file produced by parse_bam.pileup + motif: motif string (e.g. "A,0") to pull from the pileup + regions: region specifier accepted by load_processed (bed path, list, or str) + window_size: optional window applied to every region before loading + single_strand: whether to only use reads on the strand specified per region + regions_5to3prime: align all regions to the 5'->3' direction before aggregation + pseudo_count: small constant used to avoid division by zero when computing fractions + chunk_size: chunk size forwarded to load_processed.pileup_vectors_from_bedmethyl + split_large_regions: mirror of regions_to_list parameter for large regions + quiet: disable progress bars in downstream loader + cores: number of worker processes for loaders + + Returns: + Tuple containing: + * 2D numpy array (n_regions, region_length) of modification fractions + * ordered list with metadata for every region (chrom, start, end, strand) + """ + + if regions is None: + raise ValueError("regions must be provided when building a clustering matrix.") + + regions_dict = utils.regions_dict_from_input(regions, window_size) + region_metadata: list[RegionMetadata] = [] + region_strings: list[str] = [] + for chromosome, region_list in regions_dict.items(): + for start, end, strand in region_list: + region_metadata.append((chromosome, start, end, strand)) + region_strings.append(f"{chromosome}:{start}-{end},{strand}") + if len(region_metadata) == 0: + raise ValueError("No regions were found to build clustering features.") + + loader = partial( + _pileup_fraction_vector_from_bedmethyl, + bedmethyl_file=bedmethyl_file, + motif=motif, + window_size=window_size, + single_strand=single_strand, + regions_5to3prime=regions_5to3prime, + pseudo_count=pseudo_count, + quiet=quiet, + cores=cores, + chunk_size=chunk_size, + ) + per_region_vectors = load_processed.regions_to_list( + function_handle=loader, + regions=region_strings, + window_size=window_size, + quiet=quiet, + cores=cores, + split_large_regions=split_large_regions, + executor=regions_executor, + ) + + if len(per_region_vectors) != len(region_metadata): + raise RuntimeError( + "Region metadata and pileup vectors have mismatched lengths. " + "Double-check the region inputs." + ) + + feature_vectors = [] + expected_length: int | None = None + for vector in tqdm(per_region_vectors, desc="Processing regions", disable=quiet): + # Backward-compatible path if upstream loaders still return (modified, valid). + if isinstance(vector, tuple) and len(vector) == 2: + modified = np.asarray(vector[0], dtype=np.float32) + valid = np.asarray(vector[1], dtype=np.float32) + if modified.shape != valid.shape: + raise ValueError( + "Modified and valid vectors must have matching shapes." + ) + denominator = valid + (2 * pseudo_count) + with np.errstate(divide="ignore", invalid="ignore"): + fraction = np.divide( + modified + pseudo_count, + denominator, + out=np.zeros_like(modified), + where=denominator > 0, + ) + else: + fraction = np.asarray(vector, dtype=np.float32) + + if expected_length is None: + expected_length = fraction.shape[0] + elif fraction.shape[0] != expected_length: + raise ValueError( + "All regions must have the same length. " + "Pass window_size to enforce length-matched regions." + ) + feature_vectors.append(fraction) + + feature_matrix = np.vstack(feature_vectors).astype(np.float32, copy=False) + return feature_matrix, region_metadata + + +def _pileup_fraction_vector_from_bedmethyl( + *, + bedmethyl_file: str | Path, + motif: str, + regions: str | Path | list[str | Path], + window_size: int | None = None, + single_strand: bool = False, + regions_5to3prime: bool = False, + pseudo_count: float = 1e-3, + quiet: bool = False, + cores: int | None = None, + chunk_size: int = load_processed.DEFAULT_CHUNK_SIZE, +) -> np.ndarray: + modified_vec, valid_vec = load_processed.pileup_vectors_from_bedmethyl( + bedmethyl_file=bedmethyl_file, + motif=motif, + regions=regions, + window_size=window_size, + single_strand=single_strand, + regions_5to3prime=regions_5to3prime, + quiet=quiet, + cores=cores, + chunk_size=chunk_size, + ) + modified = np.asarray(modified_vec, dtype=np.float32) + valid = np.asarray(valid_vec, dtype=np.float32) + if modified.shape != valid.shape: + raise ValueError("Modified and valid vectors must have matching shapes.") + denominator = valid + (2 * pseudo_count) + with np.errstate(divide="ignore", invalid="ignore"): + return np.divide( + modified + pseudo_count, + denominator, + out=np.zeros_like(modified), + where=denominator > 0, + ) + + +def read_mod_fraction_table( + hdf5_file: str | Path, + motifs: Sequence[str], + regions: str | Path | list[str | Path] | None = None, + *, + window_size: int | None = None, + single_strand: bool = False, + sort_by: Sequence[str | tuple[str, str]] | str = ( + "chromosome", + "region_start", + "read_start", + ), + subset_parameters: dict | None = None, + span_full_window: bool = False, + metadata_fields: Sequence[str] | None = DEFAULT_METADATA_FIELDS, +) -> tuple[np.ndarray, list[str], list[dict], dict | None]: + """ + Pull per-read modification fractions from an extract .h5 file. + This is the lightest-weight read-level feature matrix: one column per motif. + + Args: + hdf5_file: path to the .h5 output from parse_bam.extract + motifs: motifs whose modification fractions should be returned + regions: optional region specifier to filter reads + window_size: optional window applied to every region before loading + single_strand: whether to enforce strand agreement with each region + sort_by: forwarded to load_processed.read_vectors_from_hdf5 + subset_parameters: forwarded to utils.random_sample through the loader + span_full_window: only keep reads fully covering the requested window + metadata_fields: metadata columns to capture per read + + Returns: + Tuple containing: + * numpy array of shape (n_reads, len(motifs)) with per-read fractions + * ordered list of feature names + * list of metadata dictionaries aligned to the rows + * regions_dict returned by load_processed.read_vectors_from_hdf5 + """ + + read_records, datasets, regions_dict = load_processed.read_vectors_from_hdf5( + file=hdf5_file, + motifs=list(motifs), + regions=regions, + window_size=window_size, + single_strand=single_strand, + sort_by=list(sort_by) if isinstance(sort_by, (list, tuple)) else [sort_by], + subset_parameters=subset_parameters, + span_full_window=span_full_window, + ) + + dataset_indices = {name: idx for idx, name in enumerate(datasets)} + feature_names = [f"{motif}_mod_fraction" for motif in motifs] + missing_features = [name for name in feature_names if name not in dataset_indices] + if missing_features: + raise ValueError( + "Missing modification fraction columns in HDF5 file: " + f"{', '.join(missing_features)}. " + "Ensure calculate_mod_fractions=True when exporting reads." + ) + + feature_matrix = np.zeros((len(read_records), len(feature_names)), dtype=float) + capture_metadata = metadata_fields is not None + metadata: list[dict] = [] + + for idx, record in enumerate(read_records): + feature_matrix[idx, :] = [ + float(record[dataset_indices[name]]) for name in feature_names + ] + if capture_metadata: + metadata.append( + { + field: record[dataset_indices[field]] + if field in dataset_indices + else None + for field in metadata_fields + } + ) + else: + metadata.append({}) + + return feature_matrix, feature_names, metadata, regions_dict + + +def cluster_features( + feature_matrix: np.ndarray, + *, + method: str = "kmeans", + n_clusters: int = 5, + random_state: int | None = 0, + **kwargs, +) -> tuple[np.ndarray, object]: + """ + Run a simple clustering algorithm on a feature matrix. + + Args: + feature_matrix: 2D array of shape (n_samples, n_features) + method: clustering algorithm to run, currently only 'kmeans' is supported + n_clusters: number of clusters to request from the estimator + random_state: forwarded to the estimator for reproducibility + kwargs: forwarded to the sklearn estimator + + Returns: + Tuple of (cluster_labels, fitted_estimator) + """ + + if feature_matrix.ndim != 2: + raise ValueError("feature_matrix must be 2-dimensional.") + if feature_matrix.shape[0] == 0: + raise ValueError("feature_matrix must contain at least one sample.") + + method = method.lower() + if method != "kmeans": + raise ValueError(f"Unsupported clustering method '{method}'.") + + kmeans_cls = _get_kmeans() + estimator = kmeans_cls( + n_clusters=n_clusters, + random_state=random_state, + **kwargs, + ) + labels = estimator.fit_predict(feature_matrix) + return labels, estimator + + +def _get_kmeans(): + try: + from sklearn.cluster import KMeans + except ( + ImportError + ) as exc: # pragma: no cover - exercised in environments w/o sklearn + raise ImportError( + "scikit-learn is required for k-means clustering. " + "Install it with `pip install scikit-learn`." + ) from exc + return KMeans + + +def _get_xgb_classifier(): + try: + from xgboost import XGBClassifier + except Exception as exc: # pragma: no cover - optional dependency + raise ImportError( + "Install xgboost to use classifier='xgboost'. Try `pip install xgboost`." + ) from exc + return XGBClassifier + + +def _build_dataset_index(dataset_names: Sequence[str]) -> dict[str, int]: + # Map dataset names from the HDF5 file into tuple indices for quick lookup + return {name: idx for idx, name in enumerate(dataset_names)} + + +def _coerce_strand(value: Any) -> str | None: + # Normalize strand values to '+' / '-' / None + if isinstance(value, str): + stripped = value.strip() + if stripped in {"+", "-"}: + return stripped + return None + + +def _should_flip(region_strand: str | None, read_strand: str | None) -> bool: + return ( + region_strand in {"+", "-"} + and read_strand in {"+", "-"} + and region_strand != read_strand + ) + + +def _region_key(record: tuple, idx: dict[str, int]) -> tuple[Any, Any, Any]: + # Defines "same region" for multi-region filtering + chrom = record[idx["chromosome"]] if "chromosome" in idx else None + start = record[idx["region_start"]] if "region_start" in idx else None + end = record[idx["region_end"]] if "region_end" in idx else None + return (chrom, start, end) + + +def _identify_multi_region_reads( + records: Sequence[tuple], idx: dict[str, int] +) -> set[str]: + if "read_name" not in idx: + return set() + regions_by_name: defaultdict[str, set[tuple[Any, Any, Any]]] = defaultdict(set) + for rec in records: + name = rec[idx["read_name"]] + regions_by_name[name].add(_region_key(rec, idx)) + return {name for name, keys in regions_by_name.items() if len(keys) > 1} + + +def _infer_window_span_from_records( + records: Sequence[tuple], + idx: dict[str, int], +) -> int: + if "region_start" not in idx or "region_end" not in idx: + raise ValueError( + "Could not infer window span from records. Pass window_size explicitly." + ) + lengths = [] + for rec in records: + try: + start = int(rec[idx["region_start"]]) + end = int(rec[idx["region_end"]]) + except Exception: + continue + length = end - start + if length > 0: + lengths.append(length) + if not lengths: + raise ValueError( + "Could not infer a default window span from region metadata. " + "Pass window_size explicitly." + ) + return int(min(lengths)) + + +def _infer_window_span_from_metadata( + metadata: Sequence[dict[str, Any]] | None, +) -> int | None: + if metadata is None: + return None + lengths = [] + for row in metadata: + try: + start = int(row.get("region_start")) + end = int(row.get("region_end")) + except Exception: + continue + width = end - start + if width > 0: + lengths.append(width) + if not lengths: + return None + return int(min(lengths)) + + +def _resolve_window_size(window_size: int | None = None) -> int | None: + if window_size is None: + return None + resolved = int(window_size) + if resolved <= 0: + raise ValueError("window_size must be a positive integer when provided.") + return resolved + + +def _window_span_from_size(window_size: int) -> int: + return int(window_size) * 2 + + +def _centered_x_axis(length: int, span_bp: int | None) -> np.ndarray: + if length <= 0: + return np.array([], dtype=float) + if length == 1: + return np.array([0.0], dtype=float) + span = float(span_bp if span_bp is not None else length) + step = span / float(length) + return (np.arange(length, dtype=float) * step) - (span / 2.0) + + +def _smooth_profile_vector( + values: np.ndarray, + *, + smoothing: str | None = None, + smooth_win: int = 21, + smooth_sigma: float = 6.0, +) -> np.ndarray: + arr = np.asarray(values, dtype=float) + if smoothing is None or arr.size < 3: + return arr + mode = smoothing.lower() + if mode not in {"gaussian", "boxcar"}: + raise ValueError("smoothing must be None, 'gaussian', or 'boxcar'.") + + win = int(max(3, smooth_win)) + if win % 2 == 0: + win += 1 + if win >= arr.size: + win = arr.size - 1 if arr.size % 2 == 0 else arr.size + if win < 3: + return arr + + if mode == "gaussian": + radius = win // 2 + x = np.arange(-radius, radius + 1, dtype=float) + sigma = max(float(smooth_sigma), 1e-6) + kernel = np.exp(-0.5 * (x / sigma) ** 2) + else: + kernel = np.ones(win, dtype=float) + kernel = kernel / kernel.sum() + + pad = len(kernel) // 2 + padded = np.pad(arr, (pad, pad), mode="edge") + return np.convolve(padded, kernel, mode="valid") + + +def _infer_shared_window_size( + regions: Sequence[str | Path | Sequence[str | Path]], +) -> int: + """ + Infer one half-window size that is safe across multiple region selectors. + Returns half of the shortest positive region width across all inputs. + """ + widths: list[int] = [] + for region_input in regions: + regions_dict = utils.regions_dict_from_input(region_input, window_size=None) + for region_list in regions_dict.values(): + for start, end, _ in region_list: + width = int(end) - int(start) + if width > 0: + widths.append(width) + if not widths: + raise ValueError("Could not infer shared window size from provided regions.") + shortest_width = int(min(widths)) + if shortest_width < 2: + raise ValueError( + "Could not infer a usable shared window_size because the shortest region " + f"width is {shortest_width} bp." + ) + return shortest_width // 2 + + +def _center_crop_matrix(matrix: np.ndarray, target_width: int) -> np.ndarray: + arr = np.asarray(matrix) + if arr.ndim != 2: + raise ValueError("Expected a 2D matrix for center-cropping.") + width = arr.shape[1] + if width == target_width: + return arr + if width < target_width: + raise ValueError( + f"Cannot center-crop from width {width} to larger target_width {target_width}." + ) + start = (width - target_width) // 2 + end = start + target_width + return arr[:, start:end] + + +def merge_read_window_results( + results: Sequence[ReadWindowExtractionResult], + *, + source_labels: Sequence[str] | None = None, + align: str = "error", +) -> ReadWindowExtractionResult: + """ + Merge multiple ReadWindowExtractionResult objects safely. + + Args: + results: extraction results to concatenate + source_labels: optional labels to append to metadata as ``source_label`` + align: one of: + - 'error': require identical window widths (strict) + - 'center_crop': center-crop all matrices to the smallest width before merging + + Returns: + A merged ReadWindowExtractionResult. + """ + if len(results) == 0: + raise ValueError( + "results must contain at least one ReadWindowExtractionResult." + ) + if source_labels is not None and len(source_labels) != len(results): + raise ValueError("source_labels length must match results length.") + if align not in {"error", "center_crop"}: + raise ValueError("align must be one of {'error', 'center_crop'}.") + + widths = [int(r.data_matrix.shape[1]) for r in results] + unique_widths = sorted(set(widths)) + target_width = min(widths) + if len(unique_widths) > 1 and align == "error": + raise ValueError( + "Read-window widths do not match across results: " + f"{unique_widths}. Use a shared window_size upstream " + "or call merge_read_window_results(..., align='center_crop')." + ) + + data_blocks: list[np.ndarray] = [] + val_blocks: list[np.ndarray] = [] + all_have_val = all(r.val_matrix is not None for r in results) + metadata_merged: list[dict[str, Any]] = [] + datasets_merged: list[str] = [] + + for i, res in enumerate(results): + data = np.asarray(res.data_matrix) + if align == "center_crop": + data = _center_crop_matrix(data, target_width) + data_blocks.append(data) + + if all_have_val and res.val_matrix is not None: + val = np.asarray(res.val_matrix) + if align == "center_crop": + val = _center_crop_matrix(val, target_width) + val_blocks.append(val) + + label = source_labels[i] if source_labels is not None else None + for row in res.metadata: + if label is None: + metadata_merged.append(dict(row)) + else: + metadata_merged.append(dict(row, source_label=label)) + + if not datasets_merged and res.datasets: + datasets_merged = list(res.datasets) + + merged_data = np.vstack(data_blocks) + merged_val = np.vstack(val_blocks) if all_have_val and len(val_blocks) > 0 else None + + return ReadWindowExtractionResult( + data_matrix=merged_data, + val_matrix=merged_val, + metadata=metadata_merged, + datasets=datasets_merged, + regions_dict=None, + ) + + +def _prepare_group_labels( + labels: np.ndarray, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + labels_arr = np.asarray(labels) + codes, uniques = pd.factorize(labels_arr, sort=True) + # Handle NaN/None labels from factorize (-1 code): convert to explicit category name + if np.any(codes < 0): + labels_arr = labels_arr.astype(object) + labels_arr[codes < 0] = "NA" + codes, uniques = pd.factorize(labels_arr, sort=True) + unique_codes = np.unique(codes) + return labels_arr, codes, unique_codes + + +def _resolve_motif_slices( + width: int, + *, + motif_count: int | None = None, + motif_labels: Sequence[str] | None = None, + window_size: int | None = None, + view_window_size: int | None = None, +) -> tuple[int, int]: + window_span = _window_span_from_size(window_size) if window_size else None + view_span = _window_span_from_size(view_window_size) if view_window_size else None + inferred_count: int | None = None + if motif_count is not None: + inferred_count = int(max(1, motif_count)) + elif motif_labels is not None and len(motif_labels) > 0: + inferred_count = int(len(motif_labels)) + elif window_span and window_span > 0 and width % window_span == 0: + inferred_count = width // window_span + elif view_span and view_span > 0 and width % view_span == 0: + inferred_count = width // view_span + n_motifs = inferred_count or 1 + slice_width = width // n_motifs if n_motifs > 0 else width + if slice_width <= 0: + return 1, width + return n_motifs, slice_width + + +def _resolve_raster_site_selection( + site_selection: dict[str, Any] | None, + *, + selection_mode: str, + window_offsets_bp: Sequence[int] | None, + n_windows: int, + min_separation_bp: int, + primary_window_index: int, + panel_widths_bp: Sequence[int], +) -> _RasterSiteSelection: + spec = dict(site_selection or {}) + mode_aliases = { + "cooccurring_regions": "cooccurring", + "cooccurring": "cooccurring", + "fixed_offsets": "fixed_offsets", + "anchor_plus_neighbors": "anchor_plus_neighbors", + } + mode_raw = spec.get("mode", selection_mode) + if mode_raw not in mode_aliases: + raise ValueError( + "site_selection mode must be 'cooccurring', 'fixed_offsets', or 'anchor_plus_neighbors'." + ) + mode = mode_aliases[str(mode_raw)] + + offsets_value = spec.get("window_offsets_bp", window_offsets_bp) + offsets = None + if offsets_value is not None: + offsets = tuple(int(value) for value in offsets_value) + if not offsets: + raise ValueError("window_offsets_bp must contain at least one offset.") + + if mode == "fixed_offsets": + if offsets is None: + raise ValueError( + "window_offsets_bp is required for fixed_offsets site selection." + ) + n_windows_effective = len(offsets) + else: + if offsets is not None: + raise ValueError( + "window_offsets_bp is only valid for fixed_offsets site selection." + ) + n_windows_effective = int(spec.get("n_windows", n_windows)) + if n_windows_effective < 1: + raise ValueError("site_selection n_windows must be >= 1.") + + primary_index = int(spec.get("primary_window_index", primary_window_index)) + if primary_index < 0 or primary_index >= n_windows_effective: + raise ValueError( + f"primary_window_index {primary_index} out of range for {n_windows_effective} windows." + ) + + default_min_distance: int | str = ( + "window_width" if site_selection is not None else min_separation_bp + ) + min_distance_value = spec.get("min_distance_bp", default_min_distance) + if min_distance_value == "window_width": + min_distance = int(max(panel_widths_bp)) + else: + min_distance = int(min_distance_value) + if min_distance < 0: + raise ValueError("site_selection min_distance_bp must be >= 0.") + + max_distance_value = spec.get("max_distance_bp") + max_distance = None if max_distance_value is None else int(max_distance_value) + if max_distance is not None and max_distance < min_distance: + raise ValueError("site_selection max_distance_bp must be >= min_distance_bp.") + + multiplicity = str(spec.get("selection_multiplicity", "one_per_read")) + if multiplicity == "all_valid_sets": + raise NotImplementedError( + "site_selection selection_multiplicity='all_valid_sets' is not implemented yet; use 'one_per_read'." + ) + if multiplicity != "one_per_read": + raise ValueError( + "site_selection selection_multiplicity must be 'one_per_read' or 'all_valid_sets'." + ) + + choose = str(spec.get("choose", "first")) + if choose not in {"first", "random", "longest_span", "shortest_span"}: + raise ValueError( + "site_selection choose must be 'first', 'random', 'longest_span', or 'shortest_span'." + ) + + anchor = dict(spec.get("anchor", {"mode": "first"}) or {"mode": "first"}) + anchor_mode = str(anchor.get("mode", "first")) + if anchor_mode not in {"first", "random", "index"}: + raise ValueError("site_selection anchor mode must be 'first', 'random', or 'index'.") + + strand_relation = str(spec.get("strand_relation", "any")) + if strand_relation not in {"any", "same", "opposite"}: + raise ValueError("site_selection strand_relation must be 'any', 'same', or 'opposite'.") + + orientation = str(spec.get("orientation", "genomic")) + if orientation not in {"genomic", "anchor_strand"}: + raise ValueError("site_selection orientation must be 'genomic' or 'anchor_strand'.") + + exclude = spec.get("exclude") + exclude_dict = dict(exclude) if exclude is not None else None + selection_seed = spec.get("selection_seed") + + return _RasterSiteSelection( + mode=mode, + n_windows=n_windows_effective, + min_distance_bp=min_distance, + max_distance_bp=max_distance, + selection_multiplicity=multiplicity, + choose=choose, + selection_seed=None if selection_seed is None else int(selection_seed), + anchor=anchor, + strand_relation=strand_relation, + exclude=exclude_dict, + orientation=orientation, + window_offsets_bp=offsets, + primary_window_index=primary_index, + ) + + +def _metadata_center_bp(meta_row: dict[str, Any], distance_mode: str) -> float: + if distance_mode == "center": + return float((int(meta_row.get("region_start", 0)) + int(meta_row.get("region_end", 0))) // 2) + return float(int(meta_row.get("read_start", 0))) + + +def _metadata_interval(meta_row: dict[str, Any]) -> tuple[str | None, int | None, int | None]: + chrom = meta_row.get("chromosome") + try: + start = int(meta_row.get("region_start")) + end = int(meta_row.get("region_end")) + except Exception: + return chrom, None, None + return chrom, start, end + + +def _interval_overlaps_any( + chrom: str | None, + start: int | None, + end: int | None, + regions_dict: dict[str, list[tuple[int, int, str]]], +) -> bool: + if chrom is None or start is None or end is None: + return False + for region_start, region_end, _ in regions_dict.get(str(chrom), []): + if max(start, int(region_start)) < min(end, int(region_end)): + return True + return False + + +def _build_raster_site_candidates( + meta: Sequence[dict[str, Any]], + *, + original_indices: np.ndarray, + selector: _RasterSiteSelection, + distance_mode: str, +) -> tuple[list[_RasterSiteCandidate], int]: + excluded_original_rows = set() + excluded_regions = None + if selector.exclude: + excluded_original_rows = { + int(value) for value in selector.exclude.get("row_indices", []) or [] + } + regions = selector.exclude.get("regions") + if regions is not None: + excluded_regions = utils.regions_dict_from_input(regions, window_size=None) + + candidates: list[_RasterSiteCandidate] = [] + excluded_count = 0 + for row_idx, meta_row in enumerate(meta): + original_idx = int(original_indices[row_idx]) + chrom, start, end = _metadata_interval(meta_row) + if original_idx in excluded_original_rows or ( + excluded_regions is not None + and _interval_overlaps_any(chrom, start, end, excluded_regions) + ): + excluded_count += 1 + continue + candidates.append( + _RasterSiteCandidate( + row_idx=row_idx, + center_bp=_metadata_center_bp(meta_row, distance_mode), + chrom=str(chrom) if chrom is not None else None, + start_bp=start, + end_bp=end, + strand=_coerce_strand(meta_row.get("region_strand")), + read_key=(meta_row.get("read_name"), meta_row.get("chromosome")), + ) + ) + return candidates, excluded_count + + +def _strand_relation_ok( + selected: Sequence[_RasterSiteCandidate], + selector: _RasterSiteSelection, +) -> bool: + if selector.strand_relation == "any": + return True + strands = [candidate.strand for candidate in selected] + if any(strand not in {"+", "-"} for strand in strands): + raise ValueError( + "site_selection strand_relation requires '+'/'-' region_strand metadata for all selected sites." + ) + if selector.strand_relation == "same": + return len(set(strands)) == 1 + anchor_strand = strands[selector.primary_window_index] + if len(strands) == 2: + return strands[0] != strands[1] + return any(strand != anchor_strand for strand in strands) + + +def _distances_ok( + selected: Sequence[_RasterSiteCandidate], + selector: _RasterSiteSelection, +) -> bool: + centers = [candidate.center_bp for candidate in selected] + if len(centers) < 2: + return True + for left, right in zip(centers, centers[1:], strict=False): + distance = abs(float(right) - float(left)) + if distance < selector.min_distance_bp: + return False + if selector.max_distance_bp is not None and distance > selector.max_distance_bp: + return False + return True + + +def _apply_anchor_orientation( + selected: Sequence[_RasterSiteCandidate], + selector: _RasterSiteSelection, +) -> list[_RasterSiteCandidate]: + ordered = list(selected) + if ( + selector.orientation == "anchor_strand" + and ordered + and ordered[selector.primary_window_index].strand == "-" + ): + ordered = list(reversed(ordered)) + return ordered + + +def _fixed_offset_sets( + items: Sequence[_RasterSiteCandidate], + selector: _RasterSiteSelection, +) -> list[list[_RasterSiteCandidate]]: + assert selector.window_offsets_bp is not None + out: list[list[_RasterSiteCandidate]] = [] + by_center: defaultdict[float, list[_RasterSiteCandidate]] = defaultdict(list) + for item in items: + by_center[item.center_bp].append(item) + for anchor in items: + selected: list[_RasterSiteCandidate] = [] + used: set[int] = set() + for offset in selector.window_offsets_bp: + matches = by_center.get(anchor.center_bp + float(offset), []) + chosen = next((candidate for candidate in matches if candidate.row_idx not in used), None) + if chosen is None: + selected = [] + break + selected.append(chosen) + used.add(chosen.row_idx) + if selected and _strand_relation_ok(selected, selector): + out.append(_apply_anchor_orientation(selected, selector)) + return out + + +def _anchor_position( + items: Sequence[_RasterSiteCandidate], + selector: _RasterSiteSelection, + rng: np.random.Generator, +) -> int: + mode = str(selector.anchor.get("mode", "first")) + if mode == "first": + return 0 + if mode == "random": + return int(rng.integers(0, len(items))) + index = int(selector.anchor.get("index", 0)) + if index < 0 or index >= len(items): + raise ValueError("site_selection anchor index is out of range for a read.") + return index + + +def _cooccurring_sets( + items: Sequence[_RasterSiteCandidate], + selector: _RasterSiteSelection, + rng: np.random.Generator, +) -> list[list[_RasterSiteCandidate]]: + out: list[list[_RasterSiteCandidate]] = [] + ordered_items = list(items) + if selector.mode == "anchor_plus_neighbors": + anchor_pos = _anchor_position(ordered_items, selector, rng) + anchor = ordered_items[anchor_pos] + combos_source = [ + [anchor, *combo] + for combo in combinations( + [item for idx, item in enumerate(ordered_items) if idx != anchor_pos], + max(0, selector.n_windows - 1), + ) + ] + else: + combos_source = combinations(ordered_items, selector.n_windows) + + for combo in combos_source: + selected = sorted(combo, key=lambda candidate: candidate.center_bp) + if not _distances_ok(selected, selector): + continue + if not _strand_relation_ok(selected, selector): + continue + out.append(_apply_anchor_orientation(selected, selector)) + return out + + +def _choose_site_set( + valid_sets: Sequence[list[_RasterSiteCandidate]], + selector: _RasterSiteSelection, + rng: np.random.Generator, +) -> list[_RasterSiteCandidate] | None: + if not valid_sets: + return None + if selector.choose == "first": + return list(valid_sets[0]) + if selector.choose == "random": + return list(valid_sets[int(rng.integers(0, len(valid_sets)))]) + + def span(site_set: Sequence[_RasterSiteCandidate]) -> float: + centers = [candidate.center_bp for candidate in site_set] + return float(max(centers) - min(centers)) + + if selector.choose == "longest_span": + return list(max(valid_sets, key=span)) + return list(min(valid_sets, key=span)) + + +def _summarize_observed_offsets( + observed_offsets: Sequence[Sequence[float]], +) -> list[dict[str, float | int | None]]: + summary: list[dict[str, float | int | None]] = [] + for values in observed_offsets: + arr = np.asarray(values, dtype=float) + if arr.size == 0: + summary.append( + { + "n": 0, + "min": None, + "median": None, + "max": None, + "unique": 0, + } + ) + continue + summary.append( + { + "n": int(arr.size), + "min": float(np.min(arr)), + "median": float(np.median(arr)), + "max": float(np.max(arr)), + "unique": int(np.unique(arr).size), + } + ) + return summary + + +def _select_raster_site_windows( + meta: Sequence[dict[str, Any]], + *, + original_indices: np.ndarray, + selector: _RasterSiteSelection, + distance_mode: str, + selection_spans_all_windows, +) -> _RasterSiteSelectionResult: + candidates, excluded_count = _build_raster_site_candidates( + meta, + original_indices=original_indices, + selector=selector, + distance_mode=distance_mode, + ) + groups: defaultdict[tuple[Any, Any], list[_RasterSiteCandidate]] = defaultdict(list) + for candidate in candidates: + groups[candidate.read_key].append(candidate) + + rng = np.random.default_rng(selector.selection_seed) + per_window_indices: list[list[int]] = [[] for _ in range(selector.n_windows)] + per_window_centers: list[list[float]] = [[] for _ in range(selector.n_windows)] + selected_read_keys: list[tuple[Any, Any]] = [] + dropped_for_window_span = 0 + observed_offsets: list[list[float]] = [[] for _ in range(selector.n_windows)] + + for read_key, items in groups.items(): + if len(items) < selector.n_windows: + continue + ordered_items = sorted(items, key=lambda candidate: candidate.center_bp) + if selector.mode == "fixed_offsets": + valid_sets = _fixed_offset_sets(ordered_items, selector) + else: + valid_sets = _cooccurring_sets(ordered_items, selector, rng) + chosen = _choose_site_set(valid_sets, selector, rng) + if chosen is None: + continue + candidate_for_span = [ + (candidate.row_idx, candidate.center_bp, window_pos) + for window_pos, candidate in enumerate(chosen) + ] + if not selection_spans_all_windows(candidate_for_span): + dropped_for_window_span += 1 + continue + primary_center = chosen[selector.primary_window_index].center_bp + for window_pos, candidate in enumerate(chosen): + per_window_indices[window_pos].append(int(candidate.row_idx)) + per_window_centers[window_pos].append(float(candidate.center_bp)) + observed_offsets[window_pos].append(float(candidate.center_bp - primary_center)) + selected_read_keys.append(read_key) + + if not all(per_window_indices): + raise ValueError("No read sets found meeting site-selection criteria.") + + stats = { + "rows_are": "reads", + "unique_reads": len(set(selected_read_keys)), + "site_sets": len(selected_read_keys), + "orientation_applied": selector.orientation, + "observed_window_center_offsets_bp": observed_offsets, + "observed_window_center_offsets_summary_bp": _summarize_observed_offsets( + observed_offsets + ), + "site_selection": { + "mode": selector.mode, + "n_windows": selector.n_windows, + "min_distance_bp": selector.min_distance_bp, + "max_distance_bp": selector.max_distance_bp, + "selection_multiplicity": selector.selection_multiplicity, + "choose": selector.choose, + "selection_seed": selector.selection_seed, + "anchor": selector.anchor, + "strand_relation": selector.strand_relation, + "orientation": selector.orientation, + "excluded_sites": excluded_count, + }, + "dropped_for_window_span": dropped_for_window_span, + } + return _RasterSiteSelectionResult( + window_indices=[np.asarray(values, dtype=int) for values in per_window_indices], + panel_centers=[np.asarray(values, dtype=float) for values in per_window_centers], + stats=stats, + ) + + +def _extract_window_from_record( + record: tuple, + idx: dict[str, int], + window_span: int, + orientation_aware: bool, + *, + enforce_thresholded_vectors: bool, + auto_threshold_if_raw: float | None, + notify_auto_threshold, +) -> tuple[np.ndarray, np.ndarray | None] | None: + required = ["read_start", "read_end", "region_start", "region_end", "mod_vector"] + for key in required: + if key not in idx: + raise ValueError(f"read_vectors_from_hdf5 output missing '{key}' dataset.") + + read_start = int(record[idx["read_start"]]) + read_end = int(record[idx["read_end"]]) + region_start = int(record[idx["region_start"]]) + region_end = int(record[idx["region_end"]]) + mod_vector = np.asarray(record[idx["mod_vector"]]) + val_vector = np.asarray(record[idx["val_vector"]]) if "val_vector" in idx else None + + if read_end <= read_start or region_end <= region_start: + return None + + center = (region_start + region_end) // 2 + half = window_span // 2 + # Grab a symmetric window around the region center + window_start = center - half + window_end = window_start + window_span + # Skip reads that do not fully span the requested window + if window_start < read_start or window_end > read_end: + return None + + slice_start = window_start - read_start + slice_end = slice_start + window_span + if slice_start < 0 or slice_end > len(mod_vector): + return None + + mod_window = mod_vector[slice_start:slice_end] + val_window = val_vector[slice_start:slice_end] if val_vector is not None else None + + if enforce_thresholded_vectors and not _is_binary_mod_window( + mod_window=mod_window, + val_window=val_window, + ): + if auto_threshold_if_raw is None: + raise ValueError( + "Loaded extract vectors appear unthresholded (ML/probability values), " + "but thresholded vectors are required. " + "Set ReadWindowExtractionConfig.auto_threshold_if_raw to a value " + "(for example 190) or disable enforcement explicitly." + ) + mod_window = _threshold_mod_window( + mod_window=mod_window, + val_window=val_window, + threshold=auto_threshold_if_raw, + ) + notify_auto_threshold(auto_threshold_if_raw) + + if orientation_aware: + region_strand = ( + _coerce_strand(record[idx["region_strand"]]) + if "region_strand" in idx + else None + ) + read_strand = _coerce_strand(record[idx["strand"]]) if "strand" in idx else None + # If read/reference strands disagree, flip to align everything 5'->3' + if _should_flip(region_strand, read_strand): + mod_window = np.flip(mod_window) + if val_window is not None: + val_window = np.flip(val_window) + + return mod_window, val_window + + +def _resolve_raw_vector_threshold(threshold: float | int | None) -> float | None: + if threshold is None: + return None + try: + numeric = float(threshold) + except Exception as exc: + raise ValueError("auto_threshold_if_raw must be numeric or None.") from exc + if numeric <= 0: + raise ValueError("auto_threshold_if_raw must be > 0 when provided.") + return float(utils.adjust_threshold(numeric, quiet=True)) + + +def _is_binary_mod_window( + mod_window: np.ndarray, val_window: np.ndarray | None +) -> bool: + arr = np.asarray(mod_window) + if arr.size == 0: + return True + if arr.dtype == np.bool_: + return True + if val_window is not None: + valid = np.asarray(val_window).astype(bool) + if valid.shape == arr.shape: + arr = arr[valid] + if arr.size == 0: + return True + unique_vals = np.unique(arr) + return bool(np.all(np.isin(unique_vals, [0, 1]))) + + +def _threshold_mod_window( + *, + mod_window: np.ndarray, + val_window: np.ndarray | None, + threshold: float, +) -> np.ndarray: + arr = np.asarray(mod_window, dtype=float) + if val_window is None: + return (arr >= threshold).astype(float) + valid = np.asarray(val_window).astype(bool) + out = np.zeros(arr.shape, dtype=float) + out[valid] = (arr[valid] >= threshold).astype(float) + return out + + +def build_multimotif_read_windows( + hdf5_file: str | Path, + motifs: Sequence[str], + regions: str | Path | list[str | Path] | None = None, + *, + window_size: int | None = None, + orientation_aware: bool = True, + single_strand: bool = False, + subset_parameters: dict | None = None, + span_full_window: bool = False, + require_all_motifs: bool = True, + enforce_thresholded_vectors: bool = True, + auto_threshold_if_raw: float | int | None = 190, + warn_on_auto_threshold: bool = True, +) -> ReadWindowExtractionResult: + """ + Group per-motif rows by read and return a combined window per read, concatenating motifs. + + Each requested motif contributes a centered window of length `2 * window_size` + (or inferred full span when window_size is None); missing motifs are filled with zeros. + Windows are concatenated in the order provided by `motifs`. + + Args: + hdf5_file: path to extract .h5 file + motifs: motifs to include (order matters for concatenation) + regions: optional region filter + window_size: half-window in bp around region center to extract per motif. + Full extracted span is ``2 * window_size``. + If None, full span is inferred from the shortest selected region length. + orientation_aware: flip windows if read strand != region strand + single_strand: passed to loader + subset_parameters: passed to loader for subsetting + span_full_window: passed to loader (if True, only reads spanning the region are loaded) + require_all_motifs: if True, drop reads that are missing any requested motif + enforce_thresholded_vectors: require thresholded/binary vectors for clustering-style workflows + auto_threshold_if_raw: threshold applied when raw probability vectors are detected + (default 190 interpreted in 0-255 space) + warn_on_auto_threshold: emit a warning when auto-thresholding is applied + + Returns: + ReadWindowExtractionResult with data_matrix shape + ``(n_reads, len(motifs) * full_window_span_bp)``. + and val_matrix if available, metadata per combined read, and datasets info. + """ + + # Load all per-motif rows + read_tuples, dataset_names, regions_dict = load_processed.read_vectors_from_hdf5( + file=hdf5_file, + motifs=list(motifs), + regions=regions, + window_size=None, # we handle windowing here + single_strand=single_strand, + subset_parameters=subset_parameters, + span_full_window=span_full_window, + ) + idx = _build_dataset_index(dataset_names) + + # Group rows by read+region key + groups: defaultdict[tuple[Any, Any, Any, Any, Any], dict[str, tuple]] = defaultdict( + dict + ) + for rec in read_tuples: + key = ( + rec[idx.get("read_name")], + rec[idx.get("chromosome")], + rec[idx.get("region_start")], + rec[idx.get("region_end")], + rec[idx.get("region_strand")] if "region_strand" in idx else None, + ) + motif = rec[idx.get("motif")] + groups[key][motif] = rec + + combined_windows: list[np.ndarray] = [] + combined_vals: list[np.ndarray] = [] + metadata: list[dict[str, Any]] = [] + has_val = "val_vector" in idx + + requested_window_size = _resolve_window_size(window_size=window_size) + requested_window_span = ( + _window_span_from_size(requested_window_size) + if requested_window_size is not None + else None + ) + effective_window_span = ( + requested_window_span + if requested_window_span is not None + else _infer_window_span_from_records(read_tuples, idx) + ) + resolved_threshold = _resolve_raw_vector_threshold(auto_threshold_if_raw) + auto_threshold_notified = False + + def _notify_auto_threshold(thresh_scaled: float) -> None: + nonlocal auto_threshold_notified + if auto_threshold_notified or not warn_on_auto_threshold: + return + auto_threshold_notified = True + threshold_255 = int(round(thresh_scaled * 255)) + warnings.warn( + "Detected unthresholded extract vectors; applying automatic thresholding " + f"at {thresh_scaled:.6f} (~{threshold_255}/255) for clustering/classification.", + RuntimeWarning, + stacklevel=2, + ) + + for key, motif_map in groups.items(): + motif_windows = [] + motif_val_windows = [] + motifs_present = [] + representative_record: tuple | None = None + for motif in motifs: + if motif in motif_map: + if representative_record is None: + representative_record = motif_map[motif] + extracted = _extract_window_from_record( + motif_map[motif], + idx, + effective_window_span, + orientation_aware, + enforce_thresholded_vectors=enforce_thresholded_vectors, + auto_threshold_if_raw=resolved_threshold, + notify_auto_threshold=_notify_auto_threshold, + ) + if extracted is None: + # If window cannot be extracted, treat as missing + motif_windows.append(np.zeros(effective_window_span, dtype=float)) + motif_val_windows.append( + np.zeros(effective_window_span, dtype=float) + if has_val + else None + ) + else: + mw, vw = extracted + motif_windows.append(np.asarray(mw, dtype=float)) + motif_val_windows.append( + np.asarray(vw, dtype=float) if vw is not None else None + ) + motifs_present.append(motif) + else: + motif_windows.append(np.zeros(effective_window_span, dtype=float)) + motif_val_windows.append( + np.zeros(effective_window_span, dtype=float) if has_val else None + ) + + if require_all_motifs and len(motifs_present) < len(motifs): + continue + + combined_windows.append(np.concatenate(motif_windows, axis=0)) + if has_val: + combined_vals.append( + np.concatenate( + [ + vw + if vw is not None + else np.zeros(effective_window_span, dtype=float) + for vw in motif_val_windows + ], + axis=0, + ) + ) + metadata.append( + { + "read_name": key[0], + "chromosome": key[1], + "read_start": ( + int(representative_record[idx["read_start"]]) + if representative_record is not None and "read_start" in idx + else None + ), + "read_end": ( + int(representative_record[idx["read_end"]]) + if representative_record is not None and "read_end" in idx + else None + ), + "read_length": ( + int(representative_record[idx["read_length"]]) + if representative_record is not None and "read_length" in idx + else None + ), + "region_start": key[2], + "region_end": key[3], + "region_strand": key[4], + "motifs_present": motifs_present, + } + ) + + if not combined_windows: + raise ValueError( + "No reads produced combined motif windows; check inputs and window_size." + ) + + data_matrix = np.vstack(combined_windows) + val_matrix = np.vstack(combined_vals) if has_val and combined_vals else None + + return ReadWindowExtractionResult( + data_matrix=data_matrix, + val_matrix=val_matrix, + metadata=metadata, + datasets=list(dataset_names), + regions_dict=regions_dict, + ) + + +def extract_read_windows( + hdf5_file: str | Path, + motifs: Sequence[str], + regions: str | Path | list[str | Path] | None = None, + *, + config: ReadWindowExtractionConfig | None = None, + window_size: int | None = None, + single_strand: bool = False, + subset_parameters: dict | None = None, + span_full_window: bool = False, + quiet: bool = True, +) -> ReadWindowExtractionResult: + """ + Extract fixed-length windows from single-read vectors, optionally flipping reads + to align with the reference strand. + + Args: + hdf5_file: path to the extract .h5 file + motifs: motifs to pull from the file + regions: optional region specifier + config: controls windowing, orientation handling, and raw-vector threshold enforcement + window_size: half-window override in bp (full span = 2 * window_size); + when omitted, falls back to config.window_size. + When omitted in both places, full span is inferred from the shortest selected region length. + + Returns: + ReadWindowExtractionResult containing thresholded/binary mod windows by default, + plus val matrices and metadata. + """ + + cfg = config or ReadWindowExtractionConfig() + requested_window_size = _resolve_window_size( + window_size=window_size if window_size is not None else cfg.window_size, + ) + requested_window_span = ( + _window_span_from_size(requested_window_size) + if requested_window_size is not None + else None + ) + effective_window_span = requested_window_span + resolved_threshold = _resolve_raw_vector_threshold(cfg.auto_threshold_if_raw) + auto_threshold_notified = False + + def _notify_auto_threshold(thresh_scaled: float) -> None: + nonlocal auto_threshold_notified + if auto_threshold_notified or not cfg.warn_on_auto_threshold: + return + auto_threshold_notified = True + threshold_255 = int(round(thresh_scaled * 255)) + warnings.warn( + "Detected unthresholded extract vectors; applying automatic thresholding " + f"at {thresh_scaled:.6f} (~{threshold_255}/255) for clustering/classification.", + RuntimeWarning, + stacklevel=2, + ) + + # Load all requested reads/vectors from extract output + read_tuples, dataset_names, regions_dict = load_processed.read_vectors_from_hdf5( + file=hdf5_file, + motifs=list(motifs), + regions=regions, + window_size=None, + single_strand=single_strand, + subset_parameters=subset_parameters, + span_full_window=span_full_window, + ) + idx = _build_dataset_index(dataset_names) + if effective_window_span is None: + effective_window_span = _infer_window_span_from_records(read_tuples, idx) + effective_window_span = int(effective_window_span) + + drop_names: set[str] = set() + if cfg.filter_multi_region_reads: + drop_names = _identify_multi_region_reads(read_tuples, idx) + + matrices: list[np.ndarray] = [] + val_matrices: list[np.ndarray] = [] + metadata: list[dict[str, Any]] = [] + has_val = "val_vector" in idx + + for rec in read_tuples: + read_name = rec[idx["read_name"]] if "read_name" in idx else None + if read_name in drop_names: + continue + + extracted = _extract_window_from_record( + rec, + idx, + effective_window_span, + cfg.orientation_aware, + enforce_thresholded_vectors=cfg.enforce_thresholded_vectors, + auto_threshold_if_raw=resolved_threshold, + notify_auto_threshold=_notify_auto_threshold, + ) + if extracted is None: + continue + + mod_window, val_window = extracted + matrices.append(mod_window) + if has_val and val_window is not None: + val_matrices.append(val_window) + + # Pull a small set of fields that are helpful for QC/relabelling + meta_fields = [ + "read_name", + "chromosome", + "read_start", + "read_end", + "read_length", + "region_start", + "region_end", + "region_strand", + "strand", + "motif", + ] + metadata.append( + {field: rec[idx[field]] if field in idx else None for field in meta_fields} + ) + + if not matrices: + raise ValueError("No reads produced a full window; try reducing window_size.") + + data_matrix = np.vstack([row.astype(float) for row in matrices]) + val_matrix = ( + np.vstack([row.astype(float) for row in val_matrices]) + if has_val and val_matrices + else None + ) + + return ReadWindowExtractionResult( + data_matrix=data_matrix, + val_matrix=val_matrix, + metadata=metadata, + datasets=list(dataset_names), + regions_dict=regions_dict, + ) + + +def compute_autocorrelation_feature(vec: np.ndarray, lag: int) -> float: + # Normalized autocorrelation at a given lag (handles short/flat vectors) + arr = np.asarray(vec, dtype=float) + if arr.size <= abs(lag): + return 0.0 + centered = arr - arr.mean() + denom = np.dot(centered, centered) + if denom == 0: + return 0.0 + if lag >= 0: + return float(np.dot(centered[:-lag], centered[lag:]) / denom) + return float(np.dot(centered[-lag:], centered[:lag]) / denom) + + +def _merge_feature_spec(feature_spec: dict[str, Any] | None) -> dict[str, Any]: + spec = { + key: (dict(value) if isinstance(value, dict) else value) + for key, value in DEFAULT_RICH_FEATURE_SPEC.items() + } + if feature_spec: + for key, value in feature_spec.items(): + if isinstance(value, dict) and isinstance(spec.get(key), dict): + merged = dict(spec[key]) + merged.update(value) + spec[key] = merged + else: + spec[key] = value + return spec + + +def _feature_enabled(spec: dict[str, Any], key: str) -> bool: + value = spec.get(key, {}) + if isinstance(value, dict): + return bool(value.get("enabled", False)) + return bool(value) + + +def _window_mean(matrix: np.ndarray, center: int, start: int, end: int) -> np.ndarray: + start_idx = max(0, center + int(start)) + end_idx = min(matrix.shape[1], center + int(end)) + if end_idx <= start_idx: + return np.zeros(matrix.shape[0], dtype=float) + return matrix[:, start_idx:end_idx].mean(axis=1) + + +def _safe_ratio(numerator: np.ndarray, denominator: np.ndarray) -> np.ndarray: + return np.divide( + numerator, + denominator, + out=np.zeros_like(numerator, dtype=float), + where=np.abs(denominator) > 1e-12, + ) + + +def _fft_period_power(matrix: np.ndarray, period_bp: float) -> np.ndarray: + if matrix.shape[1] < 3 or period_bp <= 0: + return np.zeros(matrix.shape[0], dtype=float) + centered = matrix - matrix.mean(axis=1, keepdims=True) + power = np.abs(np.fft.rfft(centered, axis=1)) ** 2 + freqs = np.fft.rfftfreq(matrix.shape[1], d=1.0) + target = 1.0 / period_bp + idx = int(np.argmin(np.abs(freqs - target))) + total = power[:, 1:].sum(axis=1) + if idx == 0: + return np.zeros(matrix.shape[0], dtype=float) + return _safe_ratio(power[:, idx], total) + + +def _append_feature( + columns: list[np.ndarray], + names: list[str], + rows: list[dict[str, Any]] | None, + values: np.ndarray, + name: str, + *, + family: str, + motif: str, + scope: str, + window: str = "full", +) -> None: + arr = np.asarray(values, dtype=float) + if arr.ndim == 1: + arr = arr[:, None] + columns.append(arr) + force_index = name == "pca" or name.endswith("__pca") + if arr.shape[1] == 1 and not force_index: + names.append(name) + if rows is not None: + rows.append( + { + "feature_name": name, + "family": family, + "motif": motif, + "scope": scope, + "window": window, + } + ) + else: + for i in range(arr.shape[1]): + col_name = f"{name}_{i}" + names.append(col_name) + if rows is not None: + rows.append( + { + "feature_name": col_name, + "family": family, + "motif": motif, + "scope": scope, + "window": window, + } + ) + + +def _motif_views( + data_matrix: np.ndarray, + val_matrix: np.ndarray | None, + *, + motif_count: int, + motif_labels: Sequence[str] | None, +) -> list[tuple[str, np.ndarray, np.ndarray | None]]: + motif_count = max(1, int(motif_count)) + if data_matrix.shape[1] % motif_count != 0: + raise ValueError( + "data_matrix width must be divisible by motif_count for motif-specific features." + ) + width = data_matrix.shape[1] // motif_count + labels = ( + list(motif_labels) + if motif_labels is not None + else [f"motif_{i}" for i in range(motif_count)] + ) + if len(labels) != motif_count: + raise ValueError("motif_labels length must match motif_count.") + views = [] + for i, label in enumerate(labels): + sl = slice(i * width, (i + 1) * width) + views.append( + ( + str(label), + data_matrix[:, sl], + val_matrix[:, sl] if val_matrix is not None else None, + ) + ) + return views + + +def _append_rich_features_for_view( + columns: list[np.ndarray], + names: list[str], + rows: list[dict[str, Any]] | None, + matrix: np.ndarray, + val_matrix: np.ndarray | None, + spec: dict[str, Any], + *, + prefix: str, + motif: str, + scope: str, + peak_prominence: float, +) -> None: + n_reads, width = matrix.shape + center = width // 2 + + if _feature_enabled(spec, "summary"): + q25 = np.percentile(matrix, 25, axis=1) + q75 = np.percentile(matrix, 75, axis=1) + for label, values in [ + ("mean", matrix.mean(axis=1)), + ("var", matrix.var(axis=1)), + ("median", np.median(matrix, axis=1)), + ("q25", q25), + ("q75", q75), + ("iqr", q75 - q25), + ]: + _append_feature( + columns, + names, + rows, + values, + f"{prefix}__{label}", + family="summary", + motif=motif, + scope=scope, + ) + if val_matrix is not None: + valid_sum = val_matrix.sum(axis=1) + frac = _safe_ratio(matrix.sum(axis=1), valid_sum) + _append_feature( + columns, + names, + rows, + frac, + f"{prefix}__mod_fraction", + family="summary", + motif=motif, + scope=scope, + ) + + if _feature_enabled(spec, "densities"): + windows = spec["densities"].get("windows", DEFAULT_DENSITY_WINDOWS) + for label, start, end in windows: + _append_feature( + columns, + names, + rows, + _window_mean(matrix, center, start, end), + f"{prefix}__{label}", + family="density", + motif=motif, + scope=scope, + window=f"{start}..{end}", + ) + + if _feature_enabled(spec, "asymmetry"): + for span in spec["asymmetry"].get("spans", (100, 300, 500)): + left = _window_mean(matrix, center, -int(span), 0) + right = _window_mean(matrix, center, 0, int(span)) + _append_feature( + columns, + names, + rows, + right - left, + f"{prefix}__right_minus_left_pm{span}", + family="asymmetry", + motif=motif, + scope=scope, + window=f"-{span}..{span}", + ) + + if _feature_enabled(spec, "center_edge"): + center_bp = int(spec["center_edge"].get("center_bp", 100)) + edge_bp = int(spec["center_edge"].get("edge_bp", 200)) + center_vals = _window_mean(matrix, center, -center_bp, center_bp) + left_edge = _window_mean(matrix, center, -width // 2, -width // 2 + edge_bp) + right_edge = _window_mean(matrix, center, width // 2 - edge_bp, width // 2) + edge_vals = (left_edge + right_edge) / 2.0 + _append_feature( + columns, + names, + rows, + center_vals - edge_vals, + f"{prefix}__center_minus_edge", + family="center_edge", + motif=motif, + scope=scope, + window=f"center_pm{center_bp}_edge{edge_bp}", + ) + _append_feature( + columns, + names, + rows, + _safe_ratio(center_vals, edge_vals), + f"{prefix}__center_edge_ratio", + family="center_edge", + motif=motif, + scope=scope, + window=f"center_pm{center_bp}_edge{edge_bp}", + ) + + if _feature_enabled(spec, "autocorr"): + for lag in spec["autocorr"].get("lags", DEFAULT_AUTOCORR_LAGS): + values = np.array([compute_autocorrelation_feature(row, int(lag)) for row in matrix]) + _append_feature( + columns, + names, + rows, + values, + f"{prefix}__autocorr_{lag}", + family="autocorr", + motif=motif, + scope=scope, + ) + + if _feature_enabled(spec, "fft"): + for period in spec["fft"].get("periods_bp", (10, 50, 100, 150, 200)): + _append_feature( + columns, + names, + rows, + _fft_period_power(matrix, float(period)), + f"{prefix}__fft_power_period_{period}bp", + family="fft", + motif=motif, + scope=scope, + ) + + if _feature_enabled(spec, "peaks") and find_peaks is not None: + prominence = float(spec["peaks"].get("prominence", peak_prominence)) + peak_counts = np.zeros(n_reads, dtype=float) + peak_prominences = np.zeros(n_reads, dtype=float) + for row_idx, row in enumerate(matrix): + peaks, props = find_peaks(row, prominence=prominence) + peak_counts[row_idx] = len(peaks) + if peaks.size > 0: + peak_prominences[row_idx] = float(np.mean(props["prominences"])) + _append_feature( + columns, + names, + rows, + peak_counts, + f"{prefix}__peak_count", + family="peaks", + motif=motif, + scope=scope, + ) + _append_feature( + columns, + names, + rows, + peak_prominences, + f"{prefix}__peak_prominence", + family="peaks", + motif=motif, + scope=scope, + ) + + +def read_window_feature_matrix( + result: ReadWindowExtractionResult, + *, + n_pca: int = 6, + random_state: int = 42, + autocorr_lags: Sequence[int] = DEFAULT_AUTOCORR_LAGS, + density_windows: Sequence[tuple[str, int, int]] = DEFAULT_DENSITY_WINDOWS, + peak_prominence: float = 0.005, + use_peak_features: bool = False, + require_nonzero_valid: bool = False, + min_valid_fraction: float = 0.0, + feature_spec: dict[str, Any] | None = None, + motif_count: int | None = None, + motif_labels: Sequence[str] | None = None, + scale_features: bool = False, + scaling_method: str = "standard", + family_weighting: str | None = None, + family_weights: dict[str, float] | None = None, + unscaled_families: Sequence[str] | None = None, + return_feature_table: bool = False, + return_scale_table: bool = False, +) -> ( + tuple[np.ndarray, list[str]] + | tuple[np.ndarray, list[str], pd.DataFrame] + | tuple[np.ndarray, list[str], pd.DataFrame, pd.DataFrame] +): + """ + Convert read windows into an augmented feature matrix including PCA components, + autocorrelation, density summaries, and basic statistics. + + Set use_peak_features=True to append peak counts/prominences (requires SciPy). + If require_nonzero_valid is True, rows with no valid sites (or below min_valid_fraction) + are dropped before feature computation (requires val_matrix to be present). + Set scale_features=True to return a scaled matrix suitable for distance-based + methods such as k-means. Use return_scale_table=True to inspect the scaling. + """ + + data_matrix = result.data_matrix.astype(float, copy=False) + val_matrix = result.val_matrix + + if require_nonzero_valid and val_matrix is not None: + valid_sums = val_matrix.sum(axis=1) + mask = valid_sums > 0 + if min_valid_fraction > 0 and val_matrix.shape[1] > 0: + mask &= (valid_sums / val_matrix.shape[1]) >= min_valid_fraction + data_matrix = data_matrix[mask] + val_matrix = val_matrix[mask] + if data_matrix.size == 0: + raise ValueError("No rows remaining after filtering for valid sites.") + + n_reads, window_size = data_matrix.shape + center = window_size // 2 + + columns: list[np.ndarray] = [] + names: list[str] = [] + build_feature_table = return_feature_table or scale_features + feature_rows: list[dict[str, Any]] | None = [] if build_feature_table else None + + if feature_spec is not None: + spec = _merge_feature_spec(feature_spec) + spec_motif_count = int(motif_count or spec.get("motif_count") or 1) + spec_motif_labels = motif_labels or spec.get("motif_labels") + views = _motif_views( + data_matrix, + val_matrix, + motif_count=spec_motif_count, + motif_labels=spec_motif_labels, + ) + motif_mode = str(spec.get("motif_mode", "per_motif")) + include_pooled = bool(spec.get("pooled", motif_mode in {"pooled", "both"})) + + pca_spec = spec.get("pca", {}) + if isinstance(pca_spec, dict) and pca_spec.get("enabled", False): + pca_scope = str(pca_spec.get("scope", "pooled")) + pca_n = int(pca_spec.get("n_components", n_pca)) + if pca_scope == "per_motif": + pca_views = views + else: + pca_views = [("pooled", data_matrix, val_matrix)] + for label, matrix, _view_val in pca_views: + n_components = min(pca_n, n_reads, matrix.shape[1]) + if n_components <= 0: + continue + from sklearn.decomposition import PCA + + pca = PCA(n_components=n_components, random_state=random_state) + pca_vals = pca.fit_transform(matrix) + _append_feature( + columns, + names, + feature_rows, + pca_vals, + f"{label}__pca", + family="pca", + motif=label, + scope=pca_scope, + ) + + if motif_mode in {"per_motif", "both"}: + for label, matrix, view_val in views: + _append_rich_features_for_view( + columns, + names, + feature_rows, + matrix, + view_val, + spec, + prefix=label, + motif=label, + scope="per_motif", + peak_prominence=peak_prominence, + ) + + if include_pooled: + _append_rich_features_for_view( + columns, + names, + feature_rows, + data_matrix, + val_matrix, + spec, + prefix="pooled", + motif="pooled", + scope="pooled", + peak_prominence=peak_prominence, + ) + + if ( + spec_motif_count > 1 + and isinstance(spec.get("cross_motif"), dict) + and spec["cross_motif"].get("enabled", False) + ): + for (left_label, left, _), (right_label, right, _) in combinations(views, 2): + left_mean = left.mean(axis=1) + right_mean = right.mean(axis=1) + _append_feature( + columns, + names, + feature_rows, + left_mean - right_mean, + f"{left_label}_minus_{right_label}__mean", + family="cross_motif", + motif=f"{left_label}|{right_label}", + scope="cross_motif", + ) + corrs = np.zeros(n_reads, dtype=float) + for row_idx in range(n_reads): + if np.std(left[row_idx]) > 0 and np.std(right[row_idx]) > 0: + corrs[row_idx] = float(np.corrcoef(left[row_idx], right[row_idx])[0, 1]) + _append_feature( + columns, + names, + feature_rows, + corrs, + f"{left_label}_{right_label}__correlation", + family="cross_motif", + motif=f"{left_label}|{right_label}", + scope="cross_motif", + ) + + if not columns: + raise ValueError("feature_spec produced no features.") + feature_matrix = np.hstack(columns) + table = pd.DataFrame(feature_rows) + if scale_features: + feature_matrix, scale_table = scale_feature_matrix( + feature_matrix, + names, + feature_table=table, + method=scaling_method, + family_weighting=family_weighting, + family_weights=family_weights, + unscaled_families=unscaled_families, + ) + if return_feature_table or return_scale_table: + if return_scale_table: + return feature_matrix, names, table, scale_table + return feature_matrix, names, table + if return_feature_table: + return feature_matrix, names, table + return feature_matrix, names + + n_components = min(n_pca, n_reads, window_size) + if n_components > 0: + from sklearn.decomposition import PCA + + pca = PCA(n_components=n_components, random_state=random_state) + pca_vals = pca.fit_transform(data_matrix) + _append_feature( + columns, + names, + feature_rows, + pca_vals, + "pca", + family="pca", + motif="pooled", + scope="legacy_pooled", + ) + + # Precompute cumulative sums to accelerate density windows + cumsum = np.cumsum(data_matrix, axis=1) + cumsum = np.pad(cumsum, ((0, 0), (1, 0)), mode="constant", constant_values=0) + + for lag in autocorr_lags: + values = np.array( + [compute_autocorrelation_feature(row, lag) for row in data_matrix] + ) + _append_feature( + columns, + names, + feature_rows, + values, + f"autocorr_{lag}", + family="autocorr", + motif="pooled", + scope="legacy_pooled", + ) + + for label, start, end in density_windows: + start_idx = max(0, center + start) + end_idx = min(window_size, center + end) + if end_idx <= start_idx: + values = np.zeros(n_reads) + else: + length = end_idx - start_idx + window_sum = cumsum[:, end_idx] - cumsum[:, start_idx] + values = window_sum / length + _append_feature( + columns, + names, + feature_rows, + values, + label, + family="density", + motif="pooled", + scope="legacy_pooled", + window=f"{start}..{end}", + ) + + global_mean = data_matrix.mean(axis=1) + global_var = data_matrix.var(axis=1) + global_median = np.median(data_matrix, axis=1) + q25 = np.percentile(data_matrix, 25, axis=1) + q75 = np.percentile(data_matrix, 75, axis=1) + + for label, values in [ + ("global_mean", global_mean), + ("global_var", global_var), + ("global_median", global_median), + ("q25", q25), + ("q75", q75), + ("iqr", q75 - q25), + ]: + _append_feature( + columns, + names, + feature_rows, + values, + label, + family="summary", + motif="pooled", + scope="legacy_pooled", + ) + + if val_matrix is not None: + valid_sum = val_matrix.sum(axis=1) + with np.errstate(divide="ignore", invalid="ignore"): + frac = np.divide( + data_matrix.sum(axis=1), + valid_sum, + out=np.zeros_like(valid_sum), + where=valid_sum > 0, + ) + _append_feature( + columns, + names, + feature_rows, + frac, + "global_mod_fraction", + family="summary", + motif="pooled", + scope="legacy_pooled", + ) + + if use_peak_features and find_peaks is not None: # pragma: no branch + peak_counts = [] + peak_prominences = [] + for row in data_matrix: + peaks, props = find_peaks(row, prominence=peak_prominence) + peak_counts.append(len(peaks)) + if peaks.size > 0: + peak_prominences.append(float(np.mean(props["prominences"]))) + else: + peak_prominences.append(0.0) + _append_feature( + columns, + names, + feature_rows, + np.array(peak_counts), + "peak_count", + family="peaks", + motif="pooled", + scope="legacy_pooled", + ) + _append_feature( + columns, + names, + feature_rows, + np.array(peak_prominences), + "peak_prominence", + family="peaks", + motif="pooled", + scope="legacy_pooled", + ) + + feature_matrix = np.hstack(columns) + table = pd.DataFrame(feature_rows) + if scale_features: + feature_matrix, scale_table = scale_feature_matrix( + feature_matrix, + names, + feature_table=table, + method=scaling_method, + family_weighting=family_weighting, + family_weights=family_weights, + unscaled_families=unscaled_families, + ) + if return_scale_table: + return feature_matrix, names, table, scale_table + if return_feature_table: + return feature_matrix, names, table + return feature_matrix, names + + +def summarize_feature_matrix( + feature_matrix: np.ndarray, + feature_names: Sequence[str], + *, + feature_table: pd.DataFrame | None = None, + labels: Sequence[Any] | None = None, + top_n_variable: int = 12, +) -> dict[str, Any]: + """Summarize feature matrix dimensions, missingness, variance, and optional labels.""" + + X = np.asarray(feature_matrix, dtype=float) + if X.ndim != 2: + raise ValueError("feature_matrix must be 2-dimensional.") + if X.shape[1] != len(feature_names): + raise ValueError("feature_names length must match feature_matrix width.") + + variance = np.nanvar(X, axis=0) + missing_fraction = np.isnan(X).mean(axis=0) + order = np.argsort(variance)[::-1][:top_n_variable] + variable = pd.DataFrame( + { + "feature_name": [feature_names[i] for i in order], + "variance": variance[order], + "missing_fraction": missing_fraction[order], + } + ) + summary: dict[str, Any] = { + "n_reads": int(X.shape[0]), + "n_features": int(X.shape[1]), + "missing_values": int(np.isnan(X).sum()), + "zero_variance_features": int(np.sum(variance <= 1e-12)), + "top_variable_features": variable, + } + if feature_table is not None and not feature_table.empty: + summary["feature_counts_by_family"] = ( + feature_table.groupby("family", dropna=False) + .size() + .rename("n_features") + .reset_index() + .sort_values("n_features", ascending=False) + ) + summary["feature_counts_by_motif"] = ( + feature_table.groupby("motif", dropna=False) + .size() + .rename("n_features") + .reset_index() + .sort_values("n_features", ascending=False) + ) + if labels is not None: + summary["label_counts"] = pd.Series(labels).value_counts(dropna=False).rename("n") + return summary + + +def scale_feature_matrix( + feature_matrix: np.ndarray, + feature_names: Sequence[str], + *, + feature_table: pd.DataFrame | None = None, + method: str = "standard", + family_weighting: str | None = None, + family_weights: dict[str, float] | None = None, + unscaled_families: Sequence[str] | None = None, + return_scaler: bool = False, +) -> tuple[np.ndarray, pd.DataFrame] | tuple[np.ndarray, pd.DataFrame, Any]: + """ + Scale engineered features for distance-based methods. + + K-means, Ward agglomerative clustering, and Euclidean nearest-neighbor views are + sensitive to feature scale. The default "standard" method centers each feature + and scales it to unit variance. "robust" uses median/IQR scaling for heavier tails. + If feature_table is provided, family_weighting="equal_family" downweights each + column by sqrt(number of scaled columns in its family) so families with many + columns do not dominate Euclidean distance solely by column count. + """ + + X = np.asarray(feature_matrix, dtype=float) + if X.ndim != 2: + raise ValueError("feature_matrix must be 2-dimensional.") + if X.shape[1] != len(feature_names): + raise ValueError("feature_names length must match feature_matrix width.") + + method_norm = method.lower() + unscaled_family_set = set(unscaled_families or ()) + families = pd.Series(["feature"] * X.shape[1], dtype=object) + if feature_table is not None and not feature_table.empty: + if "feature_name" not in feature_table.columns or "family" not in feature_table.columns: + raise ValueError("feature_table must include feature_name and family columns.") + family_map = feature_table.set_index("feature_name")["family"].to_dict() + families = pd.Series( + [family_map.get(name, "feature") for name in feature_names], + dtype=object, + ) + scaled_mask = ~families.isin(unscaled_family_set).to_numpy() + + if method_norm == "standard": + from sklearn.preprocessing import StandardScaler + + scaler = StandardScaler() + elif method_norm == "robust": + from sklearn.preprocessing import RobustScaler + + scaler = RobustScaler(quantile_range=(25.0, 75.0)) + elif method_norm in {"none", "identity"}: + scaled = X.copy() + table = pd.DataFrame( + { + "feature_name": list(feature_names), + "scaling_method": "none", + "family": families.to_numpy(), + "family_weight": np.ones(X.shape[1], dtype=float), + "center": np.zeros(X.shape[1], dtype=float), + "scale": np.ones(X.shape[1], dtype=float), + "raw_mean": np.nanmean(X, axis=0), + "raw_std": np.nanstd(X, axis=0), + } + ) + if return_scaler: + return scaled, table, None + return scaled, table + else: + raise ValueError("method must be 'standard', 'robust', or 'none'.") + + scaled = X.copy() + center = np.zeros(X.shape[1], dtype=float) + scale = np.ones(X.shape[1], dtype=float) + if np.any(scaled_mask): + scaled[:, scaled_mask] = scaler.fit_transform(X[:, scaled_mask]) + center_values = getattr( + scaler, "mean_", getattr(scaler, "center_", np.zeros(np.sum(scaled_mask))) + ) + scale_values = getattr(scaler, "scale_", np.ones(np.sum(scaled_mask))) + center[scaled_mask] = center_values + scale[scaled_mask] = scale_values + + weights = np.ones(X.shape[1], dtype=float) + if family_weighting is not None: + weighting = family_weighting.lower() + if weighting != "equal_family": + raise ValueError("family_weighting must be None or 'equal_family'.") + for _family, idx in families.groupby(families).groups.items(): + idx_arr = np.array(list(idx), dtype=int) + scaled_idx = idx_arr[scaled_mask[idx_arr]] + if len(scaled_idx) > 0: + weights[scaled_idx] *= 1.0 / math.sqrt(len(scaled_idx)) + if family_weights: + for family, weight in family_weights.items(): + weights[families.to_numpy() == family] *= float(weight) + scaled *= weights + + table = pd.DataFrame( + { + "feature_name": list(feature_names), + "scaling_method": method_norm, + "family": families.to_numpy(), + "family_weight": weights, + "scaled": scaled_mask, + "center": center, + "scale": scale, + "raw_mean": np.nanmean(X, axis=0), + "raw_std": np.nanstd(X, axis=0), + "scaled_mean": np.nanmean(scaled, axis=0), + "scaled_std": np.nanstd(scaled, axis=0), + } + ) + if return_scaler: + return scaled, table, scaler + return scaled, table + + +def rank_read_features_by_group_difference( + feature_matrix: np.ndarray, + feature_names: Sequence[str], + labels: Sequence[Any], + *, + top_n: int = 20, +) -> pd.DataFrame: + """Rank features by standardized mean separation across two or more labels.""" + + X = np.asarray(feature_matrix, dtype=float) + y = pd.Series(labels) + if X.ndim != 2: + raise ValueError("feature_matrix must be 2-dimensional.") + if X.shape[0] != len(y): + raise ValueError("labels length must match feature_matrix rows.") + if X.shape[1] != len(feature_names): + raise ValueError("feature_names length must match feature_matrix width.") + + groups = list(pd.unique(y)) + if len(groups) < 2: + raise ValueError("At least two label groups are required.") + + rows = [] + overall_std = np.nanstd(X, axis=0) + for idx, name in enumerate(feature_names): + means = [] + for group in groups: + means.append(float(np.nanmean(X[y.to_numpy() == group, idx]))) + effect = (max(means) - min(means)) / (overall_std[idx] + 1e-12) + rows.append( + { + "feature_name": name, + "effect_score": float(effect), + "min_group_mean": min(means), + "max_group_mean": max(means), + "overall_std": float(overall_std[idx]), + } + ) + return ( + pd.DataFrame(rows) + .sort_values("effect_score", ascending=False) + .head(top_n) + .reset_index(drop=True) + ) + + +def raster_stats_brief(stats: dict[str, Any]) -> dict[str, Any]: + """Return the raster stats fields most useful for notebook display.""" + + keys = [ + "pairs", + "rows_are", + "unique_reads", + "site_sets", + "rows_before_downsample", + "rows_after_downsample", + "downsampled", + "downsample_method", + "coordinate_mode", + "selection_mode", + "window_offsets_bp", + "window_widths_bp", + "ml_score_thresholds", + ] + brief = {key: stats.get(key) for key in keys if key in stats} + brief["observed_offsets_bp"] = stats.get("observed_window_center_offsets_summary_bp") + brief["site_selection"] = stats.get("site_selection") + return brief + + +def infer_region_source_labels( + region_axis_table: pd.DataFrame, + read_metadata: Sequence[dict[str, Any]], + *, + source_field: str = "source_label", + unknown_label: str = "unknown", +) -> pd.DataFrame: + """Attach the most frequent read source label to each region-axis row.""" + + axis_table = region_axis_table.copy() + if "region_id" not in axis_table.columns: + raise ValueError("region_axis_table must include a region_id column.") + meta_df = pd.DataFrame(read_metadata).copy() + if meta_df.empty or not {"chromosome", "region_start", "region_end"}.issubset( + meta_df.columns + ): + axis_table[source_field] = unknown_label + return axis_table + + if "region_strand" not in meta_df.columns: + meta_df["region_strand"] = "." + if source_field not in meta_df.columns: + meta_df[source_field] = unknown_label + meta_df["region_id"] = meta_df.apply( + lambda row: ( + f"{row['chromosome']}:{int(row['region_start'])}-" + f"{int(row['region_end'])}:{row.get('region_strand', '.')}" + ), + axis=1, + ) + source_by_region = ( + meta_df.groupby(["region_id", source_field], dropna=False) + .size() + .reset_index(name="n") + .sort_values(["region_id", "n"], ascending=[True, False]) + .drop_duplicates(subset=["region_id"], keep="first") + .loc[:, ["region_id", source_field]] + ) + axis_table = axis_table.merge(source_by_region, on="region_id", how="left") + axis_table[source_field] = axis_table[source_field].fillna(unknown_label) + return axis_table + + +def _safe_scores(X: np.ndarray, labels: np.ndarray) -> dict[str, float | None]: + from sklearn.metrics import ( + calinski_harabasz_score, + davies_bouldin_score, + silhouette_score, + ) + + scores = {"silhouette": None, "calinski_harabasz": None, "davies_bouldin": None} + unique_labels = np.unique(labels) + valid = len(unique_labels) >= 2 and not ( + len(unique_labels) == 1 and unique_labels[0] == -1 + ) + if not valid: + return scores + with contextlib.suppress(Exception): + scores["silhouette"] = float(silhouette_score(X, labels)) + with contextlib.suppress(Exception): + scores["calinski_harabasz"] = float(calinski_harabasz_score(X, labels)) + with contextlib.suppress(Exception): + scores["davies_bouldin"] = float(davies_bouldin_score(X, labels)) + return scores + + +def _renumber_by_size(labels: np.ndarray, noise_label: int | None = None) -> np.ndarray: + labels = np.asarray(labels) + counts = Counter(labels) + order = [ + label + for label, _ in sorted(counts.items(), key=lambda x: x[1], reverse=True) + if noise_label is None or label != noise_label + ] + mapping = {old: new for new, old in enumerate(order)} + remapped = [] + for label in labels: + if noise_label is not None and label == noise_label: + remapped.append(-1) + else: + remapped.append(mapping.get(label, -1)) + return np.array(remapped, dtype=int) + + +def cluster_label_mapping( + labels_raw: np.ndarray, + labels_size_ordered: np.ndarray, +) -> dict[int, int]: + """ + Build a stable mapping from raw estimator labels to size-ordered labels. + """ + + mapping: dict[int, int] = {} + for raw_label, ordered_label in zip(labels_raw, labels_size_ordered, strict=False): + raw = int(raw_label) + ordered = int(ordered_label) + if raw in mapping and mapping[raw] != ordered: + raise ValueError("Raw cluster labels map to multiple size-ordered labels.") + mapping[raw] = ordered + return mapping + + +def apply_cluster_label_mapping( + labels: np.ndarray, + mapping: dict[int, int], + *, + unknown_label: int = -1, +) -> np.ndarray: + """ + Apply a raw-to-size-ordered label mapping to new assignments. + """ + + return np.array( + [mapping.get(int(label), unknown_label) for label in np.asarray(labels)], + dtype=int, + ) + + +def sample_rows( + data: np.ndarray, + labels: Sequence[Any] | None = None, + *, + n: int | None = None, + frac: float | None = None, + random_state: int | None = 42, + stratify: bool = False, +) -> tuple[np.ndarray, np.ndarray | None, np.ndarray]: + """ + Downsample rows for faster clustering/classification. + + Args: + data: 2D array of shape (n_rows, n_features) + labels: optional parallel labels to return (e.g., cluster or sample labels) + n: fixed number of rows to sample + frac: fraction of rows to sample (ignored if n is provided) + random_state: RNG seed + stratify: if True and labels are provided, sample proportionally to label frequency + + Returns: + sampled_data, sampled_labels (or None), indices of selected rows + """ + + rng = np.random.default_rng(random_state) + n_rows = data.shape[0] + if n is None and frac is None: + raise ValueError("Provide n or frac to sample.") + if n is None: + n = max(1, int(n_rows * frac)) + n = min(n, n_rows) + + if stratify and labels is not None: + labels_arr = np.asarray(labels) + unique, counts = np.unique(labels_arr, return_counts=True) + probs = counts / counts.sum() + selected = [] + for lbl, p in zip(unique, probs, strict=False): + k = max(1, int(round(p * n))) + choices = np.flatnonzero(labels_arr == lbl) + k = min(k, len(choices)) + selected.append(rng.choice(choices, size=k, replace=False)) + idx = np.unique(np.concatenate(selected)) + if len(idx) > n: + idx = rng.choice(idx, size=n, replace=False) + else: + idx = rng.choice(n_rows, size=n, replace=False) + + sampled_data = data[idx] + sampled_labels = np.asarray(labels)[idx] if labels is not None else None + return sampled_data, sampled_labels, idx + + +def cluster_read_windows( + feature_matrix: np.ndarray, + *, + method: str = "kmeans", + n_clusters: int = 8, + random_state: int = 42, + auto_k: bool = False, + k_grid: Sequence[int] | None = None, + score: str = "silhouette", + **kwargs, +) -> ClusterResult: + """ + Run clustering on a read feature matrix with support for several algorithms. + auto_k=True grid-searches K for methods that require it using the requested score. + """ + + from sklearn.cluster import ( + DBSCAN, + OPTICS, + AgglomerativeClustering, + Birch, + KMeans, + MiniBatchKMeans, + SpectralClustering, + ) + from sklearn.mixture import GaussianMixture + + X = np.asarray(feature_matrix) + + def fit( + n_components: int, + ) -> tuple[np.ndarray, Any, dict[str, float | None], int | None]: + algo = method.lower() + model = None + labels: np.ndarray + noise_label: int | None = None + + if algo == "kmeans": + model = KMeans( + n_clusters=n_components, + random_state=random_state, + n_init=kwargs.get("n_init", 10), + ) + labels = model.fit_predict(X) + elif algo == "minibatch_kmeans": + model = MiniBatchKMeans( + n_clusters=n_components, + random_state=random_state, + n_init=kwargs.get("n_init", 10), + batch_size=kwargs.get("batch_size", 1024), + ) + labels = model.fit_predict(X) + elif algo == "gmm": + model = GaussianMixture( + n_components=n_components, + covariance_type=kwargs.get("covariance_type", "full"), + random_state=random_state, + ) + model.fit(X) + labels = model.predict(X) + elif algo == "agglomerative": + model = AgglomerativeClustering( + n_clusters=n_components, + linkage=kwargs.get("linkage", "ward"), + metric="euclidean", + ) + labels = model.fit_predict(X) + elif algo == "spectral": + model = SpectralClustering( + n_clusters=n_components, + assign_labels=kwargs.get("assign_labels", "kmeans"), + affinity=kwargs.get("affinity", "rbf"), + random_state=random_state, + ) + labels = model.fit_predict(X) + elif algo == "birch": + model = Birch( + n_clusters=n_components, threshold=kwargs.get("threshold", 0.5) + ) + labels = model.fit_predict(X) + elif algo == "dbscan": + model = DBSCAN( + eps=kwargs.get("eps", 0.8), + min_samples=kwargs.get("min_samples", 20), + ) + labels = model.fit_predict(X) + noise_label = -1 + elif algo == "optics": + model = OPTICS( + min_samples=kwargs.get("min_samples", 20), + max_eps=kwargs.get("max_eps", np.inf), + cluster_method="xi", + xi=kwargs.get("xi", 0.05), + ) + labels = model.fit_predict(X) + noise_label = -1 + elif algo == "hdbscan": + if not _HAS_HDBSCAN: + raise ImportError("Install hdbscan to use method='hdbscan'.") + model = _hdbscan_lib.HDBSCAN( + min_cluster_size=kwargs.get("min_cluster_size", 30), + min_samples=kwargs.get("min_samples"), + ) + labels = model.fit_predict(X) + noise_label = -1 + elif algo == "umap_kmeans": + try: + import umap + except Exception as exc: # pragma: no cover - optional dependency + raise ImportError( + "Install umap-learn to use method='umap_kmeans'." + ) from exc + reducer = umap.UMAP( + n_neighbors=kwargs.get("umap_n_neighbors", 15), + min_dist=kwargs.get("umap_min_dist", 0.1), + n_components=kwargs.get("umap_n_components", 2), + metric=kwargs.get("umap_metric", "euclidean"), + random_state=random_state, + ) + embedded = reducer.fit_transform(X) + kmeans_cls = _get_kmeans() + model = kmeans_cls( + n_clusters=n_components, + random_state=random_state, + n_init=kwargs.get("n_init", 10), + ) + labels = model.fit_predict(embedded) + else: + raise ValueError(f"Unknown clustering method '{method}'.") + + metrics = _safe_scores(X, labels) + return labels, model, metrics, noise_label + + if auto_k: + grid = list(k_grid or range(2, 16)) + best: ClusterResult | None = None + best_score: float | None = None + + def score_value(metric_dict: dict[str, float | None]) -> float | None: + val = metric_dict.get(score) + if val is None: + return None + if score == "davies_bouldin": + return -val + return val + + for k in grid: + labels, model, metrics, noise_label = fit(k) + val = score_value(metrics) + if val is None: + continue + if best is None or val > best_score: + best = ClusterResult( + labels_raw=labels, + labels_size_ordered=_renumber_by_size(labels, noise_label), + model=model, + metrics=metrics, + ) + best_score = val + if best is None: + labels, model, metrics, noise_label = fit(grid[-1]) + best = ClusterResult( + labels_raw=labels, + labels_size_ordered=_renumber_by_size(labels, noise_label), + model=model, + metrics=metrics, + ) + return best + + labels, model, metrics, noise_label = fit(n_clusters) + return ClusterResult( + labels_raw=labels, + labels_size_ordered=_renumber_by_size(labels, noise_label), + model=model, + metrics=metrics, + ) + + +def plot_cluster_profiles( + data_matrix: np.ndarray, + labels: np.ndarray, + *, + val_matrix: np.ndarray | None = None, + include_clusters: Sequence[int] | None = None, + view_window_size: int | None = None, + window_size: int | None = None, + motif_index: int = 0, + motif_count: int | None = None, + metadata: Sequence[dict[str, Any]] | None = None, + motif_labels: Sequence[str] | None = None, + motif_colors: Sequence[str] | None = None, + plot_all_motifs: bool = False, + motif_profile_mode: str = "single_axis", + color_points_by: str = "cluster", + show_valid_profile: bool = False, + signal_label: str = "Modification signal", + valid_label: str = "Valid-site fraction", + smoothing: str | None = None, + smooth_win: int = 21, + smooth_sigma: float = 6.0, + show_unsmoothed_overlay: bool = False, + cmap_name: str = "viridis", + invert_y: bool = True, + point_size: float = 1.0, + point_alpha: float = 0.01, +): + """ + Scatter plot of per-read modification calls with side profile summaries per label. + Supports concatenated multi-motif windows and optional overlays per motif. + """ + + import matplotlib.pyplot as plt + from matplotlib import gridspec + from matplotlib.colors import Normalize + from matplotlib.lines import Line2D + + X_full = np.asarray(data_matrix) + labs = np.asarray(labels) + if X_full.shape[0] != labs.shape[0]: + raise ValueError("data_matrix and labels must have the same number of rows.") + if include_clusters is not None: + mask = np.isin(labs, list(include_clusters)) + if not np.any(mask): + raise ValueError("No reads remain after filtering include_clusters.") + X_full = X_full[mask] + labs = labs[mask] + if val_matrix is not None: + val_matrix = np.asarray(val_matrix)[mask] + if metadata is not None: + metadata = [row for row, keep in zip(metadata, mask, strict=False) if keep] + + V_full = np.asarray(val_matrix) if val_matrix is not None else None + if V_full is not None and V_full.shape != X_full.shape: + raise ValueError("val_matrix must match data_matrix shape.") + + if color_points_by not in {"cluster", "motif"}: + raise ValueError("color_points_by must be 'cluster' or 'motif'.") + if motif_profile_mode not in {"single_axis", "separate_axes"}: + raise ValueError("motif_profile_mode must be 'single_axis' or 'separate_axes'.") + + window_len = X_full.shape[1] + n_motifs, slice_width = _resolve_motif_slices( + window_len, + motif_count=motif_count, + motif_labels=motif_labels, + window_size=window_size, + view_window_size=view_window_size, + ) + if motif_index < 0 or motif_index >= n_motifs: + raise ValueError( + f"motif_index {motif_index} out of range for {n_motifs} motif slices." + ) + motif_ids = ( + list(range(n_motifs)) if plot_all_motifs and n_motifs > 1 else [motif_index] + ) + + if motif_labels is None: + motif_labels = [f"motif_{i}" for i in range(n_motifs)] + else: + motif_labels = list(motif_labels) + if len(motif_labels) < n_motifs: + motif_labels.extend( + [f"motif_{i}" for i in range(len(motif_labels), n_motifs)] + ) + if motif_colors is None: + motif_cmap = plt.get_cmap("tab10") + motif_colors = [motif_cmap(i % 10) for i in range(n_motifs)] + else: + motif_colors = list(motif_colors) + if len(motif_colors) < n_motifs: + motif_cmap = plt.get_cmap("tab10") + motif_colors.extend( + motif_cmap(i % 10) for i in range(len(motif_colors), n_motifs) + ) + + selected_start = motif_index * slice_width + selected_end = min(window_len, selected_start + slice_width) + primary_matrix = X_full[:, selected_start:selected_end] + inferred_window_span = ( + _window_span_from_size(window_size) + if window_size is not None + else primary_matrix.shape[1] + ) + view_span = ( + _window_span_from_size(view_window_size) + if view_window_size is not None + else None + ) + x_axis = _centered_x_axis( + primary_matrix.shape[1], view_span or inferred_window_span + ) + + mod_fraction = primary_matrix.mean(axis=1) + labs_arr, lab_codes, unique_codes = _prepare_group_labels(labs) + order = np.lexsort((-mod_fraction, lab_codes)) + X_full = X_full[order] + labs_arr = labs_arr[order] + lab_codes = lab_codes[order] + V_full = V_full[order] if V_full is not None else None + + unique_labels = np.array( + [np.unique(labs_arr[lab_codes == code])[0] for code in unique_codes] + ) + cmap = plt.get_cmap(cmap_name) + norm = Normalize(vmin=int(unique_codes.min()), vmax=int(unique_codes.max() or 1)) + + fig = plt.figure(figsize=(12, max(4, len(unique_codes)))) + gs = gridspec.GridSpec(nrows=len(unique_codes), ncols=2, width_ratios=[3, 1]) + ax_left = fig.add_subplot(gs[:, 0]) + if color_points_by == "cluster": + rows, cols = np.nonzero(primary_matrix[order]) + colors = cmap(norm(lab_codes[rows])) + ax_left.scatter( + x_axis[cols], + rows, + s=point_size, + alpha=point_alpha, + c=colors, + rasterized=True, + ) + else: + for motif_id in motif_ids: + s = motif_id * slice_width + e = min(window_len, s + slice_width) + motif_matrix = X_full[:, s:e] + rows, cols = np.nonzero(motif_matrix) + ax_left.scatter( + x_axis[cols], + rows, + s=point_size, + alpha=point_alpha, + color=motif_colors[motif_id], + label=str(motif_labels[motif_id]), + rasterized=True, + ) + ax_left.set_xlabel("Distance from region center (bp)") + ax_left.set_ylabel("Sorted read index") + if invert_y: + ax_left.invert_yaxis() + + change_points = np.flatnonzero(np.diff(lab_codes)) + 1 + for cp in change_points: + ax_left.axhline(cp, color="0.2", linestyle="--", linewidth=0.3) + + for i, code in enumerate(unique_codes): + ax = fig.add_subplot(gs[i, 1]) + row_mask = lab_codes == code + panel_max = 0.0 + if motif_profile_mode == "separate_axes" and len(motif_ids) > 1: + axes_for_motifs = [ax] + motif_maxima = [0.0 for _ in motif_ids] + for motif_pos in range(1, len(motif_ids)): + twin = ax.twinx() + twin.spines["right"].set_position(("outward", 35 * motif_pos)) + axes_for_motifs.append(twin) + for motif_pos, motif_id in enumerate(motif_ids): + s = motif_id * slice_width + e = min(window_len, s + slice_width) + motif_matrix = X_full[:, s:e] + mean_profile_raw = motif_matrix[row_mask].mean(axis=0) + mean_profile = _smooth_profile_vector( + mean_profile_raw, + smoothing=smoothing, + smooth_win=smooth_win, + smooth_sigma=smooth_sigma, + ) + motif_maxima[motif_pos] = max( + motif_maxima[motif_pos], float(np.nanmax(mean_profile_raw)) + ) + panel_max = max(panel_max, motif_maxima[motif_pos]) + motif_ax = axes_for_motifs[motif_pos] + if show_unsmoothed_overlay and smoothing is not None: + motif_ax.plot( + x_axis, + mean_profile_raw, + color=motif_colors[motif_id], + linewidth=1.0, + alpha=0.25, + ) + motif_ax.plot( + x_axis, + mean_profile, + color=motif_colors[motif_id], + linewidth=1.5, + ) + motif_ax.set_ylabel( + str(motif_labels[motif_id]), color=motif_colors[motif_id] + ) + motif_ax.tick_params( + axis="y", colors=motif_colors[motif_id], labelsize=8 + ) + if show_valid_profile and V_full is not None: + mean_val_raw = V_full[row_mask, s:e].mean(axis=0) + mean_val = _smooth_profile_vector( + mean_val_raw, + smoothing=smoothing, + smooth_win=smooth_win, + smooth_sigma=smooth_sigma, + ) + motif_ax.plot( + x_axis, + mean_val, + color=motif_colors[motif_id], + linestyle="--", + linewidth=1.0, + alpha=0.85, + ) + for motif_pos, motif_ax in enumerate(axes_for_motifs): + motif_ax.set_ylim(0, max(motif_maxima[motif_pos], 0.05) * 1.05) + else: + for motif_id in motif_ids: + s = motif_id * slice_width + e = min(window_len, s + slice_width) + motif_matrix = X_full[:, s:e] + mean_profile_raw = motif_matrix[row_mask].mean(axis=0) + mean_profile = _smooth_profile_vector( + mean_profile_raw, + smoothing=smoothing, + smooth_win=smooth_win, + smooth_sigma=smooth_sigma, + ) + panel_max = max(panel_max, float(np.nanmax(mean_profile_raw))) + if show_unsmoothed_overlay and smoothing is not None: + ax.plot( + x_axis, + mean_profile_raw, + color=motif_colors[motif_id] + if len(motif_ids) > 1 + else cmap(norm(code)), + linewidth=1.0, + alpha=0.25, + ) + ax.plot( + x_axis, + mean_profile, + color=motif_colors[motif_id] + if len(motif_ids) > 1 + else cmap(norm(code)), + linewidth=1.5, + ) + if show_valid_profile and V_full is not None: + mean_val_raw = V_full[row_mask, s:e].mean(axis=0) + mean_val = _smooth_profile_vector( + mean_val_raw, + smoothing=smoothing, + smooth_win=smooth_win, + smooth_sigma=smooth_sigma, + ) + ax.plot( + x_axis, + mean_val, + color=motif_colors[motif_id] if len(motif_ids) > 1 else "0.35", + linestyle="--", + linewidth=1.0, + alpha=0.85, + ) + ax.set_title(f"{unique_labels[i]} (n={row_mask.sum()})") + ax.set_xlim(x_axis[0], x_axis[-1]) + y_max = max(panel_max, 0.05) + ax.set_ylim(0, y_max * 1.05) + + legend_handles: list[Line2D] = [] + if len(motif_ids) > 1 or color_points_by == "motif": + legend_handles.extend( + [ + Line2D( + [0], + [0], + color=motif_colors[m], + linewidth=2, + label=str(motif_labels[m]), + ) + for m in motif_ids + ] + ) + if show_valid_profile and V_full is not None: + legend_handles.extend( + [ + Line2D( + [0], + [0], + color="black", + linewidth=2, + linestyle="-", + label=signal_label, + ), + Line2D( + [0], + [0], + color="black", + linewidth=1, + linestyle="--", + label=valid_label, + ), + ] + ) + if legend_handles: + fig.legend( + handles=legend_handles, + loc="upper center", + ncol=min(4, len(legend_handles)), + frameon=False, + bbox_to_anchor=(0.5, 1.01), + ) + + fig.tight_layout() + return fig + + +def classify_read_features_binary( + feature_matrix: np.ndarray, + sample_labels: Sequence[str | int], + *, + classifier: str = "xgboost", + test_size: float = 0.2, + random_state: int = 42, + **kwargs, +) -> dict[str, Any]: + """ + Train/test a binary classifier on read-level features to quantify separability between two samples. + + Args: + feature_matrix: 2D array (n_reads, n_features) + sample_labels: iterable of length n_reads with exactly two distinct values (sample IDs) + classifier: 'xgboost' (default) or one of: + 'logreg' (sklearn LogisticRegression), + 'sgd' (sklearn SGDClassifier), + 'random_forest' (sklearn RandomForestClassifier), + 'svc' (sklearn SVC with probability=True), + 'knn' (sklearn KNeighborsClassifier) + test_size: fraction held out for testing + random_state: RNG seed for reproducibility + kwargs: forwarded to the classifier constructor + + Returns: + dict with keys: + model: fitted estimator + metrics: accuracy, roc_auc, confusion_matrix + predictions: DataFrame with columns ['true_label','pred_label','proba','split','sample_label'] + label_mapping: mapping from class name to encoded int + """ + + from sklearn.metrics import accuracy_score, confusion_matrix, roc_auc_score + from sklearn.model_selection import train_test_split + from sklearn.preprocessing import LabelEncoder + + X = np.asarray(feature_matrix) + y_raw = np.asarray(sample_labels) + if X.shape[0] != y_raw.shape[0]: + raise ValueError("feature_matrix and sample_labels must have matching length.") + row_index = np.arange(X.shape[0], dtype=int) + + le = LabelEncoder() + y = le.fit_transform(y_raw) + classes = le.classes_ + if len(classes) != 2: + raise ValueError("Binary classification requires exactly two sample labels.") + + X_train, X_test, y_train, y_test, lbl_train, lbl_test, idx_train, idx_test = ( + train_test_split( + X, + y, + y_raw, + row_index, + test_size=test_size, + random_state=random_state, + stratify=y, + ) + ) + + clf_name = classifier.lower() + if clf_name == "xgboost": + XGB = _get_xgb_classifier() + # Sensible defaults; kwargs can override + model = XGB( + n_estimators=kwargs.get("n_estimators", 200), + max_depth=kwargs.get("max_depth", 4), + learning_rate=kwargs.get("learning_rate", 0.1), + subsample=kwargs.get("subsample", 0.8), + colsample_bytree=kwargs.get("colsample_bytree", 0.8), + random_state=random_state, + n_jobs=kwargs.get("n_jobs", 4), + ) + elif clf_name == "logreg": + from sklearn.linear_model import LogisticRegression + + model = LogisticRegression( + max_iter=kwargs.get("max_iter", 200), + solver=kwargs.get("solver", "lbfgs"), + class_weight=kwargs.get("class_weight", "balanced"), + random_state=random_state, + ) + elif clf_name == "sgd": + from sklearn.linear_model import SGDClassifier + + model = SGDClassifier( + loss=kwargs.get("loss", "log_loss"), + max_iter=kwargs.get("max_iter", 1000), + alpha=kwargs.get("alpha", 1e-4), + random_state=random_state, + class_weight=kwargs.get("class_weight", "balanced"), + ) + elif clf_name == "random_forest": + from sklearn.ensemble import RandomForestClassifier + + model = RandomForestClassifier( + n_estimators=kwargs.get("n_estimators", 200), + max_depth=kwargs.get("max_depth"), + random_state=random_state, + n_jobs=kwargs.get("n_jobs", -1), + class_weight=kwargs.get("class_weight", "balanced_subsample"), + ) + elif clf_name == "svc": + from sklearn.svm import SVC + + model = SVC( + C=kwargs.get("C", 1.0), + kernel=kwargs.get("kernel", "rbf"), + probability=True, + random_state=random_state, + class_weight=kwargs.get("class_weight", "balanced"), + ) + elif clf_name == "knn": + from sklearn.neighbors import KNeighborsClassifier + + model = KNeighborsClassifier( + n_neighbors=kwargs.get("n_neighbors", 5), + weights=kwargs.get("weights", "distance"), + n_jobs=kwargs.get("n_jobs"), + ) + else: + raise ValueError(f"Unknown classifier '{classifier}'.") + + model.fit(X_train, y_train) + proba = model.predict_proba(X_test)[:, 1] + y_pred = (proba >= 0.5).astype(int) + + proba_train = model.predict_proba(X_train)[:, 1] + y_pred_train = (proba_train >= 0.5).astype(int) + + metrics = { + "test": { + "accuracy": float(accuracy_score(y_test, y_pred)), + "roc_auc": float(roc_auc_score(y_test, proba)), + "confusion_matrix": confusion_matrix(y_test, y_pred).tolist(), + }, + "train": { + "accuracy": float(accuracy_score(y_train, y_pred_train)), + "roc_auc": float(roc_auc_score(y_train, proba_train)), + "confusion_matrix": confusion_matrix(y_train, y_pred_train).tolist(), + }, + } + + preds_df = pd.DataFrame( + { + "true_label": le.inverse_transform(y_test), + "pred_label": le.inverse_transform(y_pred), + "proba": proba, + "split": "test", + "sample_label": lbl_test, + "row_index": idx_test, + } + ) + + # Also record train predictions for diagnostics + train_df = pd.DataFrame( + { + "true_label": le.inverse_transform(y_train), + "pred_label": le.inverse_transform(y_pred_train), + "proba": proba_train, + "split": "train", + "sample_label": lbl_train, + "row_index": idx_train, + } + ) + + all_preds = pd.concat([train_df, preds_df], ignore_index=True) + + return { + "model": model, + "metrics": metrics, + "predictions": all_preds, + "label_mapping": { + cls: int(code) + for cls, code in zip(classes, le.transform(classes), strict=False) + }, + } + + +def plot_confusion_matrices(predictions: pd.DataFrame, *, cmap: str = "Blues"): + """ + Plot train/test confusion matrices from the predictions DataFrame returned by classify_read_features_binary. + """ + import matplotlib.pyplot as plt + import seaborn as sns + from sklearn.metrics import confusion_matrix + + fig, axes = plt.subplots(1, 2, figsize=(8, 4)) + for ax, split in zip(axes, ["train", "test"], strict=False): + df_split = predictions[predictions["split"] == split] + labels_true = df_split["true_label"] + labels_pred = df_split["pred_label"] + uniq = sorted(labels_true.unique()) + cm = confusion_matrix(labels_true, labels_pred, labels=uniq) + sns.heatmap( + cm, + annot=True, + fmt="d", + cmap=cmap, + xticklabels=uniq, + yticklabels=uniq, + ax=ax, + ) + ax.set_xlabel("Predicted") + ax.set_ylabel("True") + ax.set_title(f"{split.capitalize()} confusion") + fig.tight_layout() + return fig + + +def _resolve_prediction_row_selection( + matrix: np.ndarray, + preds: pd.DataFrame, + *, + owner: str, +) -> tuple[np.ndarray, np.ndarray | None]: + """ + Align a feature/signal matrix to a predictions split. + + Supports two calling conventions: + 1) matrix is the full dataset and ``preds.row_index`` maps split rows. + 2) matrix is already split-aligned and has the same row count as ``preds``. + """ + matrix_full = np.asarray(matrix) + if "row_index" not in preds.columns: + if len(preds) != matrix_full.shape[0]: + raise ValueError( + f"{owner}: predictions and matrix must have matching rows for the chosen split " + "or predictions must include a row_index column." + ) + return matrix_full, None + + row_idx = preds["row_index"].to_numpy(dtype=int) + if row_idx.size == 0: + return matrix_full[:0], row_idx + + in_bounds = (row_idx >= 0) & (row_idx < matrix_full.shape[0]) + if in_bounds.all(): + return matrix_full[row_idx], row_idx + + # If matrix is already split-aligned, keep it as-is and ignore out-of-range + # row indices (which refer to original global row IDs). + if len(preds) == matrix_full.shape[0]: + return matrix_full, None + + raise ValueError( + f"{owner}: row_index contains out-of-bounds values for the provided matrix " + f"(max row_index={int(row_idx.max())}, matrix_rows={matrix_full.shape[0]})." + ) + + +def plot_classification_profiles( + data_matrix: np.ndarray, + predictions: pd.DataFrame, + *, + val_matrix: np.ndarray | None = None, + split: str = "test", + group_by: str = "pred_label", + positive_label: str | None = None, + motif_index: int = 0, + motif_count: int | None = None, + metadata: Sequence[dict[str, Any]] | None = None, + motif_labels: Sequence[str] | None = None, + motif_colors: Sequence[str] | None = None, + plot_all_motifs: bool = False, + motif_profile_mode: str = "single_axis", + color_points_by: str = "cluster", + view_window_size: int | None = None, + window_size: int | None = None, + show_valid_profile: bool = False, + smoothing: str | None = None, + smooth_win: int = 21, + smooth_sigma: float = 6.0, + show_unsmoothed_overlay: bool = False, + cmap_name: str = "viridis", + invert_y: bool = True, + point_size: float = 1.0, + point_alpha: float = 0.01, +): + """ + Visualize per-read modification calls colored by prediction-derived groups. + For binary models, ``group_by='confusion'`` renders TP/TN/FP/FN groups. + + Args: + data_matrix: per-read windows (n_reads, window_len) + predictions: DataFrame from classify_read_features_binary + val_matrix: optional valid-site matrix to overlay + split: which split to visualize ('train' or 'test') + """ + + preds = predictions[predictions["split"] == split].reset_index(drop=True) + X_full = np.asarray(data_matrix) + X, row_idx = _resolve_prediction_row_selection( + X_full, + preds, + owner="plot_classification_profiles", + ) + V_full = np.asarray(val_matrix) if val_matrix is not None else None + M_full = list(metadata) if metadata is not None else None + + if V_full is not None: + row_idx_max = ( + int(row_idx.max()) if row_idx is not None and row_idx.size > 0 else -1 + ) + if row_idx is not None and row_idx.size > 0 and row_idx_max < V_full.shape[0]: + V = V_full[row_idx] + elif V_full.shape[0] == len(preds): + V = V_full + else: + raise ValueError( + "plot_classification_profiles: val_matrix could not be aligned to predictions. " + "Pass either full val_matrix (with row_index) or split-aligned val_matrix." + ) + else: + V = None + + if M_full is not None: + row_idx_max = ( + int(row_idx.max()) if row_idx is not None and row_idx.size > 0 else -1 + ) + if row_idx is not None and row_idx.size > 0 and row_idx_max < len(M_full): + M = [M_full[i] for i in row_idx] + elif len(M_full) == len(preds): + M = M_full + else: + raise ValueError( + "plot_classification_profiles: metadata could not be aligned to predictions. " + "Pass either full metadata (with row_index) or split-aligned metadata." + ) + else: + M = None + + if group_by not in {"pred_label", "true_label", "confusion"}: + raise ValueError( + "group_by must be one of {'pred_label', 'true_label', 'confusion'}." + ) + + if group_by == "confusion": + class_values = sorted(pd.unique(preds["true_label"])) + if len(class_values) != 2: + raise ValueError("group_by='confusion' requires exactly two classes.") + positive = positive_label if positive_label is not None else class_values[-1] + true_vals = preds["true_label"].to_numpy() + pred_vals = preds["pred_label"].to_numpy() + confusion_labels = np.empty(len(preds), dtype=object) + confusion_labels[(true_vals != positive) & (pred_vals != positive)] = "TN" + confusion_labels[(true_vals != positive) & (pred_vals == positive)] = "FP" + confusion_labels[(true_vals == positive) & (pred_vals != positive)] = "FN" + confusion_labels[(true_vals == positive) & (pred_vals == positive)] = "TP" + labels = confusion_labels + else: + labels = preds[group_by].to_numpy() + + return plot_cluster_profiles( + X, + labels, + val_matrix=V, + view_window_size=view_window_size, + window_size=window_size, + motif_index=motif_index, + motif_count=motif_count, + metadata=M, + motif_labels=motif_labels, + motif_colors=motif_colors, + plot_all_motifs=plot_all_motifs, + motif_profile_mode=motif_profile_mode, + color_points_by=color_points_by, + show_valid_profile=show_valid_profile, + smoothing=smoothing, + smooth_win=smooth_win, + smooth_sigma=smooth_sigma, + show_unsmoothed_overlay=show_unsmoothed_overlay, + cmap_name=cmap_name, + invert_y=invert_y, + point_size=point_size, + point_alpha=point_alpha, + ) + + +def plot_region_classification_profiles( + pileup_matrix: np.ndarray, + predictions: pd.DataFrame, + *, + split: str = "test", + group_by: str = "pred_label", + positive_label: str | None = None, + window_size: int | None = None, + motif_index: int | None = 0, + motif_count: int | None = None, + plot_all_motifs: bool = False, + motif_labels: Sequence[str] | None = None, + motif_colors: Sequence[str] | None = None, + motif_profile_mode: str = "single_axis", + color_points_by: str = "cluster", + point_size: float = 1.0, + point_alpha: float = 0.01, + smoothing: str | None = None, + smooth_win: int = 21, + smooth_sigma: float = 6.0, + show_unsmoothed_overlay: bool = False, + cmap_name: str = "viridis", +): + """ + Region-level counterpart to plot_classification_profiles. + Uses classifier predictions to group regions by predicted label, true label, + or confusion-class (TP/TN/FP/FN for binary tasks) and renders cluster-style profiles. + """ + + preds = predictions[predictions["split"] == split].reset_index(drop=True) + X_full = np.asarray(pileup_matrix) + X, _ = _resolve_prediction_row_selection( + X_full, + preds, + owner="plot_region_classification_profiles", + ) + + if group_by not in {"pred_label", "true_label", "confusion"}: + raise ValueError( + "group_by must be one of {'pred_label', 'true_label', 'confusion'}." + ) + + if group_by == "confusion": + class_values = sorted(pd.unique(preds["true_label"])) + if len(class_values) != 2: + raise ValueError("group_by='confusion' requires exactly two classes.") + positive = positive_label if positive_label is not None else class_values[-1] + true_vals = preds["true_label"].to_numpy() + pred_vals = preds["pred_label"].to_numpy() + labels = np.empty(len(preds), dtype=object) + labels[(true_vals != positive) & (pred_vals != positive)] = "TN" + labels[(true_vals != positive) & (pred_vals == positive)] = "FP" + labels[(true_vals == positive) & (pred_vals != positive)] = "FN" + labels[(true_vals == positive) & (pred_vals == positive)] = "TP" + else: + labels = preds[group_by].to_numpy() + + return plot_region_cluster_profiles( + X, + labels, + window_size=window_size, + motif_index=motif_index, + motif_count=motif_count, + plot_all_motifs=plot_all_motifs, + motif_labels=motif_labels, + motif_colors=motif_colors, + motif_profile_mode=motif_profile_mode, + color_points_by=color_points_by, + point_size=point_size, + point_alpha=point_alpha, + smoothing=smoothing, + smooth_win=smooth_win, + smooth_sigma=smooth_sigma, + show_unsmoothed_overlay=show_unsmoothed_overlay, + cmap_name=cmap_name, + ) + + +def plot_multisite_read_raster( + read_windows: ReadWindowExtractionResult, + *, + n_windows: int = 2, + min_separation_bp: int = 5000, + selection_mode: str = "cooccurring", + window_offsets_bp: Sequence[int] | None = None, + primary_window_index: int = 0, + coordinate_mode: str | None = None, + site_selection: dict[str, Any] | None = None, + distance_mode: str = "center", # or "bounds" + window_size: int | None = None, + motif_index: int = 0, + motif_count: int | None = None, + plot_all_motifs: bool = False, + motif_labels: Sequence[str] | None = None, + window_widths_bp: int | Sequence[int] | None = None, + enforce_full_window_span: bool = True, + min_read_length_bp: int | None = None, + smoothing: str | None = "gaussian", # None, "boxcar", "gaussian" + smooth_win: int = 21, + smooth_sigma_bp: float = 6.0, + max_rows: int | None = 500, + downsample_method: str = "auto", # "auto" | "bin_mean" | "uniform" | "random" + downsample_seed: int | None = None, + cmap: str = "magma", + vmin: float | None = None, + vmax: float | None = None, + render_mode: str = "scatter", # "scatter" | "heatmap" + render_values: str = "auto", # "auto" | "raw" | "smoothed" + axis_orientation: str | None = None, # "position_x" | "position_y" + scatter_size: float = 4.0, + scatter_alpha: float = 0.7, + scatter_color_values: str = "ml_score", # "auto" | "ml_score" | "raw" | "smoothed" + ml_score_thresholds: Sequence[float] | None = None, + motif_colors: Sequence[str] | None = None, + sort_by: str = "mod_fraction", # "mod_fraction" | "cluster" | "window_center" | "read_name" | "region_start" | "read_length" | "none" + sort_window_index: int | None = None, + sort_descending: bool | None = None, + sort_labels: Sequence[int] | None = None, + beds: Sequence[str | Path] | None = None, + rotate: bool = True, +): + """ + Plot multi-window read rasters, optionally across multiple motifs. + + Args: + read_windows: ReadWindowExtractionResult from extract_read_windows / build_multimotif_read_windows + n_windows: number of windows per read for co-occurring-region mode. + min_separation_bp: minimum separation between selected site centers. + selection_mode: shorthand selector mode. "fixed_offsets" requires exact + window_offsets_bp matches per read; "cooccurring" chooses regions from the + same read separated by min_separation_bp. The legacy alias + "cooccurring_regions" is also accepted. + window_offsets_bp: fixed window offsets relative to the primary window center. + Pass one value per window; the primary window offset should usually be 0. + primary_window_index: window used as the reference coordinate frame and default sort window. + coordinate_mode: "relative_to_primary" preserves offsets along each read; "local_window" + centers every panel on its own selected site. + site_selection: optional rich selector dict. Supports modes "cooccurring", + "fixed_offsets", and "anchor_plus_neighbors"; distance bounds, seeded + random choice, strand filters, exclusions, and orientation by anchor strand. + When provided, it supersedes selection_mode/window_offsets_bp/n_windows + shorthand for site selection. + distance_mode: "center" uses region_start/end center; "bounds" uses read_start/read_end + window_size: half-window in bp; if provided, per-motif slice width is ``2 * window_size``. + motif_index: reference motif slice index for single-motif plotting and ordering + motif_count: total motif slices concatenated in data_matrix; inferred when possible + plot_all_motifs: when True, render all motif slices in a motif x window panel grid + motif_labels: optional motif names aligned to motif slices + window_widths_bp: per-window display widths in bp (common width when scalar). Defaults + to the extracted span for each panel. + enforce_full_window_span: when True (default), drop read-sets that cannot span the + full displayed windows based on read_start/read_end (or read_length when available). + min_read_length_bp: optional minimum read length filter. Must be at least the required + span implied by requested centers/separation plus plotted window width. + smoothing: None, "boxcar", or "gaussian" + smooth_win: smoothing window length + smooth_sigma_bp: sigma for gaussian smoothing + max_rows: cap rows; when exceeded, downsample according to ``downsample_method``. + Pass None to disable downsampling. + downsample_method: "auto" uses "uniform" for scatter plots to preserve real reads + and "bin_mean" for heatmaps; "bin_mean" averages adjacent sorted read rows; + "uniform" takes evenly spaced sorted rows; "random" samples sorted rows. + downsample_seed: seed for reproducible random downsampling. + cmap: matplotlib colormap + vmin/vmax: color scale limits; None auto-scales + render_mode: "scatter" or "heatmap" + render_values: value source used for plotting. + - "auto": scatter uses smoothed values when smoothing is enabled; heatmap uses raw per-read values + - "raw": always plot raw per-read values + - "smoothed": always plot smoothed values + axis_orientation: controls which axis encodes position. + - "position_x": x=position, y=reads + - "position_y": x=reads, y=position + When None, preserves legacy behavior for heatmaps based on ``rotate`` and uses ``position_x`` for scatter. + scatter_size: marker size for scatter mode + scatter_alpha: marker alpha for scatter mode + scatter_color_values: scatter color source. + - "ml_score": normalized ML score from mod_vector values (clipped to 0..1) + - "raw": raw per-read values + - "smoothed": smoothed per-read values + - "auto": ml_score (default behavior) + ml_score_thresholds: optional per-motif ML score thresholds for scatter mode. When provided, + only points with ML score >= threshold are plotted in motif-specific colors. + motif_colors: optional colors aligned to motifs for thresholded scatter display + sort_by: ordering strategy for paired reads + sort_window_index: optional window index used by window-aware sorting modes + sort_descending: descending/ascending order toggle for supported sort modes. + If None, defaults are mode-aware (descending for mod_fraction, ascending otherwise). + sort_labels: cluster labels to sort by when sort_by="cluster" + beds: optional list of BED/GTF/GFF paths; only reads intersecting all beds are retained + rotate: subplot layout orientation (legacy compatibility); does not force data-axis orientation + """ + + import math + + import matplotlib.pyplot as plt + + if render_mode not in {"scatter", "heatmap"}: + raise ValueError("render_mode must be 'scatter' or 'heatmap'.") + if render_values not in {"auto", "raw", "smoothed"}: + raise ValueError("render_values must be 'auto', 'raw', or 'smoothed'.") + if axis_orientation is not None and axis_orientation not in { + "position_x", + "position_y", + }: + raise ValueError( + "axis_orientation must be None, 'position_x', or 'position_y'." + ) + if scatter_color_values not in {"auto", "ml_score", "raw", "smoothed"}: + raise ValueError( + "scatter_color_values must be 'auto', 'ml_score', 'raw', or 'smoothed'." + ) + if selection_mode not in {"fixed_offsets", "cooccurring_regions", "cooccurring"}: + raise ValueError( + "selection_mode must be 'fixed_offsets', 'cooccurring_regions', or 'cooccurring'." + ) + if coordinate_mode is not None and coordinate_mode not in { + "relative_to_primary", + "local_window", + }: + raise ValueError( + "coordinate_mode must be None, 'relative_to_primary', or 'local_window'." + ) + if distance_mode not in {"center", "bounds"}: + raise ValueError("distance_mode must be 'center' or 'bounds'.") + if n_windows < 1: + raise ValueError("n_windows must be >= 1.") + if min_separation_bp < 0: + raise ValueError("min_separation_bp must be >= 0.") + if downsample_method not in {"auto", "bin_mean", "uniform", "random"}: + raise ValueError( + "downsample_method must be 'auto', 'bin_mean', 'uniform', or 'random'." + ) + if max_rows is not None and max_rows <= 0: + raise ValueError("max_rows must be > 0 when provided.") + if sort_window_index is not None and sort_window_index < 0: + raise ValueError("sort_window_index must be >= 0 when provided.") + + def _kernel(kind: str, win: int, sigma: float): + if win % 2 == 0: + win += 1 + if kind == "gaussian": + r = win // 2 + x = np.arange(-r, r + 1) + k = np.exp(-0.5 * (x / sigma) ** 2) + return k / k.sum() + else: + k = np.ones(win, dtype=float) + return k / k.sum() + + def _smooth(M: np.ndarray) -> np.ndarray: + if smoothing is None: + return M + k = _kernel( + "gaussian" if smoothing == "gaussian" else "boxcar", + smooth_win, + smooth_sigma_bp, + ) + r = len(k) // 2 + out = np.empty_like(M, dtype=float) + for i in range(M.shape[0]): + row = M[i] + pad_left = row[1 : r + 1][::-1] if r > 0 else row[:0] + pad_right = row[-r - 1 : -1][::-1] if r > 0 else row[:0] + padded = np.concatenate([pad_left, row, pad_right]) + out[i] = np.convolve(padded, k, mode="same")[r : r + row.size] + return out + + # Optional bed filtering + bed_filters = [] + if beds: + try: + import pyranges as pr + except Exception as exc: # pragma: no cover - optional dependency + raise ImportError("pyranges is required for bed filtering.") from exc + for bed in beds: + bed_filters.append(pr.read_bed(bed)) + + meta = list(read_windows.metadata) + filtered_original_indices = np.arange(len(meta), dtype=int) + X_full = np.asarray(read_windows.data_matrix) + if X_full.ndim != 2: + raise ValueError("read_windows.data_matrix must be a 2D array.") + width = X_full.shape[1] + n_motifs, slice_width = _resolve_motif_slices( + width, + motif_count=motif_count, + motif_labels=motif_labels, + window_size=window_size, + ) + if n_motifs <= 0: + n_motifs = 1 + if slice_width <= 0: + raise ValueError( + "Unable to infer motif slice width from read_windows.data_matrix." + ) + if width % slice_width != 0: + raise ValueError( + "read_windows.data_matrix width is not divisible into consistent motif slices. " + "Pass an explicit motif_count or compatible window_size." + ) + if motif_index < 0 or motif_index >= n_motifs: + raise ValueError( + f"motif_index {motif_index} out of range for {n_motifs} motif slices." + ) + motif_ids = ( + list(range(n_motifs)) if plot_all_motifs and n_motifs > 1 else [motif_index] + ) + if motif_labels is None: + motif_labels_resolved = [f"motif_{i}" for i in range(n_motifs)] + else: + motif_labels_resolved = list(motif_labels) + if len(motif_labels_resolved) < n_motifs: + motif_labels_resolved.extend( + [f"motif_{i}" for i in range(len(motif_labels_resolved), n_motifs)] + ) + X_by_motif = [X_full[:, m * slice_width : (m + 1) * slice_width] for m in motif_ids] + + def _normalize_ml_scores(matrix: np.ndarray) -> np.ndarray: + values = np.asarray(matrix, dtype=float) + finite = values[np.isfinite(values)] + if finite.size and float(np.nanmax(finite)) > 1.0: + values = values / 255.0 + return np.clip(values, 0.0, 1.0) + + ML_by_motif = [_normalize_ml_scores(matrix) for matrix in X_by_motif] + + raw_selector_spec = dict(site_selection or {}) + raw_mode = raw_selector_spec.get("mode", selection_mode) + raw_offsets = raw_selector_spec.get("window_offsets_bp", window_offsets_bp) + if raw_mode in {"fixed_offsets"}: + if raw_offsets is None: + raise ValueError( + "window_offsets_bp is required when selection_mode='fixed_offsets'." + ) + n_windows_effective = len(list(raw_offsets)) + else: + n_windows_effective = int(raw_selector_spec.get("n_windows", n_windows)) + + if window_widths_bp is None: + default_width_bp = int( + slice_width if window_size is None else max(1, 2 * int(window_size)) + ) + panel_widths_bp = [default_width_bp for _ in range(n_windows_effective)] + elif isinstance(window_widths_bp, (int, np.integer)): + panel_widths_bp = [int(window_widths_bp) for _ in range(n_windows_effective)] + else: + panel_widths_bp = [int(width) for width in window_widths_bp] + if len(panel_widths_bp) != n_windows_effective: + raise ValueError( + "window_widths_bp must be a scalar or a sequence with one entry per window center." + ) + if any(width <= 0 for width in panel_widths_bp): + raise ValueError("window_widths_bp values must be > 0.") + if any(width > slice_width for width in panel_widths_bp): + raise ValueError( + "window_widths_bp values cannot exceed extracted window span; increase extract/parse window_size." + ) + panel_half_widths = [0.5 * float(width) for width in panel_widths_bp] + + selector = _resolve_raster_site_selection( + site_selection, + selection_mode=selection_mode, + window_offsets_bp=window_offsets_bp, + n_windows=n_windows, + min_separation_bp=min_separation_bp, + primary_window_index=primary_window_index, + panel_widths_bp=panel_widths_bp, + ) + n_windows_effective = selector.n_windows + primary_window_index = selector.primary_window_index + target_offsets = ( + np.asarray(selector.window_offsets_bp, dtype=float) + if selector.window_offsets_bp is not None + else None + ) + effective_coordinate_mode = ( + coordinate_mode + if coordinate_mode is not None + else ("relative_to_primary" if selector.mode == "fixed_offsets" else "local_window") + ) + + if target_offsets is None: + required_read_length_bp = int( + max( + slice_width, + ((n_windows_effective - 1) * selector.min_distance_bp) + slice_width, + ) + ) + else: + required_read_length_bp = int( + max( + slice_width, + np.ceil( + float(target_offsets.max() - target_offsets.min()) + + float(slice_width) + ), + ) + ) + + if min_read_length_bp is not None and min_read_length_bp < required_read_length_bp: + raise ValueError( + "min_read_length_bp must be >= required span implied by requested windows " + f"({required_read_length_bp} bp)." + ) + effective_min_read_length = ( + required_read_length_bp + if selector.mode == "fixed_offsets" and min_read_length_bp is None + else min_read_length_bp + ) + + def _apply_index_filter(index_vector: np.ndarray) -> None: + nonlocal X_by_motif, ML_by_motif, meta, filtered_original_indices + X_by_motif = [matrix[index_vector] for matrix in X_by_motif] + ML_by_motif = [matrix[index_vector] for matrix in ML_by_motif] + meta = [meta[i] for i in index_vector] + filtered_original_indices = filtered_original_indices[index_vector] + + # Filter by beds if provided + keep_idx = np.arange(len(meta)) + if bed_filters: + interval_rows = [] + for idx, m in enumerate(meta): + try: + chrom = m.get("chromosome") + start_bp = int(m.get("region_start", -1)) + end_bp = int(m.get("region_end", -1)) + except Exception: + continue + if chrom is None or start_bp < 0 or end_bp <= start_bp: + continue + interval_rows.append( + { + "read_idx": idx, + "Chromosome": chrom, + "Start": start_bp, + "End": end_bp, + } + ) + + if interval_rows: + intervals = pd.DataFrame(interval_rows) + candidate_indices = intervals["read_idx"].to_numpy(dtype=int) + for bed_filter in bed_filters: + if candidate_indices.size == 0: + break + candidate_df = intervals.loc[ + intervals["read_idx"].isin(candidate_indices), + ["read_idx", "Chromosome", "Start", "End"], + ] + joined = pr.PyRanges(candidate_df).join(bed_filter) + if joined.df.empty: + candidate_indices = np.array([], dtype=int) + break + candidate_indices = ( + joined.df["read_idx"].drop_duplicates().to_numpy(dtype=int) + ) + + keep_idx = np.sort(candidate_indices) + else: + keep_idx = np.array([], dtype=int) + _apply_index_filter(keep_idx) + + if effective_min_read_length is not None: + keep_by_length: list[int] = [] + for idx, m in enumerate(meta): + read_length_val = m.get("read_length") + parsed_length: int | None = None + try: + if read_length_val is not None: + parsed_length = int(read_length_val) + except Exception: + parsed_length = None + if (parsed_length is None or parsed_length <= 0) and { + "read_start", + "read_end", + }.issubset(m.keys()): + try: + parsed_length = int(m["read_end"]) - int(m["read_start"]) + except Exception: + parsed_length = None + if parsed_length is not None and parsed_length >= int( + effective_min_read_length + ): + keep_by_length.append(idx) + _apply_index_filter(np.asarray(keep_by_length, dtype=int)) + + dropped_for_window_span = 0 + + def _safe_int(value: Any) -> int | None: + try: + if value is None: + return None + return int(value) + except Exception: + return None + + def _read_bounds(meta_row: dict[str, Any]) -> tuple[int, int] | None: + start = _safe_int(meta_row.get("read_start")) + end = _safe_int(meta_row.get("read_end")) + length = _safe_int(meta_row.get("read_length")) + if start is not None and end is not None and end > start: + return (start, end) + if start is not None and length is not None and length > 0: + return (start, start + length) + if end is not None and length is not None and length > 0: + return (end - length, end) + return None + + def _read_length(meta_row: dict[str, Any]) -> int | None: + length = _safe_int(meta_row.get("read_length")) + if length is not None and length > 0: + return length + bounds = _read_bounds(meta_row) + if bounds is None: + return None + return bounds[1] - bounds[0] + + def _window_is_covered( + meta_row: dict[str, Any], center_bp: float, half_width_bp: float + ) -> bool | None: + bounds = _read_bounds(meta_row) + if bounds is None: + return None + left = float(center_bp) - float(half_width_bp) + right = float(center_bp) + float(half_width_bp) + return left >= float(bounds[0]) and right <= float(bounds[1]) + + def _selection_spans_all_windows( + selected: Sequence[tuple[int, float, int]], + ) -> bool: + if not enforce_full_window_span: + return True + + unresolved_present = False + left_edges: list[float] = [] + right_edges: list[float] = [] + candidate_lengths: list[int] = [] + for row_idx, center_bp, window_pos in selected: + half_width_bp = float(panel_half_widths[window_pos]) + covered = _window_is_covered(meta[row_idx], center_bp, half_width_bp) + if covered is False: + return False + if covered is None: + unresolved_present = True + left_edges.append(float(center_bp) - half_width_bp) + right_edges.append(float(center_bp) + half_width_bp) + length_value = _read_length(meta[row_idx]) + if length_value is not None: + candidate_lengths.append(int(length_value)) + + if unresolved_present and candidate_lengths: + required_span = max(right_edges) - min(left_edges) + if max(candidate_lengths) < required_span: + return False + return True + + selection_result = _select_raster_site_windows( + meta, + original_indices=filtered_original_indices, + selector=selector, + distance_mode=distance_mode, + selection_spans_all_windows=_selection_spans_all_windows, + ) + window_indices = selection_result.window_indices + panel_centers = selection_result.panel_centers + dropped_for_window_span = int(selection_result.stats["dropped_for_window_span"]) + panels_by_motif = [] + panels_smooth_by_motif = [] + panels_ml_by_motif = [] + for motif_matrix in X_by_motif: + motif_panels = [motif_matrix[idx] for idx in window_indices] + panels_by_motif.append(motif_panels) + panels_smooth_by_motif.append([_smooth(panel) for panel in motif_panels]) + for motif_ml_matrix in ML_by_motif: + panels_ml_by_motif.append([motif_ml_matrix[idx] for idx in window_indices]) + + if render_values == "raw": + panels_render_by_motif = panels_by_motif + render_value_mode = "raw" + elif render_values == "smoothed": + panels_render_by_motif = panels_smooth_by_motif + render_value_mode = "smoothed" + else: + if render_mode == "heatmap" or smoothing is None: + panels_render_by_motif = panels_by_motif + render_value_mode = "raw" + else: + panels_render_by_motif = panels_smooth_by_motif + render_value_mode = "smoothed" + + # Sorting uses the reference motif to keep all motif panels aligned. + try: + ref_motif_pos = motif_ids.index(motif_index) + except ValueError: + ref_motif_pos = 0 + reference_panels = panels_render_by_motif[ref_motif_pos] + + if sort_window_index is None: + sort_window_pos = int(primary_window_index) + else: + sort_window_pos = int(sort_window_index) + if sort_window_pos >= n_windows_effective: + raise ValueError( + f"sort_window_index {sort_window_index} out of range for {n_windows_effective} windows." + ) + + if sort_descending is None: + effective_sort_descending = sort_by == "mod_fraction" + else: + effective_sort_descending = bool(sort_descending) + + # Sorting + if sort_by == "mod_fraction": + if sort_window_index is not None: + score = reference_panels[sort_window_pos].mean(axis=1) + order = np.argsort(score, kind="stable") + if effective_sort_descending: + order = order[::-1] + else: + means = [panel.mean(axis=1) for panel in reference_panels] + if effective_sort_descending: + order = np.lexsort(tuple(-m for m in reversed(means))) + else: + order = np.lexsort(tuple(m for m in reversed(means))) + elif sort_by == "cluster": + if sort_labels is None: + raise ValueError("sort_labels must be provided when sort_by='cluster'.") + labels_array = np.asarray(sort_labels) + if labels_array.shape[0] == len(read_windows.metadata): + labels_filtered = labels_array[filtered_original_indices] + elif labels_array.shape[0] == len(meta): + labels_filtered = labels_array + else: + raise ValueError( + "sort_labels must match either the original metadata length " + "or the filtered metadata length." + ) + pair_labels = labels_filtered[window_indices[0]] + order = np.argsort(pair_labels, kind="stable") + if effective_sort_descending: + order = order[::-1] + elif sort_by == "window_center": + center_values = panel_centers[sort_window_pos] + order = np.argsort(center_values, kind="stable") + if effective_sort_descending: + order = order[::-1] + elif sort_by == "read_name": + values = np.asarray( + [str(meta[idx].get("read_name", "")) for idx in window_indices[0]] + ) + order = np.argsort(values, kind="stable") + if effective_sort_descending: + order = order[::-1] + elif sort_by == "region_start": + values = np.asarray( + [int(meta[idx].get("region_start", 0) or 0) for idx in window_indices[0]], + dtype=float, + ) + order = np.argsort(values, kind="stable") + if effective_sort_descending: + order = order[::-1] + elif sort_by == "read_length": + + def _resolved_read_length(read_meta: dict) -> int: + length_value = read_meta.get("read_length") + if length_value is not None: + try: + return int(length_value) + except Exception: + pass + try: + return int(read_meta.get("read_end", 0)) - int( + read_meta.get("read_start", 0) + ) + except Exception: + return 0 + + values = np.asarray( + [_resolved_read_length(meta[idx]) for idx in window_indices[0]], dtype=float + ) + order = np.argsort(values, kind="stable") + if effective_sort_descending: + order = order[::-1] + elif sort_by == "none": + order = np.arange(reference_panels[0].shape[0], dtype=int) + else: + raise ValueError( + "sort_by must be one of 'mod_fraction', 'cluster', 'window_center', " + "'read_name', 'region_start', 'read_length', or 'none'." + ) + + panels_sorted_by_motif = [ + [panel[order] for panel in motif_panels] + for motif_panels in panels_render_by_motif + ] + panels_raw_sorted_by_motif = [ + [panel[order] for panel in motif_panels] for motif_panels in panels_by_motif + ] + panels_smooth_sorted_by_motif = [ + [panel[order] for panel in motif_panels] + for motif_panels in panels_smooth_by_motif + ] + panels_ml_sorted_by_motif: list[list[np.ndarray]] = [ + [panel[order] for panel in motif_panels] for motif_panels in panels_ml_by_motif + ] + expected_rows = panels_raw_sorted_by_motif[0][0].shape[0] + for motif_panels in panels_raw_sorted_by_motif: + for panel in motif_panels: + if panel.shape[0] != expected_rows: + raise RuntimeError( + "Inconsistent read ordering across multisite panels; panel row counts diverged." + ) + centers_sorted = [center_values[order] for center_values in panel_centers] + primary_centers = centers_sorted[int(primary_window_index)] + if effective_coordinate_mode == "local_window": + center_offsets = [ + np.zeros_like(center_values, dtype=float) + for center_values in centers_sorted + ] + else: + center_offsets = [ + center_values - primary_centers for center_values in centers_sorted + ] + if render_mode == "heatmap" and effective_coordinate_mode == "relative_to_primary": + for offsets in center_offsets: + if offsets.size > 1 and not np.allclose( + offsets, offsets[0], equal_nan=True + ): + raise ValueError( + "Heatmap rendering with coordinate_mode='relative_to_primary' requires constant " + "per-read offsets within each panel because imshow uses one rectangular extent. " + "Use render_mode='scatter', coordinate_mode='local_window', or fixed offsets " + "producing constant offsets." + ) + # Downsample rows if too tall + def bin_rows(M, k): + n_bins = math.ceil(M.shape[0] / k) + out = np.zeros((n_bins, M.shape[1])) + for i in range(n_bins): + s, e = i * k, min((i + 1) * k, M.shape[0]) + out[i] = M[s:e].mean(axis=0) + return out + + def bin_vector(vector: np.ndarray, k: int) -> np.ndarray: + n_bins = math.ceil(vector.shape[0] / k) + out = np.zeros(n_bins, dtype=float) + for i in range(n_bins): + s, e = i * k, min((i + 1) * k, vector.shape[0]) + out[i] = vector[s:e].mean() + return out + + P = panels_sorted_by_motif[0][0].shape[0] + rows_before_downsample = int(P) + effective_downsample_method = ( + ("uniform" if render_mode == "scatter" else "bin_mean") + if downsample_method == "auto" + else downsample_method + ) + downsampled = False + downsample_factor = 1 + if max_rows is not None and max_rows < P: + step = math.ceil(P / max_rows) + downsample_factor = step + if effective_downsample_method == "bin_mean": + panels_sorted_by_motif = [ + [bin_rows(panel, step) for panel in motif_panels] + for motif_panels in panels_sorted_by_motif + ] + panels_raw_sorted_by_motif = [ + [bin_rows(panel, step) for panel in motif_panels] + for motif_panels in panels_raw_sorted_by_motif + ] + panels_smooth_sorted_by_motif = [ + [bin_rows(panel, step) for panel in motif_panels] + for motif_panels in panels_smooth_sorted_by_motif + ] + panels_ml_sorted_by_motif = [ + [bin_rows(panel, step) for panel in motif_panels] + for motif_panels in panels_ml_sorted_by_motif + ] + center_offsets = [bin_vector(v, step) for v in center_offsets] + elif effective_downsample_method == "random": + rng = np.random.default_rng(downsample_seed) + keep = np.sort(rng.choice(P, size=max_rows, replace=False)) + panels_sorted_by_motif = [ + [panel[keep] for panel in motif_panels] + for motif_panels in panels_sorted_by_motif + ] + panels_raw_sorted_by_motif = [ + [panel[keep] for panel in motif_panels] + for motif_panels in panels_raw_sorted_by_motif + ] + panels_smooth_sorted_by_motif = [ + [panel[keep] for panel in motif_panels] + for motif_panels in panels_smooth_sorted_by_motif + ] + panels_ml_sorted_by_motif = [ + [panel[keep] for panel in motif_panels] + for motif_panels in panels_ml_sorted_by_motif + ] + center_offsets = [vector[keep] for vector in center_offsets] + else: + keep = np.unique(np.round(np.linspace(0, P - 1, max_rows)).astype(int)) + panels_sorted_by_motif = [ + [panel[keep] for panel in motif_panels] + for motif_panels in panels_sorted_by_motif + ] + panels_raw_sorted_by_motif = [ + [panel[keep] for panel in motif_panels] + for motif_panels in panels_raw_sorted_by_motif + ] + panels_smooth_sorted_by_motif = [ + [panel[keep] for panel in motif_panels] + for motif_panels in panels_smooth_sorted_by_motif + ] + panels_ml_sorted_by_motif = [ + [panel[keep] for panel in motif_panels] + for motif_panels in panels_ml_sorted_by_motif + ] + center_offsets = [vector[keep] for vector in center_offsets] + P = panels_sorted_by_motif[0][0].shape[0] + downsampled = True + + if scatter_color_values == "auto": + effective_scatter_color_values = "ml_score" + else: + effective_scatter_color_values = scatter_color_values + + threshold_map: dict[int, float] | None = None + if render_mode == "scatter" and ml_score_thresholds is not None: + thresholds_list = [float(value) for value in ml_score_thresholds] + if len(thresholds_list) == len(motif_ids): + threshold_map = { + motif_id: float(thresholds_list[idx]) + for idx, motif_id in enumerate(motif_ids) + } + elif len(thresholds_list) == n_motifs: + threshold_map = { + idx: float(value) for idx, value in enumerate(thresholds_list) + } + elif len(thresholds_list) == 1: + threshold_map = { + motif_id: float(thresholds_list[0]) for motif_id in motif_ids + } + else: + raise ValueError( + "ml_score_thresholds must have length 1, motif_count, or motifs_plotted length." + ) + + motif_color_map: dict[int, str] = {} + if threshold_map is not None: + if motif_colors is not None: + colors_list = [str(value) for value in motif_colors] + if len(colors_list) == len(motif_ids): + motif_color_map = { + motif_id: colors_list[idx] for idx, motif_id in enumerate(motif_ids) + } + elif len(colors_list) == n_motifs: + motif_color_map = { + motif_id: colors_list[motif_id] for motif_id in motif_ids + } + elif len(colors_list) == 1: + motif_color_map = {motif_id: colors_list[0] for motif_id in motif_ids} + else: + raise ValueError( + "motif_colors must have length 1, motif_count, or motifs_plotted length." + ) + else: + tab10 = plt.get_cmap("tab10") + motif_color_map = { + motif_id: tab10(idx % 10) for idx, motif_id in enumerate(motif_ids) + } + + def _default_ml_cmap_name(motif_label: str, motif_pos: int) -> str: + normalized = motif_label.strip().upper() + if normalized == "A,0": + return "Blues" + if normalized == "CG,0": + return "Oranges" + sequential_cycle = ( + "Greens", + "Purples", + "Reds", + "Greys", + "YlGnBu", + "BuPu", + "PuRd", + "YlOrBr", + "BuGn", + ) + return sequential_cycle[motif_pos % len(sequential_cycle)] + + ml_cmap_names = { + motif_id: _default_ml_cmap_name(str(motif_labels_resolved[motif_id]), motif_pos) + for motif_pos, motif_id in enumerate(motif_ids) + } + + if vmin is None or vmax is None: + if ( + render_mode == "scatter" + and threshold_map is not None + or render_mode == "scatter" + and effective_scatter_color_values == "ml_score" + ): + vmin = 0.0 if vmin is None else vmin + vmax = 1.0 if vmax is None else vmax + else: + vmax_auto = max( + panel.max() + for motif_panels in panels_sorted_by_motif + for panel in motif_panels + ) + vmin = 0.0 if vmin is None else vmin + vmax = vmax_auto if vmax is None else vmax + + x_positions = np.arange(slice_width, dtype=float) - (slice_width // 2) + + if axis_orientation is None: + if render_mode == "heatmap": + effective_axis_orientation = "position_y" if rotate else "position_x" + else: + effective_axis_orientation = "position_x" + else: + effective_axis_orientation = axis_orientation + + if effective_coordinate_mode == "relative_to_primary": + window_plot_order = sorted( + range(n_windows_effective), + key=lambda idx: float(np.nanmedian(center_offsets[idx])) + if center_offsets[idx].size + else 0.0, + reverse=bool(rotate and effective_axis_orientation == "position_y"), + ) + else: + window_plot_order = list(range(n_windows_effective)) + + share_x_axes = True + if effective_axis_orientation == "position_x" and n_windows_effective > 1: + # When each window has a different positional frame (relative-to-primary), + # sharing x limits forces misleading axis alignment across panels. + share_x_axes = False + share_y_axes = True + if effective_axis_orientation == "position_y" and n_windows_effective > 1: + # Same issue as above, but position is encoded on y. + share_y_axes = False + + n_plot_motifs = len(motif_ids) + if n_plot_motifs == 1: + if rotate: + fig, axes = plt.subplots( + n_windows_effective, + 1, + figsize=(max(6, slice_width * 0.01), 2.7 + n_windows_effective * 1.5), + sharex=share_x_axes, + ) + axes = np.atleast_1d(axes).reshape(n_windows_effective, 1) + else: + fig, axes = plt.subplots( + 1, + n_windows_effective, + figsize=(10, max(3, P * 0.06)), + sharey=share_y_axes, + ) + axes = np.atleast_1d(axes).reshape(1, n_windows_effective) + else: + if rotate: + n_rows, n_cols = n_windows_effective, n_plot_motifs + fig, axes = plt.subplots( + n_rows, + n_cols, + figsize=( + max(9, 4.4 * n_cols), + max( + 4.2, + 2.7 * n_rows + + (0.35 if render_mode == "heatmap" else 0) + + (0.7 if threshold_map is not None else 0), + ), + ), + sharex=share_x_axes, + sharey=share_y_axes, + ) + else: + n_rows, n_cols = n_plot_motifs, n_windows_effective + fig, axes = plt.subplots( + n_rows, + n_cols, + figsize=( + max(9, 4.2 * n_cols), + max(4.2, 2.5 * n_rows + (0.35 if render_mode == "heatmap" else 0)), + ), + sharey=share_y_axes, + ) + axes = np.atleast_2d(axes) + + def _axis_for(motif_pos: int, window_pos: int): + display_window_pos = window_plot_order.index(window_pos) + if len(motif_ids) == 1: + return axes[display_window_pos, 0] if rotate else axes[0, display_window_pos] + if rotate: + return axes[display_window_pos, motif_pos] + return axes[motif_pos, display_window_pos] + + first_mappable = None + first_mappable_by_motif: dict[int, Any] = {} + threshold_legend_entries: dict[str, tuple[str, float]] = {} + if threshold_map is not None: + threshold_legend_entries = { + str(motif_labels_resolved[motif_id]): ( + str(motif_color_map[motif_id]), + float(threshold_map[motif_id]), + ) + for motif_id in motif_ids + if motif_id in threshold_map + } + for motif_pos, motif_id in enumerate(motif_ids): + motif_panels = panels_sorted_by_motif[motif_pos] + motif_raw_panels = panels_raw_sorted_by_motif[motif_pos] + motif_smooth_panels = panels_smooth_sorted_by_motif[motif_pos] + motif_ml_panels = panels_ml_sorted_by_motif[motif_pos] + motif_label = str(motif_labels_resolved[motif_id]) + for window_pos in window_plot_order: + display_window_pos = window_plot_order.index(window_pos) + ax = _axis_for(motif_pos, window_pos) + panel = motif_panels[window_pos] + panel_raw = motif_raw_panels[window_pos] + panel_smooth = motif_smooth_panels[window_pos] + panel_ml = motif_ml_panels[window_pos] + row_offsets = center_offsets[window_pos] + axis_center = float(np.nanmedian(row_offsets)) if row_offsets.size else 0.0 + window_half_width = panel_half_widths[window_pos] + window_lo = axis_center - window_half_width + window_hi = axis_center + window_half_width + + if render_mode == "scatter": + rows, cols = np.nonzero(panel_raw > 0) + if ( + effective_scatter_color_values == "ml_score" + and panel_ml is not None + ): + color_panel = panel_ml + elif effective_scatter_color_values == "smoothed": + color_panel = panel_smooth + else: + color_panel = panel_raw + values = color_panel[rows, cols] + if threshold_map is not None: + threshold_value = float(threshold_map[motif_id]) + keep = panel_ml[rows, cols] >= threshold_value + rows = rows[keep] + cols = cols[keep] + values = panel_ml[rows, cols] + if rows.size > 0: + x_scatter = x_positions[cols] + row_offsets[rows] + in_window = (x_scatter >= window_lo) & (x_scatter <= window_hi) + rows = rows[in_window] + cols = cols[in_window] + values = values[in_window] + x_scatter = x_scatter[in_window] + if rows.size > 0: + if effective_axis_orientation == "position_x": + if threshold_map is None: + scatter_cmap = ( + ml_cmap_names[motif_id] + if effective_scatter_color_values == "ml_score" + else cmap + ) + first_mappable = ax.scatter( + x_scatter, + rows, + c=values, + s=scatter_size, + alpha=scatter_alpha, + cmap=scatter_cmap, + vmin=vmin, + vmax=vmax, + linewidths=0, + rasterized=True, + ) + first_mappable_by_motif.setdefault(motif_id, first_mappable) + else: + ax.scatter( + x_scatter, + rows, + c=motif_color_map[motif_id], + s=scatter_size, + alpha=scatter_alpha, + linewidths=0, + rasterized=True, + ) + x_min = float(np.nanmin(x_scatter)) + x_max = float(np.nanmax(x_scatter)) + else: + if threshold_map is None: + scatter_cmap = ( + ml_cmap_names[motif_id] + if effective_scatter_color_values == "ml_score" + else cmap + ) + first_mappable = ax.scatter( + rows, + x_scatter, + c=values, + s=scatter_size, + alpha=scatter_alpha, + cmap=scatter_cmap, + vmin=vmin, + vmax=vmax, + linewidths=0, + rasterized=True, + ) + first_mappable_by_motif.setdefault(motif_id, first_mappable) + else: + ax.scatter( + rows, + x_scatter, + c=motif_color_map[motif_id], + s=scatter_size, + alpha=scatter_alpha, + linewidths=0, + rasterized=True, + ) + y_min = float(np.nanmin(x_scatter)) + y_max = float(np.nanmax(x_scatter)) + else: + if effective_axis_orientation == "position_x": + x_min = float(window_lo) + x_max = float(window_hi) + else: + y_min = float(window_lo) + y_max = float(window_hi) + if effective_axis_orientation == "position_x": + x_min = min(x_min, float(window_lo)) + x_max = max(x_max, float(window_hi)) + x_pad = max(1.0, 0.02 * (window_hi - window_lo + 1.0)) + ax.axvline(axis_center, linestyle="--", linewidth=1, color="0.7") + ax.set_ylim(-1, panel.shape[0] + 1) + ax.set_xlim(window_lo - x_pad, window_hi + x_pad) + else: + y_min = min(y_min, float(window_lo)) + y_max = max(y_max, float(window_hi)) + y_pad = max(1.0, 0.02 * (window_hi - window_lo + 1.0)) + ax.axhline(axis_center, linestyle="--", linewidth=1, color="0.7") + ax.set_xlim(-1, panel.shape[0] + 1) + ax.set_ylim(window_lo - y_pad, window_hi + y_pad) + else: + x_lo = float(x_positions[0] + axis_center) + x_hi = float(x_positions[-1] + axis_center) + if effective_axis_orientation == "position_y": + first_mappable = ax.imshow( + panel.T, + aspect="auto", + origin="lower", + extent=[-0.5, panel.shape[0] - 0.5, x_lo, x_hi], + vmin=vmin, + vmax=vmax, + cmap=cmap, + ) + ax.axhline(axis_center, linestyle="--", linewidth=1, color="0.7") + y_pad = max(1.0, 0.02 * (window_hi - window_lo + 1.0)) + ax.set_ylim(window_lo - y_pad, window_hi + y_pad) + else: + first_mappable = ax.imshow( + panel, + aspect="auto", + origin="upper", + extent=[x_lo, x_hi, panel.shape[0] - 0.5, -0.5], + vmin=vmin, + vmax=vmax, + cmap=cmap, + ) + ax.axvline(axis_center, linestyle="--", linewidth=1, color="0.7") + x_pad = max(1.0, 0.02 * (window_hi - window_lo + 1.0)) + ax.set_xlim(window_lo - x_pad, window_hi + x_pad) + + if window_pos == primary_window_index: + for spine in ax.spines.values(): + spine.set_linewidth(2.2) + spine.set_edgecolor("black") + + if render_mode == "heatmap": + site_label = f"Site {window_pos + 1}" + if window_pos == primary_window_index: + site_label += " (primary)" + heatmap_title = ( + f"{motif_label} | {site_label}" + if len(motif_ids) > 1 + else site_label + ) + ax.set_title(heatmap_title, fontsize=9) + elif len(motif_ids) > 1: + if rotate and display_window_pos == 0: + ax.set_title(motif_label) + if not rotate and motif_pos == 0: + ax.set_title(f"Site {window_pos + 1}") + + if effective_axis_orientation == "position_x": + if rotate and motif_pos == 0: + ax.set_ylabel("Reads (sorted)") + if (not rotate) and len(motif_ids) > 1 and window_pos == 0: + ax.set_ylabel(f"{motif_label}\nReads") + else: + if rotate and motif_pos == 0: + ax.set_ylabel("Position (bp)") + if (not rotate) and len(motif_ids) > 1 and window_pos == 0: + ax.set_ylabel(f"{motif_label}\nPosition") + + position_label = ( + "Position relative to primary center (bp)" + if effective_coordinate_mode == "relative_to_primary" + else "Distance from window center (bp)" + ) + if rotate: + bottom_window = window_plot_order[-1] + bottom_axes = [ + _axis_for(motif_pos, bottom_window) + for motif_pos in range(len(motif_ids)) + ] + xlabel = ( + position_label + if effective_axis_orientation == "position_x" + else "Reads (sorted)" + ) + for ax in bottom_axes: + ax.set_xlabel(xlabel) + else: + for motif_pos in range(len(motif_ids)): + left_window = window_plot_order[0] + if effective_axis_orientation == "position_x": + _axis_for(motif_pos, left_window).set_ylabel( + "Reads (sorted)" + if len(motif_ids) == 1 + else _axis_for(motif_pos, left_window).get_ylabel() + ) + else: + _axis_for(motif_pos, left_window).set_ylabel( + "Position (bp)" + if len(motif_ids) == 1 + else _axis_for(motif_pos, left_window).get_ylabel() + ) + for window_pos in range(n_windows_effective): + if effective_axis_orientation == "position_x": + _axis_for(motif_pos, window_pos).set_xlabel( + position_label + ) + else: + _axis_for(motif_pos, window_pos).set_xlabel("Reads (sorted)") + + sort_target = f"Site {sort_window_pos + 1}" + if sort_window_pos == primary_window_index: + sort_target += " (primary)" + selection_label = selector.mode.replace("_", " ") + title_parts = [ + f"{selection_label} raster", + f"primary Site {primary_window_index + 1} shown with bold border", + f"sorted by {sort_by} in {sort_target}", + f"{effective_coordinate_mode}, {effective_axis_orientation}", + f"rows {panels_sorted_by_motif[0][0].shape[0]} / {rows_before_downsample}", + ] + fig.suptitle(" | ".join(title_parts), fontsize=9, y=0.985) + top_margin = 0.70 if threshold_map is not None else 0.88 + if render_mode == "heatmap": + top_margin -= 0.03 + left_margin = 0.16 if rotate and n_windows_effective > 1 else 0.10 + fig.subplots_adjust( + top=top_margin, + right=0.88, + left=left_margin, + hspace=0.45, + wspace=0.35, + ) + + if rotate and n_windows_effective > 1: + for window_pos in window_plot_order: + row_axis = _axis_for(0, window_pos) + bbox = row_axis.get_position() + site_label = f"Site {window_pos + 1}" + if window_pos == primary_window_index: + site_label += " (primary)" + fig.text( + max(0.01, bbox.x0 - 0.105), + (bbox.y0 + bbox.y1) / 2, + site_label, + rotation=90, + va="center", + ha="center", + fontsize=9, + fontweight="bold" if window_pos == primary_window_index else "normal", + ) + + if threshold_map is None: + if first_mappable is None: + first_axis = _axis_for(0, 0) + fallback_cmap = ( + ml_cmap_names[motif_ids[0]] + if effective_scatter_color_values == "ml_score" + else cmap + ) + first_mappable = first_axis.scatter( + [], [], c=[], cmap=fallback_cmap, vmin=vmin, vmax=vmax + ) + sigma_txt = f", σ={smooth_sigma_bp}" if smoothing == "gaussian" else "" + if ( + render_mode == "scatter" + and effective_scatter_color_values == "ml_score" + and len(motif_ids) > 1 + ): + for motif_id in motif_ids: + motif_pos = motif_ids.index(motif_id) + if motif_id not in first_mappable_by_motif: + first_mappable_by_motif[motif_id] = _axis_for(motif_pos, 0).scatter( + [], + [], + c=[], + cmap=ml_cmap_names[motif_id], + vmin=vmin, + vmax=vmax, + ) + cbar = fig.colorbar( + first_mappable_by_motif[motif_id], + ax=[ + _axis_for(motif_pos, window_pos) + for window_pos in range(n_windows_effective) + ], + shrink=0.6, + pad=0.06, + ) + cbar.set_label( + f"{motif_labels_resolved[motif_id]} normalized ML score (0-1)\n" + f"{'downsampled' if downsampled else 'full'}" + ) + else: + cbar = fig.colorbar( + first_mappable, ax=np.ravel(axes).tolist(), shrink=0.6, pad=0.06 + ) + if ( + render_mode == "scatter" + and effective_scatter_color_values == "ml_score" + ): + cbar.set_label( + f"normalized ML score (0-1)\n{'downsampled' if downsampled else 'full'}" + ) + elif render_mode == "scatter": + scale_label = "fraction modified signal" + smoothing_label = ( + f"{smoothing}, win={smooth_win}{sigma_txt}" + if render_value_mode == "smoothed" + else "none" + ) + cbar.set_label( + f"{scale_label} (values={render_value_mode}, smoother={smoothing_label})\n" + f"{'downsampled' if downsampled else 'full'}" + ) + else: + scale_label = "Signal heatmap" + smoothing_label = ( + f"{smoothing} win={smooth_win}{sigma_txt}" + if render_value_mode == "smoothed" + else "none" + ) + cbar.set_label( + f"{scale_label}\n" + f"{render_value_mode}; {smoothing_label}\n" + f"{'downsampled' if downsampled else 'full'}" + ) + else: + from matplotlib.lines import Line2D + + handles = [ + Line2D( + [0], + [0], + marker="o", + linestyle="", + color="none", + markerfacecolor=color, + markeredgecolor=color, + markersize=6, + label=f"{label} (ML >= {threshold:.2f})", + alpha=scatter_alpha, + ) + for label, (color, threshold) in threshold_legend_entries.items() + ] + fig.legend( + handles=handles, + title="Motif thresholds", + loc="upper center", + bbox_to_anchor=(0.5, 0.86), + ncol=max(1, len(handles)), + frameon=False, + ) + + return fig, { + "pairs": panels_sorted_by_motif[0][0].shape[0], + **selection_result.stats, + "downsampled": downsampled, + "downsample_method": (effective_downsample_method if downsampled else "none"), + "downsample_factor": int(downsample_factor), + "rows_before_downsample": rows_before_downsample, + "rows_after_downsample": int(panels_sorted_by_motif[0][0].shape[0]), + "vmin": vmin, + "vmax": vmax, + "render_mode": render_mode, + "render_values": render_value_mode, + "scatter_color_values": effective_scatter_color_values, + "axis_orientation": effective_axis_orientation, + "coordinate_mode": effective_coordinate_mode, + "window_plot_order": [int(idx) for idx in window_plot_order], + "sort_by": sort_by, + "sort_window_index": sort_window_pos, + "sort_descending": effective_sort_descending, + "motifs_plotted": [motif_labels_resolved[m] for m in motif_ids], + "n_windows": n_windows_effective, + "selection_mode": selector.mode, + "primary_window_index": int(primary_window_index), + "window_offsets_bp": target_offsets.tolist() if target_offsets is not None else None, + "window_widths_bp": [int(value) for value in panel_widths_bp], + "required_read_length_bp": required_read_length_bp, + "min_read_length_bp": effective_min_read_length, + "enforce_full_window_span": bool(enforce_full_window_span), + "dropped_for_window_span": int(dropped_for_window_span), + "read_order_consistent": True, + "ml_score_thresholds": ( + None + if threshold_map is None + else { + motif_labels_resolved[key]: value + for key, value in threshold_map.items() + if key in motif_ids + } + ), + "ml_score_cmaps": ( + { + motif_labels_resolved[motif_id]: ml_cmap_names[motif_id] + for motif_id in motif_ids + } + if threshold_map is None and effective_scatter_color_values == "ml_score" + else None + ), + } + + +def plot_two_site_read_raster( + read_windows: ReadWindowExtractionResult, + *, + second_site_offset_bp: int, + window_width_bp: int | None = None, + max_rows: int | None = 500, + downsample_method: str = "auto", + downsample_seed: int | None = None, + **kwargs, +): + """ + Convenience wrapper for two-site raster plots in primary-region coordinates. + + Args: + read_windows: ReadWindowExtractionResult from extract_read_windows / build_multimotif_read_windows + second_site_offset_bp: secondary site center (bp) relative to the primary site at 0 + window_width_bp: common width (bp) applied to both windows when provided + max_rows: cap displayed reads; pass None to show all rows + downsample_method: "auto" uses uniform read sampling for scatter and bin-mean for heatmap + downsample_seed: seed for reproducible random downsampling + **kwargs: forwarded to plot_multisite_read_raster + """ + + if ( + "window_offsets_bp" in kwargs + or "selection_mode" in kwargs + or "site_selection" in kwargs + ): + raise ValueError( + "plot_two_site_read_raster manages fixed offsets internally; do not pass window_offsets_bp, selection_mode, or site_selection." + ) + if "n_windows" in kwargs: + raise ValueError("plot_two_site_read_raster always uses exactly two windows.") + if "primary_window_index" in kwargs: + raise ValueError("plot_two_site_read_raster always uses window 1 as primary.") + + forwarded: dict[str, Any] = dict(kwargs) + forwarded["max_rows"] = max_rows + forwarded["downsample_method"] = downsample_method + forwarded["downsample_seed"] = downsample_seed + forwarded["site_selection"] = { + "mode": "fixed_offsets", + "window_offsets_bp": [0, int(second_site_offset_bp)], + "primary_window_index": 0, + } + if window_width_bp is not None: + forwarded["window_widths_bp"] = [int(window_width_bp), int(window_width_bp)] + return plot_multisite_read_raster(read_windows, n_windows=2, **forwarded) + + +def plot_region_cluster_profiles( + pileup_matrix: np.ndarray, + labels: np.ndarray, + *, + window_size: int | None = None, + motif_index: int | None = None, + motif_count: int | None = None, + plot_all_motifs: bool = False, + motif_labels: Sequence[str] | None = None, + motif_colors: Sequence[str] | None = None, + motif_profile_mode: str = "single_axis", + color_points_by: str = "cluster", + point_size: float = 1.0, + point_alpha: float = 0.01, + smoothing: str | None = None, + smooth_win: int = 21, + smooth_sigma: float = 6.0, + show_unsmoothed_overlay: bool = False, + invert_y: bool = True, + cmap_name: str = "viridis", +): + """ + Visualize region-level clustering with a sorted matrix view and side profiles. + Supports concatenated multi-motif matrices with shared or separate profile axes. + + Args: + pileup_matrix: 2D array (n_regions, region_length) of modification fractions + labels: cluster assignments per region + window_size: optional half-window in bp for axis scaling and motif-slice inference + (full span = ``2 * window_size``). + motif_index: if pileup_matrix is a concatenation of multiple motifs, select which motif slice to plot + motif_count: total number of motifs concatenated; if None, inferred when possible + plot_all_motifs: if True and motifs are concatenated, plot all motif slices per cluster + cmap_name: matplotlib colormap name for clusters + """ + + import matplotlib.pyplot as plt + from matplotlib import gridspec + from matplotlib.colors import Normalize + from matplotlib.lines import Line2D + + X_full = np.asarray(pileup_matrix) + labs = np.asarray(labels) + if X_full.shape[0] != labs.shape[0]: + raise ValueError("pileup_matrix rows and labels must have the same length.") + if motif_profile_mode not in {"single_axis", "separate_axes"}: + raise ValueError("motif_profile_mode must be 'single_axis' or 'separate_axes'.") + if color_points_by not in {"cluster", "motif"}: + raise ValueError("color_points_by must be 'cluster' or 'motif'.") + + region_len = X_full.shape[1] + motif_idx = motif_index if motif_index is not None else 0 + n_motifs, slice_width = _resolve_motif_slices( + region_len, + motif_count=motif_count, + motif_labels=motif_labels, + window_size=window_size, + ) + if motif_idx < 0 or motif_idx >= n_motifs: + raise ValueError( + f"motif_index {motif_idx} out of range for {n_motifs} motif slices." + ) + motif_ids = ( + list(range(n_motifs)) if plot_all_motifs and n_motifs > 1 else [motif_idx] + ) + + if motif_labels is None: + motif_labels = [f"motif_{i}" for i in range(n_motifs)] + else: + motif_labels = list(motif_labels) + if len(motif_labels) < n_motifs: + motif_labels.extend( + [f"motif_{i}" for i in range(len(motif_labels), n_motifs)] + ) + if motif_colors is None: + motif_cmap = plt.get_cmap("tab10") + motif_colors = [motif_cmap(i % 10) for i in range(n_motifs)] + else: + motif_colors = list(motif_colors) + if len(motif_colors) < n_motifs: + motif_cmap = plt.get_cmap("tab10") + motif_colors.extend( + motif_cmap(i % 10) for i in range(len(motif_colors), n_motifs) + ) + + primary_start = motif_idx * slice_width + primary_end = min(region_len, primary_start + slice_width) + primary_matrix = X_full[:, primary_start:primary_end] + x_axis = _centered_x_axis( + primary_matrix.shape[1], + _window_span_from_size(window_size) + if window_size is not None + else primary_matrix.shape[1], + ) + + labs_arr, lab_codes, unique_codes = _prepare_group_labels(labs) + order = np.lexsort((-primary_matrix.mean(axis=1), lab_codes)) + X_sorted_full = X_full[order] + lab_codes_sorted = lab_codes[order] + + unique_labels = np.array( + [np.unique(labs_arr[lab_codes == code])[0] for code in unique_codes] + ) + cmap = plt.get_cmap(cmap_name) + norm = Normalize(vmin=int(unique_codes.min()), vmax=int(unique_codes.max() or 1)) + + fig = plt.figure(figsize=(12, max(4, len(unique_codes)))) + gs = gridspec.GridSpec(nrows=len(unique_codes), ncols=2, width_ratios=[3, 1]) + ax_left = fig.add_subplot(gs[:, 0]) + + # Left panel: scatter only (parallel to read-clustering style) + for motif_id in motif_ids: + s = motif_id * slice_width + e = min(region_len, s + slice_width) + matrix_view = X_sorted_full[:, s:e] + rows, cols = np.nonzero(matrix_view) + if color_points_by == "cluster": + colors = cmap(norm(lab_codes_sorted[rows])) + else: + colors = motif_colors[motif_id] + ax_left.scatter( + x_axis[cols], + rows, + s=point_size, + alpha=point_alpha, + c=colors, + rasterized=True, + ) + + ax_left.set_xlabel("Distance from region center (bp)") + ax_left.set_ylabel("Region (sorted)") + if invert_y: + ax_left.invert_yaxis() + change_points = np.flatnonzero(np.diff(lab_codes_sorted)) + 1 + for cp in change_points: + ax_left.axhline(cp, color="0.2", linestyle="--", linewidth=0.35) + + for i, code in enumerate(unique_codes): + ax = fig.add_subplot(gs[i, 1]) + row_mask = lab_codes_sorted == code + panel_max = 0.0 + if motif_profile_mode == "separate_axes" and len(motif_ids) > 1: + axes_for_motifs = [ax] + motif_maxima = [0.0 for _ in motif_ids] + for motif_pos in range(1, len(motif_ids)): + twin = ax.twinx() + twin.spines["right"].set_position(("outward", 35 * motif_pos)) + axes_for_motifs.append(twin) + for motif_pos, motif_id in enumerate(motif_ids): + s = motif_id * slice_width + e = min(region_len, s + slice_width) + mean_profile_raw = X_sorted_full[row_mask, s:e].mean(axis=0) + mean_profile = _smooth_profile_vector( + mean_profile_raw, + smoothing=smoothing, + smooth_win=smooth_win, + smooth_sigma=smooth_sigma, + ) + motif_maxima[motif_pos] = max( + motif_maxima[motif_pos], float(np.nanmax(mean_profile_raw)) + ) + panel_max = max(panel_max, motif_maxima[motif_pos]) + motif_ax = axes_for_motifs[motif_pos] + if show_unsmoothed_overlay and smoothing is not None: + motif_ax.plot( + x_axis, + mean_profile_raw, + color=motif_colors[motif_id], + linewidth=1.0, + alpha=0.25, + ) + motif_ax.plot( + x_axis, + mean_profile, + color=motif_colors[motif_id], + linewidth=1.5, + ) + motif_ax.set_ylabel( + str(motif_labels[motif_id]), color=motif_colors[motif_id] + ) + motif_ax.tick_params( + axis="y", colors=motif_colors[motif_id], labelsize=8 + ) + for motif_pos, motif_ax in enumerate(axes_for_motifs): + motif_ax.set_ylim(0, max(motif_maxima[motif_pos], 0.05) * 1.05) + else: + for motif_id in motif_ids: + s = motif_id * slice_width + e = min(region_len, s + slice_width) + mean_profile_raw = X_sorted_full[row_mask, s:e].mean(axis=0) + mean_profile = _smooth_profile_vector( + mean_profile_raw, + smoothing=smoothing, + smooth_win=smooth_win, + smooth_sigma=smooth_sigma, + ) + panel_max = max(panel_max, float(np.nanmax(mean_profile_raw))) + if show_unsmoothed_overlay and smoothing is not None: + ax.plot( + x_axis, + mean_profile_raw, + color=motif_colors[motif_id] + if len(motif_ids) > 1 + else cmap(norm(code)), + linewidth=1.0, + alpha=0.25, + ) + ax.plot( + x_axis, + mean_profile, + color=motif_colors[motif_id] + if len(motif_ids) > 1 + else cmap(norm(code)), + linewidth=1.5, + ) + ax.set_title(f"{unique_labels[i]} (n={row_mask.sum()})") + ax.set_xlim(x_axis[0], x_axis[-1]) + y_max = max(panel_max, 0.05) + ax.set_ylim(0, y_max * 1.05) + + if len(motif_ids) > 1 or color_points_by == "motif": + handles = [ + Line2D( + [0], [0], color=motif_colors[m], linewidth=2, label=str(motif_labels[m]) + ) + for m in motif_ids + ] + fig.legend( + handles=handles, + loc="upper center", + ncol=min(4, len(handles)), + frameon=False, + bbox_to_anchor=(0.5, 1.01), + ) + + fig.tight_layout() + return fig + + +def export_region_clusters_to_bed( + region_metadata: Sequence[RegionMetadata], + labels: Sequence[int], + output_path: str | Path, + *, + name_prefix: str = "cluster", + include_header: bool = False, +): + """ + Export region cluster assignments as a BED-like file. + + Args: + region_metadata: iterable of (chrom, start, end, strand) tuples, typically from region_feature_matrix_from_pileup + labels: cluster labels aligned to region_metadata + output_path: where to write the BED + name_prefix: prefix for the BED name field; final name becomes f\"{prefix}_{label}\" + include_header: if True, writes a comment header line + """ + + if len(region_metadata) != len(labels): + raise ValueError("region_metadata and labels must have the same length.") + + output_path = Path(output_path) + lines = [] + for (chrom, start, end, strand), label in zip( + region_metadata, labels, strict=False + ): + name = f"{name_prefix}_{label}" + score = str(label) + strand_field = strand if strand in {"+", "-"} else "." + lines.append(f"{chrom}\t{start}\t{end}\t{name}\t{score}\t{strand_field}\n") + + with output_path.open("w") as fh: + if include_header: + fh.write(f"# chrom\tstart\tend\tname({name_prefix}_id)\tscore\tstrand\n") + fh.writelines(lines) + + return output_path + + +def plot_cluster_karyotype( + region_bed: str | Path, + chrom_sizes: str | Path, + *, + cmap_name: str = "tab10", + linewidth: float = 4.0, + figsize_per_chrom: float = 0.7, + min_visible_bp: int = 50_000, + min_visible_fraction: float = 0.002, + chromosome_order: str | Sequence[str] = "length_desc", + invert_position_axis: bool = True, + detect_haplotype_backbone_shading: bool = True, + show_position_axis: bool = False, + coordinate_label_stagger: bool = True, +): + """ + Plot cluster-labeled regions along chromosomes (ideogram-style), colored by cluster. + + Args: + region_bed: BED file, typically from export_region_clusters_to_bed, with Name carrying cluster id + chrom_sizes: 2-column TSV (chrom, length) e.g. ref.fasta.fai + cmap_name: matplotlib colormap to map cluster ids to colors + linewidth: thickness of region segments + figsize_per_chrom: vertical size per chromosome for sizing the figure + min_visible_bp: minimum displayed region length in bp for visibility + min_visible_fraction: minimum displayed region length as a fraction of chromosome length + chromosome_order: "length_desc", "length_asc", "natural", or explicit ordered chromosome list + invert_position_axis: if True, chromosome position increases downward + detect_haplotype_backbone_shading: if True, apply subtle maternal/paternal-like + backbone shading when paired haplotype naming is detected + show_position_axis: if True, show y-axis ticks/label; default hides them for cleaner ideogram view + coordinate_label_stagger: if True, alternate end-label vertical offsets to reduce overlap + """ + import re + + import matplotlib.pyplot as plt + from matplotlib.colors import Normalize + + clusters_df = pd.read_csv( + region_bed, + sep="\t", + comment="#", + header=None, + usecols=[0, 1, 2, 3], + names=["Chromosome", "Start", "End", "Name"], + ) + if clusters_df.empty: + raise ValueError(f"No records found in region_bed: {region_bed}") + chrom_df = pd.read_csv( + chrom_sizes, + sep="\t", + header=None, + usecols=[0, 1], + names=["Chromosome", "Length"], + ) + if chrom_df.empty: + raise ValueError(f"No chromosome sizes found in: {chrom_sizes}") + df = clusters_df.merge(chrom_df, on="Chromosome", how="inner") + if df.empty: + raise ValueError("No overlapping chromosomes between BED and chrom_sizes.") + + df["Start"] = pd.to_numeric(df["Start"], errors="coerce") + df["End"] = pd.to_numeric(df["End"], errors="coerce") + df["Length"] = pd.to_numeric(df["Length"], errors="coerce") + df = df.dropna(subset=["Start", "End", "Length"]).copy() + if df.empty: + raise ValueError("No valid numeric coordinates after parsing BED/chrom sizes.") + + def _natural_chrom_key(chrom: str): + text = str(chrom) + match = re.match(r"^chr(\d+)$", text, flags=re.IGNORECASE) + if match: + return (0, int(match.group(1)), text) + if text.lower() in {"chrx", "x"}: + return (1, 23, text) + if text.lower() in {"chry", "y"}: + return (1, 24, text) + if text.lower() in {"chrm", "mt", "m"}: + return (1, 25, text) + return (2, text.lower(), text) + + def _split_haplotype(chrom: str) -> tuple[str, str | None]: + name = str(chrom) + patterns = ( + r"^(?P.+?)[._-](?Pmat|pat|maternal|paternal)$", + r"^(?P.+?)[._-](?Phap1|hap2)$", + ) + for pattern in patterns: + m = re.match(pattern, name, flags=re.IGNORECASE) + if m: + return (m.group("base"), m.group("hap").lower()) + return (name, None) + + parsed_cluster = pd.to_numeric( + df["Name"].astype(str).str.extract(r"(\d+)$", expand=False), + errors="coerce", + ) + if parsed_cluster.isna().all(): + df["cluster_id"] = pd.factorize(df["Name"].astype(str))[0] + else: + fallback = pd.factorize(df["Name"].astype(str))[0] + df["cluster_id"] = parsed_cluster.fillna( + pd.Series(fallback, index=df.index) + ).astype(int) + + chrom_sizes_series = df.groupby("Chromosome")["Length"].max() + if isinstance(chromosome_order, (list, tuple, pd.Index, np.ndarray)): + requested = [str(chrom) for chrom in chromosome_order] + observed = chrom_sizes_series.index.astype(str).tolist() + chrom_order = [chrom for chrom in requested if chrom in observed] + chrom_order.extend([chrom for chrom in observed if chrom not in chrom_order]) + else: + order_mode = str(chromosome_order).lower() + if order_mode == "length_asc": + chrom_order = ( + chrom_sizes_series.sort_values(ascending=True) + .index.astype(str) + .tolist() + ) + elif order_mode == "natural": + chrom_order = sorted( + chrom_sizes_series.index.astype(str).tolist(), key=_natural_chrom_key + ) + else: + chrom_order = ( + chrom_sizes_series.sort_values(ascending=False) + .index.astype(str) + .tolist() + ) + + haplotype_pairs: dict[str, set[str]] = {} + if detect_haplotype_backbone_shading: + for chrom in chrom_order: + base, hap = _split_haplotype(chrom) + if hap is None: + continue + haplotype_pairs.setdefault(base, set()).add(hap) + + def _backbone_color(chrom: str) -> str: + if not detect_haplotype_backbone_shading: + return "0.75" + base, hap = _split_haplotype(chrom) + if hap is None: + return "0.75" + pair = haplotype_pairs.get(base, set()) + # Only apply shading when we detect at least a pair for the same base. + if len(pair) < 2: + return "0.75" + if hap in {"maternal", "mat", "hap1"}: + return "0.66" + if hap in {"paternal", "pat", "hap2"}: + return "0.84" + return "0.75" + + cmap = plt.get_cmap(cmap_name) + unique_clusters = np.sort(df["cluster_id"].unique()) + if hasattr(cmap, "colors") and len(cmap.colors) > 0: + color_cycle = list(cmap.colors) + cluster_to_color = { + int(cid): color_cycle[i % len(color_cycle)] + for i, cid in enumerate(unique_clusters) + } + else: + norm = Normalize(vmin=0, vmax=max(1, len(unique_clusters) - 1)) + cluster_to_color = { + int(cid): cmap(norm(i)) for i, cid in enumerate(unique_clusters) + } + + fig_height = max(3.0, figsize_per_chrom * len(chrom_order)) + fig, ax = plt.subplots(figsize=(max(8, len(chrom_order) * 0.7), fig_height + 1.5)) + max_length = float(df["Length"].max()) + x_positions = np.arange(len(chrom_order), dtype=float) + cluster_counts = df["cluster_id"].value_counts().to_dict() + # Draw dense clusters first and rarer clusters on top for visibility. + draw_order = sorted( + unique_clusters, + key=lambda cid: int(cluster_counts.get(int(cid), 0)), + reverse=True, + ) + + for xi, chrom in zip(x_positions, chrom_order, strict=False): + chrom_len = float(df.loc[df["Chromosome"] == chrom, "Length"].max()) + # Backbone for full chromosome (proportional in bp units) + ax.plot( + [xi, xi], + [0, chrom_len], + color=_backbone_color(chrom), + lw=linewidth + 1.5, + alpha=0.7, + solid_capstyle="butt", + zorder=1, + ) + # Cluster-assigned regions + sub = df[df["Chromosome"] == chrom].copy() + for z_rank, cid in enumerate(draw_order, start=2): + sub_cluster = sub[sub["cluster_id"] == int(cid)] + if sub_cluster.empty: + continue + for _, row in sub_cluster.iterrows(): + start_bp = float(row["Start"]) + end_bp = float(row["End"]) + region_len = max(0.0, end_bp - start_bp) + min_len = max( + float(min_visible_bp), chrom_len * float(min_visible_fraction) + ) + if region_len < min_len: + center = 0.5 * (start_bp + end_bp) + start_bp = max(0.0, center - (0.5 * min_len)) + end_bp = min(chrom_len, center + (0.5 * min_len)) + ax.plot( + [xi, xi], + [start_bp, end_bp], + color=cluster_to_color[int(row.cluster_id)], + lw=linewidth + 2.0, + alpha=0.98, + solid_capstyle="butt", + zorder=z_rank, + ) + + # Per-chromosome coordinate labels centered on each ideogram. + # Use per-chromosome padding (not global max length) so labels stay close. + x_label = xi + y_pad = max(1.0, 0.0015 * chrom_len) + if coordinate_label_stagger: + bottom_extra = 0.0 if (int(xi) % 2 == 0) else (0.6 * y_pad) + else: + bottom_extra = 0.0 + if invert_position_axis: + top_label_y = -y_pad + bottom_label_y = chrom_len + y_pad + bottom_extra + top_label_text = "0" + bottom_label_text = f"{int(chrom_len):,}" + else: + top_label_y = chrom_len + y_pad + bottom_extra + bottom_label_y = -y_pad + top_label_text = f"{int(chrom_len):,}" + bottom_label_text = "0" + ax.text( + x_label, + top_label_y, + top_label_text, + ha="center", + va="bottom", + fontsize=8, + color="0.35", + bbox={"facecolor": "white", "edgecolor": "none", "alpha": 0.75, "pad": 0.2}, + zorder=4, + ) + ax.text( + x_label, + bottom_label_y, + bottom_label_text, + ha="center", + va="top", + fontsize=8, + color="0.35", + bbox={"facecolor": "white", "edgecolor": "none", "alpha": 0.75, "pad": 0.2}, + zorder=4, + ) + + ax.set_xlim(-0.7, len(chrom_order) - 0.3) + if invert_position_axis: + ax.set_ylim(1.08 * max_length, -0.1 * max_length) + else: + ax.set_ylim(-0.1 * max_length, 1.08 * max_length) + ax.set_xticks(x_positions) + ax.set_xticklabels(chrom_order, rotation=45, ha="right") + if show_position_axis: + ax.set_ylabel("Position (bp)") + else: + ax.set_ylabel("") + ax.yaxis.set_visible(False) + ax.spines["left"].set_visible(False) + ax.set_title("Clustered Regions by Chromosome (Vertical, Proportional Length)") + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + from matplotlib.lines import Line2D + + legend_handles = [ + Line2D( + [0], + [0], + color=cluster_to_color[int(cid)], + linewidth=3, + label=f"C{int(cid)}", + ) + for cid in unique_clusters + ] + ax.legend( + handles=legend_handles, + title="Cluster", + loc="upper right", + frameon=False, + ncol=min(4, len(legend_handles)), + ) + plt.tight_layout() + return fig + + +def summarize_region_cluster_annotations( + region_metadata: Sequence[RegionMetadata], + labels: Sequence[int], + annotation_file: str | Path, + *, + feature_field: str | None = None, + name_prefix: str = "cluster", +) -> pd.DataFrame: + """ + Intersect clustered regions with genome annotations and return per-cluster tallies. + + Args: + region_metadata: iterable of (chrom, start, end, strand) tuples + labels: cluster labels aligned to region_metadata + annotation_file: path to a BED/GTF/GFF file with annotations + feature_field: column to use as the feature/category. For GTF/GFF this defaults + to "Feature" if present. For BED, this defaults to the Name/score column if + available, else "annotation". + name_prefix: expected prefix for cluster names in the BED export (used to strip + when reading Name) + + Returns: + pandas DataFrame with counts per (cluster, feature) and a column "total_cluster" + plus a fraction-normalized view ("fraction"). + + Note: + Requires pyranges (pip install pyranges). This keeps everything in Python without + shelling out to bedtools. + """ + + try: + import pyranges as pr + except Exception as exc: # pragma: no cover - optional dependency + raise ImportError( + "summarize_region_cluster_annotations requires pyranges. " + "Install it with `pip install pyranges`." + ) from exc + + if len(region_metadata) != len(labels): + raise ValueError("region_metadata and labels must have the same length.") + + # Build PyRanges for clusters + cluster_df = pd.DataFrame( + { + "Chromosome": [c for c, _, _, _ in region_metadata], + "Start": [s for _, s, _, _ in region_metadata], + "End": [e for _, _, e, _ in region_metadata], + "Strand": [st if st in {"+", "-"} else "." for *_, st in region_metadata], + "Name": [f"{name_prefix}_{lab}" for lab in labels], + "Score": labels, + } + ) + clusters = pr.PyRanges(cluster_df) + + # Read annotations + annotation_file = Path(annotation_file) + if annotation_file.suffix.lower() in {".gtf", ".gff", ".gff3"}: + annot = pr.read_gff(annotation_file) + else: + annot = pr.read_bed(annotation_file) + + # Pick feature column + annot_df = annot.df + if feature_field and feature_field in annot_df.columns: + feature_col = feature_field + elif "Feature" in annot_df.columns: + feature_col = "Feature" + elif "Name" in annot_df.columns: + feature_col = "Name" + else: + feature_col = None + annot_df["feature_tmp"] = "annotation" + feature_col = "feature_tmp" + annot = pr.PyRanges(annot_df) + + # Intersect and count + overlaps = clusters.join(annot) + if overlaps.df.empty: + return pd.DataFrame() + + df = overlaps.df.copy() + df["cluster"] = ( + df["Name"] + .astype(str) + .str.replace(f"{name_prefix}_", "", regex=False) + .astype(int) + ) + df["feature"] = df[feature_col].astype(str) + + counts = df.groupby(["cluster", "feature"]).size().reset_index(name="count") + totals = counts.groupby("cluster")["count"].sum().reset_index(name="total_cluster") + merged = counts.merge(totals, on="cluster", how="left") + merged["fraction"] = merged["count"] / merged["total_cluster"] + return merged + + +def _adjust_p_values_bh(p_values: pd.Series) -> pd.Series: + if p_values.empty: + return pd.Series(dtype=float, index=p_values.index) + + numeric = pd.to_numeric(p_values, errors="coerce") + valid_mask = numeric.notna() + if not valid_mask.any(): + return pd.Series(np.nan, index=p_values.index, dtype=float) + + valid_values = numeric[valid_mask].to_numpy(dtype=float) + order = np.argsort(valid_values, kind="mergesort") + ranked = valid_values[order] + adjusted = np.empty_like(ranked) + running_min = 1.0 + total = len(ranked) + + for index in range(total - 1, -1, -1): + rank = index + 1 + candidate = min(1.0, ranked[index] * total / rank) + running_min = min(running_min, candidate) + adjusted[index] = running_min + + restored = np.full(len(p_values), np.nan, dtype=float) + restored_indices = np.flatnonzero(valid_mask.to_numpy()) + restored[restored_indices[order]] = adjusted + return pd.Series(restored, index=p_values.index, dtype=float) + + +def _binomial_greater_tail(k: int, n: int, p: float) -> float: + if n <= 0: + return 1.0 + if k <= 0: + return 1.0 + if p <= 0.0: + return 0.0 if k > 0 else 1.0 + if p >= 1.0: + return 1.0 + if k > n: + raise ValueError("k cannot exceed n for a binomial tail probability.") + + log_p = math.log(p) + log_q = math.log1p(-p) + tail = 0.0 + for i in range(k, n + 1): + log_prob = ( + math.lgamma(n + 1) + - math.lgamma(i + 1) + - math.lgamma(n - i + 1) + + i * log_p + + (n - i) * log_q + ) + tail += math.exp(log_prob) + return float(min(max(tail, 0.0), 1.0)) + + +def summarize_read_cluster_region_associations( + metadata: Sequence[dict[str, Any]], + labels: Sequence[int | str], + *, + include_strand: bool = True, + min_reads_per_region: int = 1, + prior_count: float = 0.5, +) -> pd.DataFrame: + """ + Summarize read-cluster associations per region with enrichment statistics. + + Returns a long-form DataFrame with one row per (region, cluster). + """ + + if len(metadata) != len(labels): + raise ValueError("metadata and labels must have the same length.") + if min_reads_per_region < 1: + raise ValueError("min_reads_per_region must be at least 1.") + if prior_count < 0: + raise ValueError("prior_count must be non-negative.") + + rows: list[dict[str, Any]] = [] + for meta, cluster_label in zip(metadata, labels, strict=False): + chrom = meta.get("chromosome", meta.get("chrom")) + start = meta.get("region_start", meta.get("start")) + end = meta.get("region_end", meta.get("end")) + strand_value = ( + meta.get("region_strand", meta.get("strand")) if include_strand else "." + ) + strand = strand_value if strand_value in {"+", "-"} else "." + rows.append( + { + "chrom": chrom, + "start": start, + "end": end, + "strand": strand, + "cluster": cluster_label, + } + ) + + columns = [ + "chrom", + "start", + "end", + "strand", + "cluster", + "count", + "total_reads", + "fraction", + "global_count", + "global_total", + "global_fraction", + "log2_enrichment", + "p_value", + "q_value", + ] + df = pd.DataFrame(rows, columns=["chrom", "start", "end", "strand", "cluster"]) + if df.empty: + return pd.DataFrame(columns=columns) + + region_cols = ["chrom", "start", "end", "strand"] + cluster_order = list(pd.unique(df["cluster"])) + region_totals = ( + df.groupby(region_cols, sort=True).size().reset_index(name="total_reads") + ) + kept_regions = region_totals.loc[ + region_totals["total_reads"] >= min_reads_per_region, region_cols + ] + if kept_regions.empty: + return pd.DataFrame(columns=columns) + + region_grid = ( + kept_regions.assign(_key=1) + .merge( + pd.DataFrame({"cluster": cluster_order, "_key": 1}), + on="_key", + how="outer", + ) + .drop(columns="_key") + ) + + observed_counts = ( + df.groupby(region_cols + ["cluster"], sort=True) + .size() + .reset_index(name="count") + ) + global_counts = ( + df.groupby("cluster", sort=True) + .size() + .reindex(cluster_order, fill_value=0) + .reset_index(name="global_count") + ) + global_total = int(len(df)) + global_counts["global_total"] = global_total + global_counts["global_fraction"] = ( + global_counts["global_count"] / global_total if global_total > 0 else 0.0 + ) + + summary = region_grid.merge( + observed_counts, on=region_cols + ["cluster"], how="left" + ) + summary["count"] = summary["count"].fillna(0).astype(int) + summary = summary.merge(region_totals, on=region_cols, how="left") + summary = summary.merge(global_counts, on="cluster", how="left") + summary["fraction"] = summary["count"] / summary["total_reads"] + + cluster_count = max(len(cluster_order), 1) + if prior_count == 0: + with np.errstate(divide="ignore", invalid="ignore"): + summary["log2_enrichment"] = np.log2( + (summary["count"] / summary["total_reads"]) + / (summary["global_count"] / summary["global_total"]) + ) + else: + region_shrunk = (summary["count"] + prior_count) / ( + summary["total_reads"] + (prior_count * cluster_count) + ) + global_shrunk = (summary["global_count"] + prior_count) / ( + summary["global_total"] + (prior_count * cluster_count) + ) + summary["log2_enrichment"] = np.log2(region_shrunk / global_shrunk) + + summary["p_value"] = [ + _binomial_greater_tail(int(count), int(total), float(prob)) + for count, total, prob in zip( + summary["count"], + summary["total_reads"], + summary["global_fraction"].fillna(0.0), + strict=False, + ) + ] + summary["q_value"] = _adjust_p_values_bh(summary["p_value"]) + summary = summary.sort_values(region_cols + ["cluster"]).reset_index(drop=True) + return summary[columns] + + +def summarize_read_clusters_by_region( + metadata: Sequence[dict[str, Any]], + labels: Sequence[int], + *, + include_strand: bool = True, +) -> pd.DataFrame: + """ + Summarize read-level cluster composition per locus to assess homogeneity. + + Args: + metadata: list of metadata dicts returned by extract_read_windows + labels: cluster labels aligned to metadata entries + include_strand: if True, region keys include strand; set False to pool strands + + Returns: + DataFrame with one row per region key containing: + - counts per cluster + - total_reads + - dominant_cluster + - dominant_fraction + - entropy (natural log base) + """ + + if len(metadata) != len(labels): + raise ValueError("metadata and labels must have the same length.") + summary = summarize_read_cluster_region_associations( + metadata, + labels, + include_strand=include_strand, + ) + if summary.empty: + return pd.DataFrame( + columns=[ + "chrom", + "start", + "end", + "strand", + "total_reads", + "dominant_cluster", + "dominant_fraction", + "entropy", + ] + ) + + pivot = summary.pivot_table( + index=["chrom", "start", "end", "strand"], + columns="cluster", + values="count", + fill_value=0, + aggfunc="sum", + ) + pivot["total_reads"] = pivot.sum(axis=1) + + # Homogeneity metrics per region + def _dominance_and_entropy(row: pd.Series) -> tuple[int, float, float]: + cluster_counts = row[row.index != "total_reads"].astype(float) + total = cluster_counts.sum() + if total == 0: + return -1, 0.0, 0.0 + dominant_cluster = int(cluster_counts.idxmax()) + dominant_fraction = float(cluster_counts.max() / total) + probs = cluster_counts / total + entropy = float(-(probs * np.log(probs + 1e-12)).sum()) + return dominant_cluster, dominant_fraction, entropy + + dom, dom_frac, ent = zip(*pivot.apply(_dominance_and_entropy, axis=1), strict=False) + pivot["dominant_cluster"] = dom + pivot["dominant_fraction"] = dom_frac + pivot["entropy"] = ent + + pivot = pivot.reset_index() + return pivot + + +__all__ = [ + "cluster_features", + "read_mod_fraction_table", + "merge_read_window_results", + "extract_read_windows", + "build_multimotif_read_windows", + "read_window_feature_matrix", + "summarize_feature_matrix", + "scale_feature_matrix", + "rank_read_features_by_group_difference", + "cluster_read_windows", + "classify_read_features_binary", + "plot_cluster_profiles", + "region_feature_matrix_from_pileup", + "plot_region_cluster_profiles", + "export_region_clusters_to_bed", + "summarize_region_cluster_annotations", + "summarize_read_cluster_region_associations", + "summarize_read_clusters_by_region", + "plot_cluster_karyotype", + "plot_classification_profiles", + "plot_region_classification_profiles", + "plot_confusion_matrices", + "sample_rows", + "cluster_label_mapping", + "apply_cluster_label_mapping", + "plot_two_site_read_raster", + "plot_multisite_read_raster", + "raster_stats_brief", + "infer_region_source_labels", +] diff --git a/dimelo/distribution.py b/dimelo/distribution.py new file mode 100644 index 0000000..8b4de4a --- /dev/null +++ b/dimelo/distribution.py @@ -0,0 +1,135 @@ +from __future__ import annotations + +from collections.abc import Iterable + +import numpy as np +import pandas as pd + +_CLUSTER_DISTRIBUTION_COLUMNS = [ + "sample_id", + "condition", + "cluster", + "count", + "fraction", +] + +_CONDITION_DISTRIBUTION_COLUMNS = [ + "condition", + "cluster", + "count", + "fraction", + "replicate_n", +] + + +def _require_columns(frame: pd.DataFrame, required: Iterable[str], name: str) -> None: + missing = [column for column in required if column not in frame.columns] + if missing: + raise ValueError(f"{name} is missing required columns: {', '.join(missing)}") + + +def build_cluster_distribution(assignments: pd.DataFrame) -> pd.DataFrame: + _require_columns(assignments, ("sample_id", "condition", "cluster"), "assignments") + if assignments.empty: + return pd.DataFrame(columns=_CLUSTER_DISTRIBUTION_COLUMNS) + + grouped = ( + assignments.groupby(["sample_id", "condition", "cluster"], sort=True) + .size() + .reset_index(name="count") + ) + totals = grouped.groupby("sample_id")["count"].transform("sum") + grouped["fraction"] = grouped["count"] / totals + return grouped[_CLUSTER_DISTRIBUTION_COLUMNS] + + +def build_condition_distribution(cluster_distribution: pd.DataFrame) -> pd.DataFrame: + _require_columns( + cluster_distribution, + ("sample_id", "condition", "cluster", "count"), + "cluster_distribution", + ) + if cluster_distribution.empty: + return pd.DataFrame(columns=_CONDITION_DISTRIBUTION_COLUMNS) + + replicate_counts = ( + cluster_distribution.groupby("condition", sort=True)["sample_id"] + .nunique() + .rename("replicate_n") + .reset_index() + ) + grouped = ( + cluster_distribution.groupby(["condition", "cluster"], sort=True) + .agg(count=("count", "sum")) + .reset_index() + ) + grouped = grouped.merge(replicate_counts, on="condition", how="left") + totals = grouped.groupby("condition")["count"].transform("sum") + grouped["fraction"] = grouped["count"] / totals + return grouped[_CONDITION_DISTRIBUTION_COLUMNS] + + +def build_distribution_change( + condition_distribution: pd.DataFrame, + *, + reference_condition: str, + pseudo_count: float = 1e-9, +) -> pd.DataFrame: + _require_columns( + condition_distribution, + ("condition", "cluster", "fraction"), + "condition_distribution", + ) + if condition_distribution.empty: + return pd.DataFrame( + columns=[ + "condition", + "cluster", + "count", + "fraction", + "replicate_n", + "reference_fraction", + "delta_fraction", + "log2_fc", + ] + ) + + reference_rows = condition_distribution[ + condition_distribution["condition"] == reference_condition + ] + if reference_rows.empty: + raise ValueError( + f"reference_condition {reference_condition!r} not present in condition_distribution" + ) + + reference_fractions = reference_rows.loc[:, ["cluster", "fraction"]].rename( + columns={"fraction": "reference_fraction"} + ) + merged = condition_distribution.merge(reference_fractions, on="cluster", how="left") + merged = merged[merged["condition"] != reference_condition].copy() + if "count" not in merged.columns: + merged["count"] = pd.NA + if "replicate_n" not in merged.columns: + merged["replicate_n"] = pd.NA + merged["reference_fraction"] = merged["reference_fraction"].fillna(0.0) + merged["delta_fraction"] = merged["fraction"] - merged["reference_fraction"] + merged["log2_fc"] = np.log2( + (merged["fraction"] + pseudo_count) + / (merged["reference_fraction"] + pseudo_count) + ) + return ( + merged[ + [ + "condition", + "cluster", + "count", + "fraction", + "replicate_n", + "reference_fraction", + "delta_fraction", + "log2_fc", + ] + ] + .sort_values(["condition", "cluster"], kind="stable") + .reset_index(drop=True) + ) diff --git a/dimelo/dmr.py b/dimelo/dmr.py new file mode 100644 index 0000000..fecc014 --- /dev/null +++ b/dimelo/dmr.py @@ -0,0 +1,386 @@ +from __future__ import annotations + +import subprocess +from collections.abc import Iterable, Sequence +from pathlib import Path +from typing import Any + +import pandas as pd + +from . import run_modkit, utils +from .models import ModkitDMRMultiResult, ModkitDMRPairResult + + +def _coerce_path(value: str | Path) -> Path: + return Path(value).expanduser().resolve() + + +def _coerce_sample_items( + samples: dict[str, str | Path] | Iterable[tuple[str, str | Path]], +) -> list[tuple[str, Path]]: + if isinstance(samples, dict): + items = [(str(name), _coerce_path(path)) for name, path in samples.items()] + else: + items = [(str(name), _coerce_path(path)) for name, path in samples] + if len(items) < 2: + raise ValueError("modkit dmr multi requires at least two samples.") + seen_names: set[str] = set() + for name, path in items: + if len(name) == 0: + raise ValueError("Sample names for modkit dmr multi cannot be empty.") + if name in seen_names: + # modkit supports duplicate names (combining), but keep workflow strict by default. + raise ValueError(f"Duplicate sample name for modkit dmr multi: {name!r}") + seen_names.add(name) + if not path.exists(): + raise FileNotFoundError(f"Sample bedMethyl file does not exist: {path}") + return items + + +def _bed_table_from_path(path: Path) -> pd.DataFrame: + if not path.exists(): + raise FileNotFoundError(f"Expected modkit output file not found: {path}") + if path.stat().st_size == 0: + return pd.DataFrame() + + with path.open("r", encoding="utf-8", errors="replace") as handle: + first_line = handle.readline().strip() + + if first_line.startswith("#"): + columns = first_line.lstrip("#").split("\t") + return pd.read_csv(path, sep="\t", comment="#", header=None, names=columns) + return pd.read_csv(path, sep="\t", header=None) + + +def _select_high_confidence_sites( + sites_table: pd.DataFrame, + *, + pvalue_max: float = 0.01, + abs_effect_size_min: float = 0.1, + min_total_coverage: int | None = None, +) -> pd.DataFrame: + if sites_table.empty: + return sites_table.copy() + + out = sites_table.copy() + mask = pd.Series(True, index=out.index) + + pvalue_col = "map_pvalue" if "map_pvalue" in out.columns else None + if pvalue_col is not None: + pvals = pd.to_numeric(out[pvalue_col], errors="coerce") + mask &= pvals <= float(pvalue_max) + + effect_col = "effect_size" if "effect_size" in out.columns else None + if effect_col is not None: + effects = pd.to_numeric(out[effect_col], errors="coerce") + mask &= effects.abs() >= float(abs_effect_size_min) + elif "score" in out.columns: + scores = pd.to_numeric(out["score"], errors="coerce") + mask &= scores.abs() >= float(abs_effect_size_min) + + if min_total_coverage is not None: + coverage_col = None + for candidate in ("a_total", "b_total", "num_sites"): + if candidate in out.columns: + coverage_col = candidate + break + if coverage_col is not None: + coverage = pd.to_numeric(out[coverage_col], errors="coerce") + mask &= coverage >= int(min_total_coverage) + + return out.loc[mask].reset_index(drop=True) + + +def _append_if_value(command: list[str], flag: str, value: Any) -> None: + if value is None: + return + command.extend([flag, str(value)]) + + +def run_dmr_pair( + *, + control_bed_methyl: str | Path, + experiment_bed_methyl: str | Path, + ref_genome: str | Path, + out_path: str | Path, + regions_bed: str | Path | None = None, + segment_path: str | Path | None = None, + bases: Sequence[str] = ("A",), + assign_codes: Sequence[str] | None = None, + min_valid_coverage: int = 0, + dmr_prior: float | None = None, + diff_stay: float | None = None, + significance_factor: float | None = None, + decay_distance: int | None = None, + max_gap_size: int | None = None, + log_transition_decay: bool = False, + fine_grained: bool = False, + prior_alpha: float | None = None, + prior_beta: float | None = None, + delta: float | None = None, + n_sample_records: int | None = None, + max_coverages: tuple[int, int] | None = None, + cap_coverages: bool = False, + missing: str | None = None, + threads: int | None = None, + io_threads: int | None = None, + batch_size: int | None = None, + interval_size: int | None = None, + header: bool = True, + force: bool = True, + suppress_progress: bool = True, + log_filepath: str | Path | None = None, + modkit_executable: str | Path | None = None, + pvalue_max: float = 0.01, + abs_effect_size_min: float = 0.1, + min_total_coverage: int | None = None, +) -> ModkitDMRPairResult: + """ + Run modkit's native pairwise DMR pipeline (including optional HMM segmentation). + + Notes: + - `regions_bed` and `segment_path` are mutually exclusive in modkit. + - When `segment_path` is set, the output includes single-site statistics plus + segmented regions from the HMM model. + """ + if regions_bed is not None and segment_path is not None: + raise ValueError( + "modkit dmr pair does not allow --regions-bed with --segment in one command. " + "Run region-level and segmented analyses as separate runs." + ) + if len(bases) == 0: + raise ValueError("run_dmr_pair requires at least one primary base in `bases`.") + + capabilities = run_modkit._ensure_modkit_available( + quiet=True, + executable=modkit_executable, + ) + executable = capabilities.executable + control_path = _coerce_path(control_bed_methyl) + experiment_path = _coerce_path(experiment_bed_methyl) + reference_path = _coerce_path(ref_genome) + output_path = _coerce_path(out_path) + region_path = None if regions_bed is None else _coerce_path(regions_bed) + segment_output_path = None if segment_path is None else _coerce_path(segment_path) + if not control_path.exists(): + raise FileNotFoundError( + f"Control bedMethyl file does not exist: {control_path}" + ) + if not experiment_path.exists(): + raise FileNotFoundError( + f"Experiment bedMethyl file does not exist: {experiment_path}" + ) + if not reference_path.exists(): + raise FileNotFoundError(f"Reference FASTA does not exist: {reference_path}") + if region_path is not None and not region_path.exists(): + raise FileNotFoundError(f"regions_bed does not exist: {region_path}") + + output_path.parent.mkdir(parents=True, exist_ok=True) + if segment_output_path is not None: + segment_output_path.parent.mkdir(parents=True, exist_ok=True) + + command: list[str] = [ + executable, + "dmr", + "pair", + "-a", + str(control_path), + "-b", + str(experiment_path), + "--ref", + str(reference_path), + "--out-path", + str(output_path), + ] + if region_path is not None: + command.extend(["--regions-bed", str(region_path)]) + if segment_output_path is not None: + command.extend(["--segment", str(segment_output_path)]) + + for base in bases: + command.extend(["--base", str(base)]) + for code in assign_codes or (): + command.extend(["--assign-code", str(code)]) + + _append_if_value(command, "--min-valid-coverage", min_valid_coverage) + _append_if_value(command, "--dmr-prior", dmr_prior) + _append_if_value(command, "--diff-stay", diff_stay) + _append_if_value(command, "--significance-factor", significance_factor) + _append_if_value(command, "--decay-distance", decay_distance) + _append_if_value(command, "--max-gap-size", max_gap_size) + _append_if_value(command, "--delta", delta) + _append_if_value(command, "--n-sample-records", n_sample_records) + _append_if_value(command, "--missing", missing) + _append_if_value(command, "--batch-size", batch_size) + _append_if_value(command, "--interval-size", interval_size) + if prior_alpha is not None or prior_beta is not None: + if prior_alpha is None or prior_beta is None: + raise ValueError( + "prior_alpha and prior_beta must both be provided together." + ) + command.extend(["--prior", str(prior_alpha), str(prior_beta)]) + if max_coverages is not None: + if len(max_coverages) != 2: + raise ValueError( + "max_coverages must be a 2-tuple: (control_max, experiment_max)." + ) + command.extend( + ["--max-coverages", str(max_coverages[0]), str(max_coverages[1])] + ) + + resolved_threads = utils.cores_to_run(threads) + command.extend(["--threads", str(resolved_threads)]) + if io_threads is not None: + command.extend(["--io-threads", str(utils.cores_to_run(io_threads))]) + if log_transition_decay: + command.append("--log-transition-decay") + if fine_grained: + command.append("--fine-grained") + if cap_coverages: + command.append("--cap-coverages") + if header: + command.append("--header") + if force: + command.append("--force") + if suppress_progress: + command.append("--suppress-progress") + if log_filepath is not None: + command.extend(["--log-filepath", str(_coerce_path(log_filepath))]) + + subprocess.run(command, check=True) + + sites_table = _bed_table_from_path(output_path) + segments_table = ( + _bed_table_from_path(segment_output_path) + if segment_output_path is not None + else None + ) + high_confidence_sites = _select_high_confidence_sites( + sites_table, + pvalue_max=pvalue_max, + abs_effect_size_min=abs_effect_size_min, + min_total_coverage=min_total_coverage, + ) + + return ModkitDMRPairResult( + output_path=output_path, + segment_path=segment_output_path, + command=command, + sites=sites_table, + segments=segments_table, + high_confidence_sites=high_confidence_sites, + metadata={ + "bases": list(bases), + "regions_bed": None if region_path is None else str(region_path), + "pvalue_max": float(pvalue_max), + "abs_effect_size_min": float(abs_effect_size_min), + "min_total_coverage": min_total_coverage, + "resolved_threads": resolved_threads, + "modkit_version": capabilities.version, + "modkit_executable": executable, + }, + ) + + +def run_dmr_multi( + *, + samples: dict[str, str | Path] | Iterable[tuple[str, str | Path]], + regions_bed: str | Path, + ref_genome: str | Path, + out_dir: str | Path, + bases: Sequence[str] = ("A",), + assign_codes: Sequence[str] | None = None, + min_valid_coverage: int = 0, + missing: str | None = None, + threads: int | None = None, + io_threads: int | None = None, + prefix: str | None = None, + header: bool = True, + force: bool = True, + suppress_progress: bool = True, + log_filepath: str | Path | None = None, + modkit_executable: str | Path | None = None, +) -> ModkitDMRMultiResult: + """ + Run modkit's native multi-sample DMR workflow. + """ + if len(bases) == 0: + raise ValueError("run_dmr_multi requires at least one primary base in `bases`.") + sample_items = _coerce_sample_items(samples) + + capabilities = run_modkit._ensure_modkit_available( + quiet=True, + executable=modkit_executable, + ) + executable = capabilities.executable + region_path = _coerce_path(regions_bed) + reference_path = _coerce_path(ref_genome) + output_dir = _coerce_path(out_dir) + if not region_path.exists(): + raise FileNotFoundError(f"regions_bed does not exist: {region_path}") + if not reference_path.exists(): + raise FileNotFoundError(f"Reference FASTA does not exist: {reference_path}") + output_dir.mkdir(parents=True, exist_ok=True) + + command: list[str] = [ + executable, + "dmr", + "multi", + "--regions-bed", + str(region_path), + "--ref", + str(reference_path), + "--out-dir", + str(output_dir), + ] + for name, path in sample_items: + command.extend(["--sample", str(path), str(name)]) + for base in bases: + command.extend(["--base", str(base)]) + for code in assign_codes or (): + command.extend(["--assign-code", str(code)]) + + _append_if_value(command, "--min-valid-coverage", min_valid_coverage) + _append_if_value(command, "--missing", missing) + if prefix is not None: + command.extend(["--prefix", str(prefix)]) + + resolved_threads = utils.cores_to_run(threads) + command.extend(["--threads", str(resolved_threads)]) + if io_threads is not None: + command.extend(["--io-threads", str(utils.cores_to_run(io_threads))]) + if header: + command.append("--header") + if force: + command.append("--force") + if suppress_progress: + command.append("--suppress-progress") + if log_filepath is not None: + command.extend(["--log-filepath", str(_coerce_path(log_filepath))]) + + subprocess.run(command, check=True) + + pair_paths = sorted(output_dir.glob("*.bed")) + pair_rows: list[dict[str, Any]] = [] + for bed_path in pair_paths: + pair_rows.append( + { + "pair_file": bed_path, + "pair_name": bed_path.stem, + } + ) + pair_files = pd.DataFrame(pair_rows) + + return ModkitDMRMultiResult( + out_dir=output_dir, + command=command, + pair_files=pair_files, + metadata={ + "bases": list(bases), + "regions_bed": str(region_path), + "resolved_threads": resolved_threads, + "n_samples": len(sample_items), + "modkit_version": capabilities.version, + "modkit_executable": executable, + }, + ) diff --git a/dimelo/export.py b/dimelo/export.py index e2c973c..9eb4a85 100644 --- a/dimelo/export.py +++ b/dimelo/export.py @@ -81,21 +81,17 @@ def pileup_to_bigwig( # count up the number of rows, for progress tracking, and pull out the last row so as to grab the length of the chromosome # note: the tqdm progress bar slows things down by about 33%, which was deemed better at the time of writing this than # 90 seconds without any status updates - rows_count, last_row = list( - tail( - n=1, - iterable=enumerate( - tqdm( - tabix.fetch(contig), - mininterval=1.0, - desc=f"Indexing {contig}.", - leave=False, - ) - ), - ) - )[0] - fields = last_row.split("\t") - max_coord = int(fields[2]) + rows_count = 0 + max_coord = 0 + for row in tqdm( + tabix.fetch(contig), + mininterval=1.0, + desc=f"Indexing {contig}.", + leave=False, + ): + rows_count += 1 + fields = row.split("\t") + max_coord = max(max_coord, int(fields[2])) contig_lengths_tuples.append((contig, max_coord)) lines_by_contig[contig] = rows_count # If we have a fasta file we can just reference that for contig lengths @@ -123,11 +119,112 @@ def pileup_to_bigwig( tabix.contigs, desc=f"Step 2: Writing {bedmethyl_file.name} contents to {output_file_path.name}", ): + contig_is_sorted = True + previous_coord: int | None = None + + for row in tabix.fetch(contig): + keep_basemod, genomic_coord, _modified_in_row, valid_in_row = ( + load_processed.process_pileup_row( + row=row, + parsed_motif=parsed_motif, + region_strand=strand, + single_strand=(strand != "."), + ) + ) + if keep_basemod and valid_in_row > 0: + if previous_coord is not None and genomic_coord < previous_coord: + contig_is_sorted = False + break + previous_coord = genomic_coord + + pending_coord: int | None = None + pending_modified = 0 + pending_valid = 0 contig_list = [] start_list = [] end_list = [] values_list = [] + def _queue_entry( + target_contig: str, + target_contig_list: list[str], + target_start_list: list[int], + target_end_list: list[int], + target_values_list: list[float], + coord: int, + modified_sum: int, + valid_sum: int, + ) -> None: + if valid_sum <= 0: + return + target_contig_list.append(target_contig) + target_start_list.append(int(coord)) + target_end_list.append(int(coord) + 1) + target_values_list.append(float(modified_sum) / float(valid_sum)) + + def _flush_chunk( + target_contig_list: list[str], + target_start_list: list[int], + target_end_list: list[int], + target_values_list: list[float], + ) -> tuple[list[str], list[int], list[int], list[float]]: + if not target_values_list: + return ( + target_contig_list, + target_start_list, + target_end_list, + target_values_list, + ) + bw.addEntries( + target_contig_list, # Contig names + target_start_list, # Start positions + ends=target_end_list, # End positions + values=target_values_list, # Corresponding values + ) + return [], [], [], [] + + if not contig_is_sorted: + rows_by_coord: dict[int, list[int]] = {} + for row in tqdm( + tabix.fetch(contig), + desc=f"Writing {contig}.", + total=lines_by_contig[contig], + leave=False, + ): + keep_basemod, genomic_coord, modified_in_row, valid_in_row = ( + load_processed.process_pileup_row( + row=row, + parsed_motif=parsed_motif, + region_strand=strand, + single_strand=(strand != "."), + ) + ) + if keep_basemod and valid_in_row > 0: + counts = rows_by_coord.setdefault(int(genomic_coord), [0, 0]) + counts[0] += int(modified_in_row) + counts[1] += int(valid_in_row) + + for coord in sorted(rows_by_coord): + modified_sum, valid_sum = rows_by_coord[coord] + _queue_entry( + contig, + contig_list, + start_list, + end_list, + values_list, + coord, + modified_sum, + valid_sum, + ) + if len(values_list) >= chunk_size: + contig_list, start_list, end_list, values_list = _flush_chunk( + contig_list, start_list, end_list, values_list + ) + contig_list, start_list, end_list, values_list = _flush_chunk( + contig_list, start_list, end_list, values_list + ) + continue + for row in tqdm( tabix.fetch(contig), desc=f"Writing {contig}.", @@ -143,25 +240,45 @@ def pileup_to_bigwig( ) ) if keep_basemod and valid_in_row > 0: - contig_list.append(contig) - start_list.append(genomic_coord) - end_list.append(genomic_coord + 1) - values_list.append(modified_in_row / valid_in_row) - - if len(values_list) > chunk_size: - bw.addEntries( - contig_list, # Contig names - start_list, # Start positions - ends=end_list, # End positions - values=values_list, # Corresponding values + if pending_coord is None: + pending_coord = genomic_coord + pending_modified = int(modified_in_row) + pending_valid = int(valid_in_row) + elif genomic_coord == pending_coord: + pending_modified += int(modified_in_row) + pending_valid += int(valid_in_row) + else: + _queue_entry( + contig, + contig_list, + start_list, + end_list, + values_list, + pending_coord, + pending_modified, + pending_valid, ) - contig_list = [] - start_list = [] - end_list = [] - values_list = [] - bw.addEntries( - contig_list, # Contig names - start_list, # Start positions - ends=end_list, # End positions - values=values_list, # Corresponding values + if len(values_list) >= chunk_size: + contig_list, start_list, end_list, values_list = ( + _flush_chunk( + contig_list, start_list, end_list, values_list + ) + ) + pending_coord = genomic_coord + pending_modified = int(modified_in_row) + pending_valid = int(valid_in_row) + + if pending_coord is not None: + _queue_entry( + contig, + contig_list, + start_list, + end_list, + values_list, + pending_coord, + pending_modified, + pending_valid, + ) + contig_list, start_list, end_list, values_list = _flush_chunk( + contig_list, start_list, end_list, values_list ) diff --git a/dimelo/global_analysis.py b/dimelo/global_analysis.py new file mode 100644 index 0000000..dea40ad --- /dev/null +++ b/dimelo/global_analysis.py @@ -0,0 +1,344 @@ +from __future__ import annotations + +from collections.abc import Iterable, Sequence +from functools import partial +from pathlib import Path + +import pandas as pd +import pysam +from tqdm.auto import tqdm + +from . import load_processed, utils +from .models import GlobalAnalysisResult, SampleSpec + + +def _global_counts_for_motifs_from_bedmethyl( + bedmethyl_file: str | Path, + motifs: Sequence[str], + quiet: bool = True, +) -> dict[str, tuple[int, int]]: + """ + Sum modified and valid pileup counts across all contigs for all motifs in one pass. + """ + + del quiet + + unique_motifs = list(dict.fromkeys(motifs)) + if len(unique_motifs) == 0: + return {} + + parsed_motifs = {motif: utils.ParsedMotif(motif) for motif in unique_motifs} + counts_by_motif = {motif: [0, 0] for motif in unique_motifs} + + with pysam.TabixFile(str(bedmethyl_file)) as tabix_file: + for contig in tabix_file.contigs: + for row in tabix_file.fetch(contig): + for motif, parsed_motif in parsed_motifs.items(): + keep_basemod, _, modified_in_row, valid_in_row = ( + load_processed.process_pileup_row( + row=row, + parsed_motif=parsed_motif, + region_strand=".", + ) + ) + if keep_basemod: + counts_by_motif[motif][0] += modified_in_row + counts_by_motif[motif][1] += valid_in_row + + return { + motif: (modified_count, valid_count) + for motif, (modified_count, valid_count) in counts_by_motif.items() + } + + +def _global_counts_from_bedmethyl( + bedmethyl_file: str | Path, + motif: str, + quiet: bool = True, +) -> tuple[int, int]: + """ + Sum modified and valid pileup counts across all contigs for one motif. + """ + + counts_by_motif = _global_counts_for_motifs_from_bedmethyl( + bedmethyl_file=bedmethyl_file, + motifs=[motif], + quiet=quiet, + ) + return counts_by_motif[motif] + + +def summarize_global_samples( + samples: Sequence[SampleSpec], + motifs: Sequence[str], + quiet: bool = True, +) -> pd.DataFrame: + """ + Build a long-form table of global motif fractions per sample. + """ + + rows: list[dict[str, object]] = [] + + for sample in tqdm(samples, desc="Summarizing global samples", disable=quiet): + if not sample.metadata or "pileup_path" not in sample.metadata: + raise ValueError( + f"Sample {sample.sample_id!r} is missing metadata['pileup_path'] for global_analysis mode." + ) + + pileup_path = sample.metadata["pileup_path"] + counts_by_motif = _global_counts_for_motifs_from_bedmethyl( + bedmethyl_file=pileup_path, + motifs=motifs, + quiet=quiet, + ) + for motif in motifs: + modified_count, valid_count = counts_by_motif[motif] + rows.append( + { + "sample_id": sample.sample_id, + "condition": sample.condition, + "replicate": sample.replicate, + "motif": motif, + "modified_count": modified_count, + "valid_count": valid_count, + "global_fraction": ( + modified_count / valid_count if valid_count else float("nan") + ), + } + ) + + return pd.DataFrame( + rows, + columns=[ + "sample_id", + "condition", + "replicate", + "motif", + "modified_count", + "valid_count", + "global_fraction", + ], + ) + + +def tile_genome_windows( + *, + genome_sizes: dict[str, int], + window_size: int, + step_size: int, + include_contigs: Iterable[str] | None = None, + exclude_contigs: Iterable[str] | None = None, +) -> pd.DataFrame: + if window_size <= 0: + raise ValueError("window_size must be positive.") + if step_size <= 0: + raise ValueError("step_size must be positive.") + + include_set = set(include_contigs) if include_contigs is not None else None + exclude_set = set(exclude_contigs or []) + + rows: list[dict[str, object]] = [] + for chromosome, contig_length in genome_sizes.items(): + if include_set is not None and chromosome not in include_set: + continue + if chromosome in exclude_set: + continue + if contig_length <= 0: + continue + + start = 0 + while start < contig_length: + end = min(start + window_size, contig_length) + rows.append( + { + "window_id": f"{chromosome}:{start}-{end}", + "chromosome": chromosome, + "start": start, + "end": end, + "strand": ".", + } + ) + if end >= contig_length: + break + start += step_size + + return pd.DataFrame( + rows, + columns=["window_id", "chromosome", "start", "end", "strand"], + ) + + +def build_window_summary( + *, + samples: Sequence[SampleSpec], + motifs: Sequence[str], + genome_sizes: dict[str, int], + window_size: int, + step_size: int, + include_contigs: Iterable[str] | None = None, + exclude_contigs: Iterable[str] | None = None, + quiet: bool = True, + cores: int | None = None, +) -> pd.DataFrame: + motifs = list(motifs) + if len(motifs) != 1: + raise ValueError("build_window_summary currently supports exactly one motif.") + + motif = motifs[0] + windows = tile_genome_windows( + genome_sizes=genome_sizes, + window_size=window_size, + step_size=step_size, + include_contigs=include_contigs, + exclude_contigs=exclude_contigs, + ) + + region_strings = [ + f"{row.chromosome}:{row.start}-{row.end},{row.strand}" + for row in windows.itertuples(index=False) + ] + + rows: list[dict[str, object]] = [] + for sample in tqdm(samples, desc="Processing tiled windows", disable=quiet): + metadata = sample.metadata or {} + if "pileup_path" not in metadata: + raise ValueError( + f"Sample {sample.sample_id!r} is missing metadata['pileup_path'] for global_analysis mode." + ) + + pileup_loader = partial( + load_processed.pileup_counts_from_bedmethyl, + bedmethyl_file=metadata["pileup_path"], + motif=motif, + ) + counts_by_window = load_processed.regions_to_list( + function_handle=pileup_loader, + regions=region_strings, + window_size=None, + quiet=quiet, + cores=cores, + ) + + if len(counts_by_window) != len(windows): + raise RuntimeError( + "Pileup window count length did not match the number of tiled windows." + ) + + for window_row, (modified_count, valid_count) in zip( + windows.itertuples(index=False), + counts_by_window, + strict=False, + ): + window_fraction = 0.0 if valid_count == 0 else modified_count / valid_count + rows.append( + { + "sample_id": sample.sample_id, + "condition": sample.condition, + "replicate": sample.replicate, + "motif": motif, + "window_id": window_row.window_id, + "chromosome": window_row.chromosome, + "start": window_row.start, + "end": window_row.end, + "strand": window_row.strand, + "modified_count": modified_count, + "valid_count": valid_count, + "window_fraction": window_fraction, + } + ) + + return pd.DataFrame( + rows, + columns=[ + "sample_id", + "condition", + "replicate", + "motif", + "window_id", + "chromosome", + "start", + "end", + "strand", + "modified_count", + "valid_count", + "window_fraction", + ], + ) + + +def compute_global_normalization_factors(summary: pd.DataFrame) -> pd.DataFrame: + factors = summary.copy() + factors["reference_fraction"] = factors.groupby("motif", sort=True)[ + "global_fraction" + ].transform("mean") + factors["global_offset"] = ( + factors["global_fraction"] - factors["reference_fraction"] + ) + return factors.loc[ + :, + [ + "sample_id", + "condition", + "replicate", + "motif", + "global_fraction", + "reference_fraction", + "global_offset", + ], + ].copy() + + +def run_global_analysis( + *, + samples: Sequence[SampleSpec], + motifs: Iterable[str], + genome_sizes: dict[str, int], + window_size: int, + step_size: int, + include_contigs: Iterable[str] | None = None, + exclude_contigs: Iterable[str] | None = None, + quiet: bool = True, + cores: int | None = None, +) -> GlobalAnalysisResult: + motifs = list(motifs) + if len(motifs) == 0: + raise ValueError("run_global_analysis requires at least one motif.") + + summary = summarize_global_samples(samples=samples, motifs=motifs, quiet=quiet) + per_motif_windows = [ + build_window_summary( + samples=samples, + motifs=[motif], + genome_sizes=genome_sizes, + window_size=window_size, + step_size=step_size, + include_contigs=include_contigs, + exclude_contigs=exclude_contigs, + quiet=quiet, + cores=cores, + ) + for motif in motifs + ] + windows = pd.concat(per_motif_windows, ignore_index=True) + normalization_factors = compute_global_normalization_factors(summary) + plot_data = { + "global_fraction_bar": summary.loc[ + :, + ["sample_id", "condition", "motif", "global_fraction"], + ].copy(), + "window_fraction_table": windows.copy(), + } + + return GlobalAnalysisResult( + summary=summary, + windows=windows, + normalization_factors=normalization_factors, + plot_data=plot_data, + metadata={ + "normalization_mode": "per_sample_global", + "window_size": window_size, + "step_size": step_size, + "motifs": motifs, + }, + figures={}, + ) diff --git a/dimelo/load_processed.py b/dimelo/load_processed.py index b14e4f0..533481b 100644 --- a/dimelo/load_processed.py +++ b/dimelo/load_processed.py @@ -1,7 +1,8 @@ import concurrent.futures import gzip import multiprocessing -from collections import defaultdict +import os +import warnings from functools import partial from multiprocessing import shared_memory from pathlib import Path @@ -20,6 +21,14 @@ # on 32 cores is fairly similar, but sitting in the middle of the range should support 10x more cores (beyond # the reasonable upper bound) and 10x fewer cores (which is about the reasonable lower bound). DEFAULT_CHUNK_SIZE = 1_000_000 +AUTO_PARALLEL_MIN_TASKS = 64 +AUTO_PARALLEL_TASKS_PER_CORE = 16 +AUTO_PARALLEL_BATCHES_PER_WORKER = 8 +AUTO_PARALLEL_CHUNKS_PER_TASK = 8 +AUTO_PARALLEL_MEMORY_FRACTION = 0.5 +AUTO_PARALLEL_PROCESS_OVERHEAD_BYTES = 64 * 1024 * 1024 +_CGROUP_MEMORY_SENTINEL_LIMIT = 1 << 60 +_TABIX_CACHE: dict[str, pysam.TabixFile] = {} ################################################################################################################ #### Loader wrappers #### @@ -58,6 +67,7 @@ def regions_to_list( Returns: List(function_handle return objects per region) """ + executor = kwargs.pop("executor", None) regions_dict = utils.regions_dict_from_input( regions, window_size, @@ -70,37 +80,73 @@ def regions_to_list( for start, end, strand in region_list ] - cores_to_run = utils.cores_to_run(cores) + cores_to_run = _resolve_cores_for_task_count( + requested_cores=cores, + task_count=len(region_strings), + ) # quiet and cores logic below is driven by the following: # If the parallelization is within regions: # (1) progress bars should happen within regions if at all, because we assume regions are # large if they make sense to parallelize # (2) the cores_to_run will be allocated to within-region parallelization, and the top-level # jobs sequence is run sequentially - with concurrent.futures.ProcessPoolExecutor( - max_workers=1 if split_large_regions else cores_to_run - ) as executor: - # Use functools.partial to pre-fill arguments + # For single-core, avoid process-pool startup and run regions directly. + if not split_large_regions and cores_to_run == 1: process_partial = partial( apply_loader_function_to_region, function_handle=function_handle, - quiet=quiet or not split_large_regions, - cores=cores_to_run - if split_large_regions - else 1, # if parallelization is within region + quiet=quiet, + cores=1, **kwargs, ) - results = list( + return list( tqdm( - executor.map(process_partial, region_strings), + map(process_partial, region_strings), total=len(region_strings), desc="Loading data", - disable=quiet or split_large_regions, + disable=quiet, leave=False, ) ) - return results + process_partial = partial( + _apply_loader_function_to_region_batch, + function_handle=function_handle, + quiet=quiet or not split_large_regions, + cores=cores_to_run + if split_large_regions + else 1, # if parallelization is within region + **kwargs, + ) + + # Keep enough tasks per worker to amortize process overhead on large region lists. + if split_large_regions: + batch_size = 1 + else: + target_batches = max(1, cores_to_run * AUTO_PARALLEL_BATCHES_PER_WORKER) + batch_size = max(1, len(region_strings) // target_batches) + region_batches = list(_iter_batches(region_strings, batch_size=batch_size)) + + def _collect(mapped_batches) -> list: + return [ + item + for batch in tqdm( + mapped_batches, + total=len(region_batches), + desc="Loading data", + disable=quiet or split_large_regions, + leave=False, + ) + for item in batch + ] + + if executor is not None and not split_large_regions: + return _collect(executor.map(process_partial, region_batches)) + + with concurrent.futures.ProcessPoolExecutor( + max_workers=1 if split_large_regions else cores_to_run + ) as local_executor: + return _collect(local_executor.map(process_partial, region_batches)) def apply_loader_function_to_region(region_string, function_handle, **kwargs): @@ -118,6 +164,295 @@ def apply_loader_function_to_region(region_string, function_handle, **kwargs): return function_handle(regions=region_string, **kwargs) +def _apply_loader_function_to_region_batch(region_batch, function_handle, **kwargs): + return [ + function_handle(regions=region_string, **kwargs) + for region_string in region_batch + ] + + +def _iter_batches(items: list[str], *, batch_size: int): + for idx in range(0, len(items), batch_size): + yield items[idx : idx + batch_size] + + +def _parallel_chunk_batch_size(*, task_count: int, workers: int) -> int: + target_tasks = max(1, workers * AUTO_PARALLEL_CHUNKS_PER_TASK) + return max(1, task_count // target_tasks) + + +def _get_tabix_file(path: str | Path) -> pysam.TabixFile: + key = str(path) + cached = _TABIX_CACHE.get(key) + if cached is None: + cached = pysam.TabixFile(key) + _TABIX_CACHE[key] = cached + return cached + + +def _clear_tabix_cache() -> None: + _TABIX_CACHE.clear() + + +def _resolve_cores_for_task_count( + *, requested_cores: int | None, task_count: int +) -> int: + """ + Resolve worker count for loader-style fanout. + + If cores are explicitly requested, honor that request. For automatic mode (cores=None), + avoid expensive multiprocessing startup for small task batches. + """ + resolved = utils.cores_to_run(requested_cores) + if requested_cores is not None: + return resolved + + cores_by_tasks = 1 + if task_count >= AUTO_PARALLEL_MIN_TASKS: + cores_by_tasks = max(1, task_count // AUTO_PARALLEL_TASKS_PER_CORE) + + return max(1, min(resolved, cores_by_tasks)) + + +def _parse_positive_int(value: str | None) -> int | None: + if value is None: + return None + text = str(value).strip() + if not text: + return None + digits = [] + for char in text: + if char.isdigit(): + digits.append(char) + else: + break + if not digits: + return None + parsed = int("".join(digits)) + return parsed if parsed > 0 else None + + +def _cgroup_memory_limit_bytes() -> int | None: + candidates: list[int] = [] + for candidate_path in ( + "/sys/fs/cgroup/memory.max", + "/sys/fs/cgroup/memory/memory.limit_in_bytes", + ): + try: + value = Path(candidate_path).read_text(encoding="utf-8").strip() + except Exception: + continue + if not value or value.lower() == "max": + continue + parsed = _parse_positive_int(value) + if parsed is None or parsed >= _CGROUP_MEMORY_SENTINEL_LIMIT: + continue + candidates.append(parsed) + if not candidates: + return None + return min(candidates) + + +def _slurm_memory_limit_bytes() -> int | None: + mem_per_node_mb = _parse_positive_int(os.environ.get("SLURM_MEM_PER_NODE")) + mem_per_cpu_mb = _parse_positive_int(os.environ.get("SLURM_MEM_PER_CPU")) + cpus_per_task = _parse_positive_int(os.environ.get("SLURM_CPUS_PER_TASK")) + cpus_on_node = _parse_positive_int(os.environ.get("SLURM_CPUS_ON_NODE")) + + candidates: list[int] = [] + if mem_per_node_mb is not None: + candidates.append(mem_per_node_mb * 1024 * 1024) + if mem_per_cpu_mb is not None: + cpus = cpus_per_task or cpus_on_node + if cpus is not None: + candidates.append(mem_per_cpu_mb * cpus * 1024 * 1024) + + if not candidates: + return None + return min(candidates) + + +def _available_memory_bytes() -> int | None: + override = _parse_positive_int(os.environ.get("DIMELO_AVAILABLE_MEMORY_BYTES")) + if override is not None: + return override + + candidates: list[int] = [] + try: + import psutil # type: ignore + + candidates.append(int(psutil.virtual_memory().available)) + except Exception: + pass + + cgroup_limit = _cgroup_memory_limit_bytes() + if cgroup_limit is not None: + candidates.append(cgroup_limit) + + slurm_limit = _slurm_memory_limit_bytes() + if slurm_limit is not None: + candidates.append(slurm_limit) + + if not candidates: + return None + return min(candidates) + + +def _memory_limited_cores( + *, + requested_cores: int | None, + task_count: int, + bytes_per_task: int, + extra_shared_bytes: int = 0, +) -> int: + """ + Resolve worker count using CPU availability, task fanout, and available memory. + + Explicit cores override auto-tuning and are always returned unchanged. + """ + baseline = _resolve_cores_for_task_count( + requested_cores=requested_cores, + task_count=task_count, + ) + if requested_cores is not None: + return baseline + + available = _available_memory_bytes() + if available is None: + return baseline + + budget = int(available * AUTO_PARALLEL_MEMORY_FRACTION) - max( + 0, int(extra_shared_bytes) + ) + if budget <= 0: + return 1 + + per_worker = max(1, int(bytes_per_task)) + AUTO_PARALLEL_PROCESS_OVERHEAD_BYTES + memory_cap = max(1, budget // per_worker) + return max(1, min(baseline, memory_cap)) + + +def _pileup_counts_from_bedmethyl_single_core( + *, + bedmethyl_file: str | Path, + parsed_motif: utils.ParsedMotif, + chunks_list: list[dict], + single_strand: bool, + quiet: bool, +) -> tuple[int, int]: + """Single-process fallback used when only one core is requested.""" + source_tabix = _get_tabix_file(bedmethyl_file) + total_valid = 0 + total_modified = 0 + + for chunk in tqdm( + chunks_list, + total=len(chunks_list), + disable=quiet, + desc="Loading data", + leave=False, + ): + chromosome = chunk["chromosome"] + subregion_start = chunk["subregion_start"] + subregion_end = chunk["subregion_end"] + strand = chunk["strand"] + + if chromosome not in source_tabix.contigs: + continue + + for row in source_tabix.fetch( + chromosome, max(subregion_start, 0), subregion_end + ): + keep_basemod, _, modified_in_row, valid_in_row = process_pileup_row( + row=row, + parsed_motif=parsed_motif, + region_strand=strand, + single_strand=single_strand, + ) + if keep_basemod: + total_valid += valid_in_row + total_modified += modified_in_row + + return total_modified, total_valid + + +def _pileup_vectors_from_bedmethyl_single_core( + *, + bedmethyl_file: str | Path, + parsed_motif: utils.ParsedMotif, + chunks_list: list[dict], + region_len: int, + single_strand: bool, + regions_5to3prime: bool, + quiet: bool, +) -> tuple[np.ndarray, np.ndarray]: + """Single-process fallback used when only one core is requested.""" + source_tabix = _get_tabix_file(bedmethyl_file) + valid_base_counts = np.zeros(region_len, dtype=np.int32) + modified_base_counts = np.zeros(region_len, dtype=np.int32) + + for chunk in tqdm( + chunks_list, + total=len(chunks_list), + disable=quiet, + desc="Loading data", + leave=False, + ): + chromosome = chunk["chromosome"] + region_start = chunk["region_start"] + region_end = chunk["region_end"] + subregion_start = chunk["subregion_start"] + subregion_end = chunk["subregion_end"] + strand = chunk["strand"] + + flip_coords = regions_5to3prime and strand == "-" + if flip_coords: + subregion_offset = region_end - subregion_end + else: + subregion_offset = subregion_start - region_start + + if region_end - region_start > region_len: + print( + f"WARNING: You have specified a region at {chromosome}:{region_start}-{region_end} that is longer than the first region; the end of the region will be skipped. To make a profile plot with differently-sized region, consider using the window_size parameter to make a profile across centered windows." + ) + + subregion_len = subregion_end - subregion_start + valid_base_subregion = np.zeros(subregion_len, dtype=np.int32) + modified_base_subregion = np.zeros(subregion_len, dtype=np.int32) + + if chromosome in source_tabix.contigs: + for row in source_tabix.fetch( + chromosome, max(subregion_start, 0), subregion_end + ): + keep_basemod, genomic_coord, modified_in_row, valid_in_row = ( + process_pileup_row( + row=row, + parsed_motif=parsed_motif, + region_strand=strand, + single_strand=single_strand, + ) + ) + if keep_basemod: + if flip_coords: + pileup_coord_in_subregion = subregion_end - genomic_coord - 1 + else: + pileup_coord_in_subregion = genomic_coord - subregion_start + if pileup_coord_in_subregion < subregion_len: + valid_base_subregion[pileup_coord_in_subregion] += valid_in_row + modified_base_subregion[pileup_coord_in_subregion] += ( + modified_in_row + ) + + valid_base_counts[subregion_offset : subregion_offset + subregion_len] += ( + valid_base_subregion + ) + modified_base_counts[subregion_offset : subregion_offset + subregion_len] += ( + modified_base_subregion + ) + + return modified_base_counts, valid_base_counts + + ################################################################################################################ #### Pileup loaders #### ################################################################################################################ @@ -147,8 +482,6 @@ def pileup_counts_from_bedmethyl( If no regions are specified, returns the sum total for the motif of interest across the entire bedmethyl file. - TODO: Consider renaming this method, e.g. counts_from_pileup - Args: bedmethyl_file: Path to bedmethyl file regions: Path to bed file specifying regions @@ -171,32 +504,38 @@ def pileup_counts_from_bedmethyl( regions_dict, chunk_size=chunk_size ) - cores_to_run = utils.cores_to_run(cores) - - # Initialize shared memory as length-one numpy arrays to make it easy to map to buffer in subprocesses - shm_valid = shared_memory.SharedMemory( - create=True, size=np.dtype(np.int32).itemsize - ) - shm_modified = shared_memory.SharedMemory( - create=True, size=np.dtype(np.int32).itemsize + cores_to_run = _memory_limited_cores( + requested_cores=cores, + task_count=len(chunks_list), + bytes_per_task=0, ) - manager = multiprocessing.Manager() - lock = manager.Lock() + if cores_to_run == 1: + return _pileup_counts_from_bedmethyl_single_core( + bedmethyl_file=bedmethyl_file, + parsed_motif=parsed_motif, + chunks_list=chunks_list, + single_strand=single_strand, + quiet=quiet, + ) + batch_size = _parallel_chunk_batch_size( + task_count=len(chunks_list), + workers=cores_to_run, + ) + chunk_batches = list(_iter_batches(chunks_list, batch_size=batch_size)) + modified_base_count = 0 + valid_base_count = 0 with concurrent.futures.ProcessPoolExecutor(max_workers=cores_to_run) as executor: futures = [ executor.submit( - pileup_counts_process_chunk, + _pileup_counts_process_chunk_batch_local, bedmethyl_file, parsed_motif, - chunk, - shm_modified.name, - shm_valid.name, - lock, + chunk_batch, single_strand, ) - for chunk in chunks_list + for chunk_batch in chunk_batches ] for future in tqdm( concurrent.futures.as_completed(futures), @@ -206,23 +545,12 @@ def pileup_counts_from_bedmethyl( leave=False, ): try: - future.result() + modified_in_batch, valid_in_batch = future.result() + modified_base_count += int(modified_in_batch) + valid_base_count += int(valid_in_batch) except Exception as err: raise RuntimeError("pileup_counts_process_chunk failed.") from err - # Directly convert shared memory buffers to integers - modified_base_count = int.from_bytes( - shm_modified.buf[:4], byteorder="little", signed=True - ) - valid_base_count = int.from_bytes( - shm_valid.buf[:4], byteorder="little", signed=True - ) - # Close and unlink shared memory - not fully handled by garbage collection otherwise - shm_modified.close() - shm_modified.unlink() - shm_valid.close() - shm_valid.unlink() - return modified_base_count, valid_base_count @@ -259,8 +587,6 @@ def pileup_vectors_from_bedmethyl( A region must be provided because otherwise there is no way to know what vector to return. However, a region can be a whole chromosome if desired. - TODO: Consider renaming this method, e.g. vectors_from_pileup - Args: bedmethyl_file: Path to bedmethyl file regions: Path to bed file specifying centered equal-length regions @@ -283,14 +609,39 @@ def pileup_vectors_from_bedmethyl( chunks_list = utils.process_chunks_from_regions_dict( regions_dict, chunk_size=chunk_size ) - - cores_to_run = utils.cores_to_run(cores) - # Peek at a region to figure out what size the vectors should be first_key = next(iter(regions_dict)) first_tuple = regions_dict[first_key][0] region_len = first_tuple[1] - first_tuple[0] + max_chunk_span = max( + ( + max(0, int(chunk["subregion_end"]) - int(chunk["subregion_start"])) + for chunk in chunks_list + ), + default=0, + ) + per_worker_bytes = 2 * max_chunk_span * np.dtype(np.int32).itemsize + shared_bytes = 2 * region_len * np.dtype(np.int32).itemsize + cores_to_run = _memory_limited_cores( + requested_cores=cores, + task_count=len(chunks_list), + bytes_per_task=per_worker_bytes, + extra_shared_bytes=shared_bytes, + ) + + # Avoid process-pool/shared-memory startup overhead for single-core workloads. + if cores_to_run == 1: + return _pileup_vectors_from_bedmethyl_single_core( + bedmethyl_file=bedmethyl_file, + parsed_motif=parsed_motif, + chunks_list=chunks_list, + region_len=region_len, + single_strand=single_strand, + regions_5to3prime=regions_5to3prime, + quiet=quiet, + ) + # Initialize shared memory as numpy arrays to make it easy to map to buffer in subprocesses shm_valid = shared_memory.SharedMemory( create=True, size=(region_len) * np.dtype(np.int32).itemsize @@ -302,13 +653,18 @@ def pileup_vectors_from_bedmethyl( manager = multiprocessing.Manager() lock = manager.Lock() + batch_size = _parallel_chunk_batch_size( + task_count=len(chunks_list), + workers=cores_to_run, + ) + chunk_batches = list(_iter_batches(chunks_list, batch_size=batch_size)) with concurrent.futures.ProcessPoolExecutor(max_workers=cores_to_run) as executor: futures = [ executor.submit( - pileup_vectors_process_chunk, + _pileup_vectors_process_chunk_batch, bedmethyl_file, parsed_motif, - chunk, + chunk_batch, region_len, shm_modified.name, shm_valid.name, @@ -316,7 +672,7 @@ def pileup_vectors_from_bedmethyl( single_strand, regions_5to3prime, ) - for chunk in chunks_list + for chunk_batch in chunk_batches ] for future in tqdm( concurrent.futures.as_completed(futures), @@ -389,34 +745,28 @@ def pileup_vectors_process_chunk( single_strand, regions_5to3prime, ) -> None: - """ - Helper function to allow pileup_vectors_from_bedmethyl to operate in a parallized fashion. - - Sum up modified and valid counts for a subregion chunk in a bedmethyl file. - - Args: - bedmethyl_file: Path to bedmethyl file - parsed_motif: ParsedMotif object - chunk: a dict containing subregion chunk information - shm_name_modified: the name string for the shared memory location containing the modified counts array - shm_name_valid: the name string for the shared memory location containing the valid counts array - lock: a manager.Lock object to allow synchronization in accessing shared memory - single_strand: True if only single-strand mods are desired - regions_5to3prime: True means negative strand regions get flipped, False means no flipping - - Returns: - None. Counts are added to arrays in-place to shared memory. - """ - source_tabix = pysam.TabixFile(str(bedmethyl_file)) - existing_valid = shared_memory.SharedMemory(name=shm_name_valid) - existing_modified = shared_memory.SharedMemory(name=shm_name_modified) - valid_base_counts = np.ndarray( - (region_len,), dtype=np.int32, buffer=existing_valid.buf - ) - modified_base_counts = np.ndarray( - (region_len,), dtype=np.int32, buffer=existing_modified.buf + _pileup_vectors_process_chunk_batch( + bedmethyl_file=bedmethyl_file, + parsed_motif=parsed_motif, + chunk_batch=[chunk], + region_len=region_len, + shm_name_modified=shm_name_modified, + shm_name_valid=shm_name_valid, + lock=lock, + single_strand=single_strand, + regions_5to3prime=regions_5to3prime, ) + +def _pileup_vectors_local_contribution( + *, + source_tabix: pysam.TabixFile, + parsed_motif: utils.ParsedMotif, + chunk: dict, + region_len: int, + single_strand: bool, + regions_5to3prime: bool, +) -> tuple[int, np.ndarray, np.ndarray] | None: chromosome = chunk["chromosome"] region_start = chunk["region_start"] region_end = chunk["region_end"] @@ -425,7 +775,6 @@ def pileup_vectors_process_chunk( strand = chunk["strand"] flip_coords = regions_5to3prime and strand == "-" - if flip_coords: subregion_offset = region_end - subregion_end else: @@ -436,122 +785,154 @@ def pileup_vectors_process_chunk( f"WARNING: You have specified a region at {chromosome}:{region_start}-{region_end} that is longer than the first region; the end of the region will be skipped. To make a profile plot with differently-sized region, consider using the window_size parameter to make a profile across centered windows." ) - valid_base_subregion = np.zeros(subregion_end - subregion_start, dtype=int) - modified_base_subregion = np.zeros(subregion_end - subregion_start, dtype=int) + subregion_span = subregion_end - subregion_start + valid_base_subregion = np.zeros(subregion_span, dtype=np.int32) + modified_base_subregion = np.zeros(subregion_span, dtype=np.int32) - # tabix throws and error if the contig is not present - # by the current design, this should be silent - if chromosome in source_tabix.contigs: - for row in source_tabix.fetch( - chromosome, max(subregion_start, 0), subregion_end - ): - ( - keep_basemod, - genomic_coord, - modified_in_row, - valid_in_row, - ) = process_pileup_row( - row=row, - parsed_motif=parsed_motif, - region_strand=strand, - single_strand=single_strand, - ) - if keep_basemod: - if flip_coords: - # We want to flip the coordinates for this region so that it is recorded along the 5 prime to 3 prime direction - # This will enable analyses where the orientation of protein binding / transcriptional dynamics / etc is relevant for our pileup signal - pileup_coord_in_subregion = subregion_end - genomic_coord - 1 - else: - # Normal coordinates are the default. This will be used both for the '+' case and the '.' (no strand specified) case - pileup_coord_in_subregion = genomic_coord - subregion_start - if pileup_coord_in_subregion < (subregion_end - subregion_start): - valid_base_subregion[pileup_coord_in_subregion] += valid_in_row - modified_base_subregion[pileup_coord_in_subregion] += ( - modified_in_row - ) + if chromosome not in source_tabix.contigs: + return subregion_offset, valid_base_subregion, modified_base_subregion - with lock: - valid_base_counts[ - subregion_offset : subregion_offset + abs(subregion_end - subregion_start) - ] += valid_base_subregion - modified_base_counts[ - subregion_offset : subregion_offset + abs(subregion_end - subregion_start) - ] += modified_base_subregion - # Close the file descriptor/handle to the shared memory - existing_modified.close() - existing_valid.close() + for row in source_tabix.fetch(chromosome, max(subregion_start, 0), subregion_end): + keep_basemod, genomic_coord, modified_in_row, valid_in_row = process_pileup_row( + row=row, + parsed_motif=parsed_motif, + region_strand=strand, + single_strand=single_strand, + ) + if not keep_basemod: + continue + if flip_coords: + pileup_coord_in_subregion = subregion_end - genomic_coord - 1 + else: + pileup_coord_in_subregion = genomic_coord - subregion_start + if pileup_coord_in_subregion < subregion_span: + valid_base_subregion[pileup_coord_in_subregion] += valid_in_row + modified_base_subregion[pileup_coord_in_subregion] += modified_in_row + return subregion_offset, valid_base_subregion, modified_base_subregion -def pileup_counts_process_chunk( + +def _pileup_vectors_process_chunk_batch( bedmethyl_file, parsed_motif, - chunk, + chunk_batch, + region_len, shm_name_modified, shm_name_valid, lock, single_strand, + regions_5to3prime, ) -> None: """ - Helper function to allow pileup_counts_from_bedmethyl to operate in a parallized fashion. + Worker helper that processes a batch of chunks and acquires the shared-memory lock once. + """ + source_tabix = _get_tabix_file(bedmethyl_file) + existing_valid = shared_memory.SharedMemory(name=shm_name_valid) + existing_modified = shared_memory.SharedMemory(name=shm_name_modified) + valid_base_counts = np.ndarray( + (region_len,), dtype=np.int32, buffer=existing_valid.buf + ) + modified_base_counts = np.ndarray( + (region_len,), dtype=np.int32, buffer=existing_modified.buf + ) - Sum up modified and valid counts for a subregion chunk in a bedmethyl file. + updates: list[tuple[int, np.ndarray, np.ndarray]] = [] + for chunk in chunk_batch: + contribution = _pileup_vectors_local_contribution( + source_tabix=source_tabix, + parsed_motif=parsed_motif, + chunk=chunk, + region_len=region_len, + single_strand=single_strand, + regions_5to3prime=regions_5to3prime, + ) + if contribution is not None: + updates.append(contribution) + + if updates: + with lock: + for ( + subregion_offset, + valid_base_subregion, + modified_base_subregion, + ) in updates: + subregion_span = len(valid_base_subregion) + valid_base_counts[ + subregion_offset : subregion_offset + subregion_span + ] += valid_base_subregion + modified_base_counts[ + subregion_offset : subregion_offset + subregion_span + ] += modified_base_subregion - Args: - bedmethyl_file: Path to bedmethyl file - parsed_motif: ParsedMotif object - chunk: a dict containing subregion chunk information - shm_name_modified: the name string for the shared memory location containing the modified counts sum - shm_name_valid: the name string for the shared memory location containing the valid counts sum - lock: a manager.Lock object to allow synchronization in accessing shared memory - single_strand: True if only single-strand mods are desired + existing_modified.close() + existing_valid.close() - Returns: - None. Counts are added in-place to shared memory. - """ - source_tabix = pysam.TabixFile(str(bedmethyl_file)) + +def pileup_counts_process_chunk( + bedmethyl_file, + parsed_motif, + chunk, + shm_name_modified, + shm_name_valid, + lock, + single_strand, +) -> None: + modified_subregion_counts, valid_subregion_counts = ( + _pileup_counts_process_chunk_batch_local( + bedmethyl_file=bedmethyl_file, + parsed_motif=parsed_motif, + chunk_batch=[chunk], + single_strand=single_strand, + ) + ) existing_valid = shared_memory.SharedMemory(name=shm_name_valid) existing_modified = shared_memory.SharedMemory(name=shm_name_modified) valid_base_counts = np.ndarray((1,), dtype=np.int32, buffer=existing_valid.buf) modified_base_counts = np.ndarray( (1,), dtype=np.int32, buffer=existing_modified.buf ) + with lock: + valid_base_counts[0] += valid_subregion_counts + modified_base_counts[0] += modified_subregion_counts + existing_valid.close() + existing_modified.close() - chromosome = chunk["chromosome"] - subregion_start = chunk["subregion_start"] - subregion_end = chunk["subregion_end"] - strand = chunk["strand"] - - valid_base_subregion_counts = 0 - modified_base_subregion_counts = 0 - # tabix throws and error if the contig is not present - # by the current design, this should be silent - if chromosome in source_tabix.contigs: +def _pileup_counts_process_chunk_batch_local( + bedmethyl_file, + parsed_motif, + chunk_batch, + single_strand, +) -> tuple[int, int]: + """ + Worker helper for counts mode that accumulates locally and returns one tuple per batch. + """ + source_tabix = _get_tabix_file(bedmethyl_file) + valid_total = 0 + modified_total = 0 + + for chunk in chunk_batch: + chromosome = chunk["chromosome"] + subregion_start = chunk["subregion_start"] + subregion_end = chunk["subregion_end"] + strand = chunk["strand"] + + if chromosome not in source_tabix.contigs: + continue for row in source_tabix.fetch( chromosome, max(subregion_start, 0), subregion_end ): - ( - keep_basemod, - _, - modified_in_row, - valid_in_row, - ) = process_pileup_row( + keep_basemod, _, modified_in_row, valid_in_row = process_pileup_row( row=row, parsed_motif=parsed_motif, region_strand=strand, single_strand=single_strand, ) if keep_basemod: - valid_base_subregion_counts += valid_in_row - modified_base_subregion_counts += modified_in_row - - with lock: - valid_base_counts[0] += valid_base_subregion_counts - modified_base_counts[0] += modified_base_subregion_counts + valid_total += valid_in_row + modified_total += modified_in_row - # Close the file descriptor/handle to the shared memory - existing_valid.close() - existing_modified.close() + return modified_total, valid_total def process_pileup_row( @@ -578,39 +959,57 @@ def process_pileup_row( """ tabix_fields = row.split("\t") pileup_basemod = tabix_fields[3] - pileup_strand = tabix_fields[5] - - if single_strand and pileup_strand.strip() != region_strand: - # We are on the wrong strand, can't keep this position - keep_basemod = False - elif len(pileup_basemod.split(",")) == 3: - pileup_modname, pileup_motif, pileup_mod_coord = pileup_basemod.split(",") - if ( + + if single_strand and tabix_fields[5] != region_strand: + # We are on the wrong strand, can't keep this position. + return (False, 0, 0, 0) + + pileup_basemod_parts = pileup_basemod.split(",") + if len(pileup_basemod_parts) == 3: + pileup_modname, pileup_motif, pileup_mod_coord = pileup_basemod_parts + keep_basemod = ( pileup_motif == parsed_motif.motif_seq and int(pileup_mod_coord) == parsed_motif.modified_pos and pileup_modname in parsed_motif.mod_codes - ): - keep_basemod = True - else: - keep_basemod = False - elif len(pileup_basemod.split(",")) == 1: + ) + elif len(pileup_basemod_parts) == 1: keep_basemod = pileup_basemod in parsed_motif.mod_codes else: raise ValueError( f"Unexpected format in bedmethyl file: {row} contains {pileup_basemod} which cannot be parsed." ) - pileup_info = tabix_fields[9].split(" ") + if not keep_basemod: + return (False, 0, 0, 0) + genomic_coord = int(tabix_fields[1]) - valid_in_row = int(pileup_info[0]) - modified_in_row = int(pileup_info[2]) - - return ( - keep_basemod, - genomic_coord, - modified_in_row, - valid_in_row, - ) + + # Modern modkit pileup bedMethyl format (0.6.x) emits full 18-column rows: + # chrom, start, end, mod, score, strand, thickStart, thickEnd, color, + # valid_cov, frac_mod, N_mod, N_canonical, N_other_mod, N_delete, N_fail, + # N_diff, N_nocall. + # Legacy/older formats may encode summary info in tabix_fields[9] as a + # whitespace-delimited payload where [0]=valid and [2]=modified. + try: + if len(tabix_fields) >= 13: + valid_in_row = int(float(tabix_fields[9])) + modified_in_row = int(float(tabix_fields[11])) + else: + pileup_info = tabix_fields[9].split() + valid_in_row = int(float(pileup_info[0])) + modified_in_row = int(float(pileup_info[2])) + except Exception: + if not getattr(process_pileup_row, "_warned_malformed_bedmethyl_row", False): + warnings.warn( + "Skipping malformed bedMethyl row while loading pileup data. " + "This warning is shown once per process.", + RuntimeWarning, + stacklevel=2, + ) + process_pileup_row._warned_malformed_bedmethyl_row = True + return (False, 0, 0, 0) + + return (True, genomic_coord, modified_in_row, valid_in_row) ################################################################################################################ @@ -618,6 +1017,28 @@ def process_pileup_row( ################################################################################################################ +def _validate_subset_parameters(subset_parameters: dict | None) -> None: + if subset_parameters is None: + return + if not isinstance(subset_parameters, dict): + raise ValueError("subset_parameters must be provided as a dictionary.") + if "array" in subset_parameters: + raise ValueError( + "subset_parameters cannot include 'array'; this is set internally." + ) + if "n" not in subset_parameters and "frac" not in subset_parameters: + raise ValueError("subset_parameters must include at least one of n or frac.") + + +def _subset_indices( + relevant_read_indices: np.ndarray, + subset_parameters: dict | None, +) -> np.ndarray: + if subset_parameters is None or relevant_read_indices.size == 0: + return relevant_read_indices + return np.sort(utils.random_sample(relevant_read_indices, **subset_parameters)) + + def read_vectors_from_hdf5( file: str | Path, motifs: list[str], @@ -630,6 +1051,8 @@ def read_vectors_from_hdf5( cores: int | None = None, # currently unused subset_parameters: dict | None = None, span_full_window: bool = False, + random_sample_n_reads: int | None = None, + min_read_length_bp: int | None = None, ) -> tuple[list[tuple], list[str], dict | None]: """ User-facing function. @@ -681,6 +1104,10 @@ def read_vectors_from_hdf5( reads to be returned. If not None, at least one of n or frac must be provided. The array parameter should not be provided here. span_full_window: If True, only load reads that fully span the window defined by region_start-region_end + random_sample_n_reads: Optional global random sample size applied after filtering and + region/motif selection. Samples unique read names (not tuple rows), then retains all + motif rows associated with those reads. + min_read_length_bp: Optional minimum read length filter (bp) applied before loading vectors. Returns: a list of tuples, each tuple containing all datasets corresponding to an individual read that @@ -691,6 +1118,18 @@ def read_vectors_from_hdf5( TODO: The way the subsetting is implemented is confusing, in that you need to pass all but one of the available parameters. """ + _validate_subset_parameters(subset_parameters) + if random_sample_n_reads is not None and int(random_sample_n_reads) <= 0: + raise ValueError("random_sample_n_reads must be > 0 when provided.") + if min_read_length_bp is not None and int(min_read_length_bp) < 0: + raise ValueError("min_read_length_bp must be >= 0 when provided.") + random_sample_n_reads = ( + int(random_sample_n_reads) if random_sample_n_reads is not None else None + ) + min_read_length_bp = ( + int(min_read_length_bp) if min_read_length_bp is not None else None + ) + with h5py.File(file, "r") as h5: datasets: list[str] = [ name for name, obj in h5.items() if isinstance(obj, h5py.Dataset) @@ -698,8 +1137,9 @@ def read_vectors_from_hdf5( if "threshold" in h5: # we are looking at an .h5 file with the new, much better compressed format that does # not know the data type intrinsically for mod and val vectors, so we must check + metadata_datasets = {"threshold", "threshold_by_motif_json"} readwise_datasets = [ - dataset for dataset in datasets if dataset not in ["threshold"] + dataset for dataset in datasets if dataset not in metadata_datasets ] compressed_binary_datasets = ["mod_vector", "val_vector"] threshold_applied_to_h5 = h5["threshold"][()] @@ -715,6 +1155,7 @@ def read_vectors_from_hdf5( read_chromosomes = np.array(h5["chromosome"], dtype=str) read_starts = np.array(h5["read_start"]) read_ends = np.array(h5["read_end"]) + read_lengths = read_ends - read_starts read_motifs = np.array(h5["motif"], dtype=str) ref_strands = np.array(h5["strand"], dtype=str) @@ -735,6 +1176,11 @@ def read_vectors_from_hdf5( & (read_starts < region_end) & (read_starts <= region_start if span_full_window else True) & (read_ends >= region_end if span_full_window else True) + & ( + read_lengths >= min_read_length_bp + if min_read_length_bp is not None + else True + ) & np.isin(read_motifs, motifs) & (read_chromosomes == chrom) & ( @@ -743,12 +1189,9 @@ def read_vectors_from_hdf5( | (ref_strands == region_strand) ) ) - if subset_parameters is not None: - relevant_read_indices = np.sort( - utils.random_sample( - relevant_read_indices, **subset_parameters - ) - ) + relevant_read_indices = _subset_indices( + relevant_read_indices, subset_parameters=subset_parameters + ) read_tuples_raw += list( zip( *( @@ -766,15 +1209,22 @@ def read_vectors_from_hdf5( [region_start for _ in relevant_read_indices], [region_end for _ in relevant_read_indices], [region_strand for _ in relevant_read_indices], + strict=False, ) ) else: regions_dict = None - relevant_read_indices = np.flatnonzero(np.isin(read_motifs, motifs)) - if subset_parameters is not None: - relevant_read_indices = np.sort( - utils.random_sample(relevant_read_indices, **subset_parameters) + relevant_read_indices = np.flatnonzero( + np.isin(read_motifs, motifs) + & ( + read_lengths >= min_read_length_bp + if min_read_length_bp is not None + else True ) + ) + relevant_read_indices = _subset_indices( + relevant_read_indices, subset_parameters=subset_parameters + ) read_tuples_raw = list( zip( *( @@ -792,6 +1242,7 @@ def read_vectors_from_hdf5( [-1 for _ in relevant_read_indices], [-1 for _ in relevant_read_indices], ["." for _ in relevant_read_indices], + strict=False, ) ) # We add region information (start, end, and strand; chromosome is already present!) @@ -813,25 +1264,53 @@ def read_vectors_from_hdf5( for tup in read_tuples_raw ] + # Optional global random sample by unique read name, applied after filtering/loading. + if random_sample_n_reads is not None and read_tuples_processed: + read_name_idx_pre = readwise_datasets.index("read_name") + unique_read_names = np.array( + sorted({row[read_name_idx_pre] for row in read_tuples_processed}, key=str) + ) + if random_sample_n_reads < unique_read_names.size: + sampled_names = set( + utils.random_sample(unique_read_names, n=random_sample_n_reads).tolist() + ) + read_tuples_processed = [ + row + for row in read_tuples_processed + if row[read_name_idx_pre] in sampled_names + ] + read_start_idx = readwise_datasets.index("read_start") read_end_idx = readwise_datasets.index("read_end") + read_name_idx = readwise_datasets.index("read_name") + motif_idx = readwise_datasets.index("motif") + mod_vector_idx = readwise_datasets.index("mod_vector") + val_vector_idx = readwise_datasets.index("val_vector") if calculate_mod_fractions: # Add the MOTIF_mod_fraction entries to the readwise_datasets list for future reference in sorting readwise_datasets += [f"{motif}_mod_fraction" for motif in motifs] - # dict[read_name][motif]=modified fraction of motif in read, float - mod_fractions_by_read_name_by_motif: defaultdict[ - str, defaultdict[str, float] - ] = defaultdict(lambda: defaultdict(lambda: 0.0)) - for motif in motifs: - for read_tuple in read_tuples_processed: - if read_tuple[readwise_datasets.index("motif")] == motif: - mod_sum = np.sum(read_tuple[readwise_datasets.index("mod_vector")]) - val_sum = np.sum(read_tuple[readwise_datasets.index("val_vector")]) - mod_fraction = mod_sum / val_sum if val_sum > 0 else 0 - mod_fractions_by_read_name_by_motif[ - read_tuple[readwise_datasets.index("read_name")] - ][motif] = mod_fraction + # dict[read_name][motif_index]=modified fraction of motif in read, float + motif_idx_by_name = {motif: i for i, motif in enumerate(motifs)} + mod_fractions_by_read_name: dict[str, list[float]] = {} + for read_tuple in read_tuples_processed: + motif = read_tuple[motif_idx] + motif_fraction_idx = motif_idx_by_name.get(motif) + if motif_fraction_idx is None: + continue + mod_sum = np.sum(read_tuple[mod_vector_idx]) + val_sum = np.sum(read_tuple[val_vector_idx]) + mod_fraction = mod_sum / val_sum if val_sum > 0 else 0 + read_name = read_tuple[read_name_idx] + if read_name not in mod_fractions_by_read_name: + mod_fractions_by_read_name[read_name] = [0.0] * len(motifs) + mod_fractions_by_read_name[read_name][motif_fraction_idx] = mod_fraction + + empty_mod_fractions = tuple(0.0 for _ in motifs) + mod_fractions_by_read_name_tup = { + read_name: tuple(mod_fractions) + for read_name, mod_fractions in mod_fractions_by_read_name.items() + } read_tuples_all = [] for read_tuple in read_tuples_processed: @@ -839,11 +1318,8 @@ def read_vectors_from_hdf5( read_tuples_all.append( tuple(val for val in read_tuple) + (read_length,) - + tuple( - mod_frac - for mod_frac in mod_fractions_by_read_name_by_motif[ - read_tuple[readwise_datasets.index("read_name")] - ].values() + + mod_fractions_by_read_name_tup.get( + read_tuple[read_name_idx], empty_mod_fractions ) ) else: @@ -919,6 +1395,8 @@ def readwise_binary_modification_arrays( quiet: bool = True, # currently unused; change to default False when pbars are implemented cores: int | None = None, # currently unused subset_parameters: dict | None = None, + random_sample_n_reads: int | None = None, + min_read_length_bp: int | None = None, ) -> tuple[list[np.ndarray], np.ndarray[int], np.ndarray[str], dict | None]: """ Primarily designed as a helper function for single-read plotting, but can be used by a user. @@ -969,6 +1447,8 @@ def readwise_binary_modification_arrays( subset_parameters: Parameters to pass to the utils.random_sample() method, to subset the reads to be returned. If not None, at least one of n or frac must be provided. The array parameter should not be provided here. + random_sample_n_reads: Optional global random sample size by unique read name. + min_read_length_bp: Optional minimum read length filter (bp) applied before loading vectors. Returns: Returns a tuple of three arrays, of length (N_READS * len(mod_names)), and a dict of regions. @@ -991,6 +1471,11 @@ def readwise_binary_modification_arrays( window_size=window_size, single_strand=single_strand, sort_by=sort_by, + quiet=quiet, + cores=cores, + subset_parameters=subset_parameters, + random_sample_n_reads=random_sample_n_reads, + min_read_length_bp=min_read_length_bp, ) read_name_index = datasets.index("read_name") mod_vector_index = datasets.index("mod_vector") @@ -1000,6 +1485,14 @@ def readwise_binary_modification_arrays( read_start_index = datasets.index("read_start") region_strand_index = datasets.index("region_strand") + if len(sorted_read_data_converted) == 0: + return ( + np.array([], dtype=int), + np.array([], dtype=int), + np.array([], dtype=str), + regions_dict, + ) + # Check this .h5 file was created with a threshold, i.e. that the mod calls are binarized if thresh is None: if not (sorted_read_data_converted[0][mod_vector_index].dtype == np.bool_): @@ -1013,18 +1506,26 @@ def readwise_binary_modification_arrays( mod_coords_list = [] motifs_list = [] - read_names = np.array( - [read_data[read_name_index] for read_data in sorted_read_data_converted] - ) - # TODO: handle the case where a read shows up in more than one different region - _, unique_first_indices = np.unique(read_names, return_index=True) - unique_in_order = read_names[np.sort(unique_first_indices)] - string_to_int = { - read_name: index for index, read_name in enumerate(unique_in_order) - } - read_ints = np.array([string_to_int[read_name] for read_name in read_names]) - - for read_int, read_data in zip(read_ints, sorted_read_data_converted): + read_ids_by_region_key: dict[tuple[str, int, int, str], int] = {} + read_ints = np.empty(len(sorted_read_data_converted), dtype=int) + next_read_int = 0 + for idx, read_data in enumerate(sorted_read_data_converted): + read_key = ( + read_data[read_name_index], + read_data[region_start_index], + read_data[region_end_index], + read_data[region_strand_index], + ) + read_int = read_ids_by_region_key.get(read_key) + if read_int is None: + read_int = next_read_int + read_ids_by_region_key[read_key] = read_int + next_read_int += 1 + read_ints[idx] = read_int + + for read_int, read_data in zip( + read_ints, sorted_read_data_converted, strict=False + ): if thresh is None: mod_pos_in_read = np.flatnonzero(read_data[mod_vector_index]) else: diff --git a/dimelo/models.py b/dimelo/models.py new file mode 100644 index 0000000..3dafc8d --- /dev/null +++ b/dimelo/models.py @@ -0,0 +1,440 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +import pandas as pd + +_SELECTED_REGION_CHROM_COLUMNS = {"chrom", "chromosome"} +_SELECTED_REGION_REQUIRED_COLUMNS = {"start", "end"} + + +def _validate_selected_regions_dataframe( + selected_regions: pd.DataFrame, + *, + owner: str, +) -> None: + if not isinstance(selected_regions, pd.DataFrame): + raise TypeError(f"{owner}.selected_regions must be a pandas DataFrame") + + has_chrom_column = bool( + _SELECTED_REGION_CHROM_COLUMNS & set(selected_regions.columns) + ) + has_required_columns = _SELECTED_REGION_REQUIRED_COLUMNS.issubset( + selected_regions.columns + ) + if not has_chrom_column or not has_required_columns: + raise ValueError( + f"{owner}.selected_regions must include 'start', 'end', and either " + "'chrom' or 'chromosome'" + ) + + +@dataclass +class SampleSpec: + sample_id: str + condition: str + extract_h5: str | Path + regions_bed: str | Path | None = None + replicate: int | None = None + metadata: dict[str, Any] | None = None + + +@dataclass +class DatasetArtifact: + sample_id: str + artifact_type: str + path: str | Path + format: str + params: dict[str, Any] + provenance: dict[str, Any] + metadata: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + if self.params is None: + raise ValueError("DatasetArtifact.params cannot be None") + if self.provenance is None: + raise ValueError("DatasetArtifact.provenance cannot be None") + if self.metadata is None: + raise ValueError("DatasetArtifact.metadata cannot be None") + + +@dataclass +class ContrastSpec: + mode: str + numerator: list[str] | None = None + denominator: list[str] | None = None + background: list[str] | None = None + time_order: list[str] | None = None + pairing_key: str | None = None + reference_condition: str | None = None + metadata: dict[str, Any] | None = None + + def __post_init__(self) -> None: + allowed_modes = { + "single_dataset", + "pairwise", + "matched_pairwise", + "group_vs_group", + "background_adjusted", + "time_course", + } + if self.mode not in allowed_modes: + raise ValueError(f"Unsupported contrast mode: {self.mode}") + if self.mode in {"pairwise", "group_vs_group"} and ( + not self.numerator or not self.denominator + ): + raise ValueError( + "ContrastSpec pairwise/group_vs_group modes require numerator and denominator." + ) + if self.mode == "matched_pairwise" and ( + not self.numerator or not self.denominator or not self.pairing_key + ): + raise ValueError( + "ContrastSpec matched_pairwise mode requires numerator, " + "denominator, and pairing_key." + ) + if self.mode == "background_adjusted": + if not self.numerator or not self.denominator: + raise ValueError( + "ContrastSpec background_adjusted mode requires numerator and denominator." + ) + if not self.background: + raise ValueError( + "ContrastSpec background_adjusted mode requires background." + ) + if self.mode == "time_course" and not self.time_order: + raise ValueError("ContrastSpec time_course mode requires time_order.") + + +@dataclass +class SharedClusterModel: + mode: str + motifs: list[str] + feature_names: list[str] + preprocessing: dict[str, Any] + estimator: Any + cluster_labels: list[str] + fit_metadata: dict[str, Any] + + +@dataclass +class SharedClusterResult: + model: SharedClusterModel + assignments: pd.DataFrame + cluster_distribution: pd.DataFrame + condition_distribution: pd.DataFrame + distribution_change: pd.DataFrame | None + cluster_profiles: pd.DataFrame + region_summaries: pd.DataFrame | None + plot_data: dict[str, pd.DataFrame | dict[str, Any]] + figures: dict[str, Any] = field(default_factory=dict) + metadata: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "model": self.model, + "assignments": self.assignments, + "cluster_distribution": self.cluster_distribution, + "condition_distribution": self.condition_distribution, + "cluster_profiles": self.cluster_profiles, + "plot_data": self.plot_data, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "SharedClusterResult requires non-None values for: " + f"{', '.join(missing)}" + ) + + +@dataclass +class SharedClusterContrastResult: + summary: pd.DataFrame + details: pd.DataFrame + pairwise: pd.DataFrame | None = None + plot_data: dict[str, pd.DataFrame | dict[str, Any]] = field(default_factory=dict) + figures: dict[str, Any] = field(default_factory=dict) + metadata: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "summary": self.summary, + "details": self.details, + "plot_data": self.plot_data, + "figures": self.figures, + "metadata": self.metadata, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "SharedClusterContrastResult requires non-None values for: " + f"{', '.join(missing)}" + ) + + +@dataclass +class GlobalAnalysisResult: + summary: pd.DataFrame + windows: pd.DataFrame | None + normalization_factors: pd.DataFrame + plot_data: dict[str, pd.DataFrame | dict[str, Any]] + metadata: dict[str, Any] = field(default_factory=dict) + figures: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "summary": self.summary, + "normalization_factors": self.normalization_factors, + "plot_data": self.plot_data, + "metadata": self.metadata, + "figures": self.figures, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "GlobalAnalysisResult requires non-None values for: " + f"{', '.join(missing)}" + ) + + +@dataclass +class RegionContrastResult: + regions: pd.DataFrame + summary: pd.DataFrame + contrast: ContrastSpec + plot_data: dict[str, pd.DataFrame | dict[str, Any]] + metadata: dict[str, Any] = field(default_factory=dict) + figures: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "regions": self.regions, + "summary": self.summary, + "contrast": self.contrast, + "plot_data": self.plot_data, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "RegionContrastResult requires non-None values for: " + f"{', '.join(missing)}" + ) + + +@dataclass +class RegionDiscoveryResult: + hits: pd.DataFrame + windows: pd.DataFrame + contrast: ContrastSpec | None + plot_data: dict[str, pd.DataFrame | dict[str, Any]] + metadata: dict[str, Any] = field(default_factory=dict) + figures: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "hits": self.hits, + "windows": self.windows, + "plot_data": self.plot_data, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "RegionDiscoveryResult requires non-None values for: " + f"{', '.join(missing)}" + ) + + +@dataclass +class RegionDiscoveryClusterContrastResult: + discovery: RegionDiscoveryResult + clustering: SharedClusterResult + contrasts: RegionContrastResult + selected_regions: pd.DataFrame + metadata: dict[str, Any] = field(default_factory=dict) + figures: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "discovery": self.discovery, + "clustering": self.clustering, + "contrasts": self.contrasts, + "selected_regions": self.selected_regions, + "metadata": self.metadata, + "figures": self.figures, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "RegionDiscoveryClusterContrastResult requires non-None values for: " + f"{', '.join(missing)}" + ) + if not isinstance(self.discovery, RegionDiscoveryResult): + raise TypeError( + "RegionDiscoveryClusterContrastResult.discovery must be a " + "RegionDiscoveryResult" + ) + if not isinstance(self.clustering, SharedClusterResult): + raise TypeError( + "RegionDiscoveryClusterContrastResult.clustering must be a " + "SharedClusterResult" + ) + if not isinstance(self.contrasts, RegionContrastResult): + raise TypeError( + "RegionDiscoveryClusterContrastResult.contrasts must be a " + "RegionContrastResult" + ) + _validate_selected_regions_dataframe( + self.selected_regions, + owner="RegionDiscoveryClusterContrastResult", + ) + + +@dataclass +class RegionDiscoveryClusterResult: + discovery: RegionDiscoveryResult + clustering: SharedClusterResult + selected_regions: pd.DataFrame + metadata: dict[str, Any] = field(default_factory=dict) + figures: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "discovery": self.discovery, + "clustering": self.clustering, + "selected_regions": self.selected_regions, + "metadata": self.metadata, + "figures": self.figures, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "RegionDiscoveryClusterResult requires non-None values for: " + f"{', '.join(missing)}" + ) + if not isinstance(self.discovery, RegionDiscoveryResult): + raise TypeError( + "RegionDiscoveryClusterResult.discovery must be a RegionDiscoveryResult" + ) + if not isinstance(self.clustering, SharedClusterResult): + raise TypeError( + "RegionDiscoveryClusterResult.clustering must be a SharedClusterResult" + ) + + +@dataclass +class ChipAtlasEnrichmentResult: + request_id: str + status: str + results: pd.DataFrame | None + query: dict[str, Any] = field(default_factory=dict) + status_history: list[dict[str, Any]] = field(default_factory=list) + submit_url: str | None = None + status_url: str | None = None + result_url: str | None = None + metadata: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + if not self.request_id: + raise ValueError("ChipAtlasEnrichmentResult.request_id cannot be empty.") + if not self.status: + raise ValueError("ChipAtlasEnrichmentResult.status cannot be empty.") + if self.query is None: + raise ValueError("ChipAtlasEnrichmentResult.query cannot be None.") + if self.status_history is None: + raise ValueError("ChipAtlasEnrichmentResult.status_history cannot be None.") + if self.metadata is None: + raise ValueError("ChipAtlasEnrichmentResult.metadata cannot be None.") + + +@dataclass +class UniBindJobResult: + job_id: str + status: str + job_url: str + endpoint_url: str + results_url: str | None = None + download_urls: list[str] = field(default_factory=list) + query: dict[str, Any] = field(default_factory=dict) + status_history: list[dict[str, Any]] = field(default_factory=list) + metadata: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + if not self.job_id: + raise ValueError("UniBindJobResult.job_id cannot be empty.") + if not self.status: + raise ValueError("UniBindJobResult.status cannot be empty.") + if not self.job_url: + raise ValueError("UniBindJobResult.job_url cannot be empty.") + if not self.endpoint_url: + raise ValueError("UniBindJobResult.endpoint_url cannot be empty.") + if self.download_urls is None: + raise ValueError("UniBindJobResult.download_urls cannot be None.") + if self.query is None: + raise ValueError("UniBindJobResult.query cannot be None.") + if self.status_history is None: + raise ValueError("UniBindJobResult.status_history cannot be None.") + if self.metadata is None: + raise ValueError("UniBindJobResult.metadata cannot be None.") + + +@dataclass +class CohortSpec: + cohort_id: str + sample_ids: list[str] + workflow: str + params: dict[str, Any] + metadata: dict[str, Any] | None = None + + +@dataclass +class BatchJob: + job_id: str + workflow: str + cohorts: list[CohortSpec] + artifact_policy: str = "prefer_cached" + metadata: dict[str, Any] | None = None + + +@dataclass +class ModkitDMRPairResult: + output_path: Path + segment_path: Path | None + command: list[str] + sites: pd.DataFrame + segments: pd.DataFrame | None + high_confidence_sites: pd.DataFrame + metadata: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + if self.output_path is None: + raise ValueError("ModkitDMRPairResult.output_path cannot be None.") + if self.command is None: + raise ValueError("ModkitDMRPairResult.command cannot be None.") + if self.sites is None: + raise ValueError("ModkitDMRPairResult.sites cannot be None.") + if self.high_confidence_sites is None: + raise ValueError( + "ModkitDMRPairResult.high_confidence_sites cannot be None." + ) + if self.metadata is None: + raise ValueError("ModkitDMRPairResult.metadata cannot be None.") + + +@dataclass +class ModkitDMRMultiResult: + out_dir: Path + command: list[str] + pair_files: pd.DataFrame + metadata: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + if self.out_dir is None: + raise ValueError("ModkitDMRMultiResult.out_dir cannot be None.") + if self.command is None: + raise ValueError("ModkitDMRMultiResult.command cannot be None.") + if self.pair_files is None: + raise ValueError("ModkitDMRMultiResult.pair_files cannot be None.") + if self.metadata is None: + raise ValueError("ModkitDMRMultiResult.metadata cannot be None.") diff --git a/dimelo/parse_bam.py b/dimelo/parse_bam.py index e86b7ee..507554a 100644 --- a/dimelo/parse_bam.py +++ b/dimelo/parse_bam.py @@ -1,7 +1,11 @@ +import csv import gzip +import itertools +import json import multiprocessing import os import subprocess +import sys from collections import defaultdict from pathlib import Path @@ -23,6 +27,323 @@ # Specifies how many reads to check for the base modifications of interest. NUM_READS_TO_CHECK = 100 +ThresholdInput = int | float | dict[str, int | float] | None + + +def _should_render_live_progress() -> bool: + """ + Decide whether tqdm live bars should be rendered in this process context. + + "auto" mode avoids notebook/non-TTY contexts where carriage-return bars are + frequently rendered as mangled output. + """ + mode = os.environ.get("DIMELO_PROGRESS_MODE", "auto").strip().lower() + if mode in {"off", "none", "false", "0"}: + return False + if mode in {"on", "force", "true", "1"}: + return True + in_notebook = "JPY_PARENT_PID" in os.environ or "IPYKERNEL_PARENT_PID" in os.environ + return sys.stderr.isatty() and not in_notebook + + +def _unique_preserve_order(items: list[str]) -> list[str]: + seen: set[str] = set() + ordered: list[str] = [] + for item in items: + if item in seen: + continue + seen.add(item) + ordered.append(item) + return ordered + + +def _build_pileup_targeting_command_list( + motifs: list[str], + capabilities: run_modkit.ModkitCapabilities, +) -> list[str]: + # modkit 0.6.x requires --modified-bases. In some environments capability + # detection can be stale during in-session binary upgrades, so treat version + # 0.6+ as requiring modified-bases even if the help-derived flag is false. + requires_modified_bases = capabilities.supports_modified_bases or ( + capabilities.version_tuple is not None + and len(capabilities.version_tuple) >= 2 + and (capabilities.version_tuple[0], capabilities.version_tuple[1]) >= (0, 6) + ) + + motif_command_list: list[str] = [] + modified_bases: list[str] = [] + include_cpg = False + motif_pairs_seen: set[tuple[str, int]] = set() + + for motif in motifs: + parsed_motif = utils.ParsedMotif(motif) + if requires_modified_bases: + modified_bases.extend( + f"{parsed_motif.modified_base}:{mod_code}" + for mod_code in parsed_motif.mod_codes + ) + + if parsed_motif.motif_seq == "CG" and parsed_motif.modified_pos == 0: + include_cpg = True + continue + + if ( + requires_modified_bases + and len(parsed_motif.motif_seq) == 1 + and parsed_motif.modified_pos == 0 + ): + continue + + key = (parsed_motif.motif_seq, parsed_motif.modified_pos) + if key in motif_pairs_seen: + continue + motif_pairs_seen.add(key) + motif_command_list.extend( + ["--motif", parsed_motif.motif_seq, str(parsed_motif.modified_pos)] + ) + + modified_bases_command_list: list[str] = [] + if requires_modified_bases: + for modified_base in _unique_preserve_order(modified_bases): + modified_bases_command_list.extend(["--modified-bases", modified_base]) + if len(modified_bases_command_list) == 0: + raise ValueError( + "No mod codes were resolved from motifs; cannot build required " + "--modified-bases arguments for modkit pileup. Pass motifs with " + "explicit mod codes (for example 'A,0,a' or 'CG,0,m')." + ) + + cpg_command_list = ["--cpg"] if include_cpg else [] + return modified_bases_command_list + cpg_command_list + motif_command_list + + +def _build_extract_targeting_command_list( + parsed_motif: utils.ParsedMotif, +) -> list[str]: + if parsed_motif.motif_seq == "CG" and parsed_motif.modified_pos == 0: + return ["--cpg"] + return ["--motif", parsed_motif.motif_seq, str(parsed_motif.modified_pos)] + + +def _build_mod_threshold_command_list( + motifs: list[str], + motif_thresholds: dict[str, float], + capabilities: run_modkit.ModkitCapabilities, +) -> list[str]: + if capabilities.supports_mod_threshold: + threshold_flag = "--mod-threshold" + elif capabilities.supports_mod_thresholds: + threshold_flag = "--mod-thresholds" + else: + threshold_flag = "--mod-threshold" + + mod_code_thresholds: dict[str, float] = {} + for motif in motifs: + parsed_motif = utils.ParsedMotif(motif) + threshold_for_motif = motif_thresholds[motif] + for mod_code in parsed_motif.mod_codes: + existing_threshold = mod_code_thresholds.get(mod_code) + if ( + existing_threshold is not None + and abs(existing_threshold - threshold_for_motif) > 1e-12 + ): + raise ValueError( + "Cannot apply different thresholds to motifs that share mod code " + f"{mod_code!r}. Received both {existing_threshold} and {threshold_for_motif}." + ) + mod_code_thresholds[mod_code] = threshold_for_motif + + command: list[str] = [] + for mod_code in _unique_preserve_order(list(mod_code_thresholds.keys())): + command.extend([threshold_flag, f"{mod_code}:{mod_code_thresholds[mod_code]}"]) + return command + + +def _build_extract_command_prefix( + input_file: Path, + output_txt: Path, + capabilities: run_modkit.ModkitCapabilities, +) -> list[str | Path]: + if _extract_requires_subcommands(capabilities): + return [capabilities.executable, "extract", "full", input_file, output_txt] + return [capabilities.executable, "extract", input_file, output_txt] + + +def _build_extract_reference_command_list( + ref_genome: Path, + capabilities: run_modkit.ModkitCapabilities, +) -> list[str | Path]: + if _extract_requires_subcommands(capabilities): + if capabilities.extract_supports_reference_long: + return ["--reference", ref_genome] + # modkit 0.6+ extract subcommands require --reference for motif filtering. + return ["--reference", ref_genome] + if capabilities.extract_supports_reference_short: + return ["--ref", ref_genome] + if capabilities.extract_supports_reference_long: + return ["--reference", ref_genome] + return ["--ref", ref_genome] + + +def _extract_requires_subcommands( + capabilities: run_modkit.ModkitCapabilities, +) -> bool: + if capabilities.supports_extract_subcommands: + return True + return bool( + capabilities.version_tuple is not None + and len(capabilities.version_tuple) >= 2 + and (capabilities.version_tuple[0], capabilities.version_tuple[1]) >= (0, 6) + ) + + +def _build_implicit_tag_command_list( + capabilities: run_modkit.ModkitCapabilities, +) -> list[str]: + if capabilities.supports_force_allow_implicit: + return ["--force-allow-implicit"] + return [] + + +def _modkit_requires_multi_motif_pileup_split( + capabilities: run_modkit.ModkitCapabilities, +) -> bool: + if capabilities.version_tuple is None or len(capabilities.version_tuple) < 2: + return False + return (capabilities.version_tuple[0], capabilities.version_tuple[1]) >= (0, 6) + + +def _canonical_motif_key(motif: str) -> str: + parsed = utils.ParsedMotif(motif) + return f"{parsed.motif_seq},{parsed.modified_pos}" + + +def _group_motifs_for_pileup( + motifs: list[str], + capabilities: run_modkit.ModkitCapabilities, +) -> list[tuple[str, list[str]]]: + if ( + not _modkit_requires_multi_motif_pileup_split(capabilities) + or len({_canonical_motif_key(motif) for motif in motifs}) <= 1 + ): + return [("combined", motifs)] + + grouped: dict[str, list[str]] = {} + ordered_keys: list[str] = [] + for motif in motifs: + canonical_key = _canonical_motif_key(motif) + if canonical_key not in grouped: + grouped[canonical_key] = [] + ordered_keys.append(canonical_key) + grouped[canonical_key].append(motif) + return [(key, grouped[key]) for key in ordered_keys] + + +def _resolve_motif_thresholds( + motifs: list[str], + thresh: ThresholdInput, + quiet: bool, +) -> dict[str, float] | None: + if thresh is None: + return None + + if isinstance(thresh, (int, float)): + adjusted = utils.adjust_threshold(float(thresh), quiet=quiet) + return {motif: adjusted for motif in motifs} + + if not isinstance(thresh, dict): + raise TypeError( + "thresh must be None, a number, or a dict mapping motif keys to thresholds." + ) + + resolved: dict[str, float] = {} + for motif in motifs: + canonical_key = _canonical_motif_key(motif) + threshold_value = None + for key in (motif, canonical_key, "default", "*"): + if key in thresh: + threshold_value = thresh[key] + break + if threshold_value is None: + raise ValueError( + f"Missing threshold for motif {motif!r}. Provide an exact key, canonical key " + f"{canonical_key!r}, or a default key ('default' or '*')." + ) + resolved[motif] = utils.adjust_threshold(float(threshold_value), quiet=quiet) + return resolved + + +def _threads_command_list(cores: int | None, quiet: bool) -> list[str]: + cores_avail = multiprocessing.cpu_count() + if cores is None: + if not quiet: + print( + f"No specified number of cores requested. {cores_avail} available on machine, allocating all." + ) + return ["--threads", str(cores_avail)] + if cores > cores_avail: + if not quiet: + print( + f"Warning: {cores} cores request, {cores_avail} available. Allocating {cores_avail}" + ) + return ["--threads", str(cores_avail)] + if not quiet: + print(f"Allocating requested {cores} cores.") + return ["--threads", str(cores)] + + +def _compress_uint8_vector( + vector: np.ndarray, + compress_level: int, +) -> np.ndarray: + return np.frombuffer( + gzip.compress(vector.tobytes(), compresslevel=compress_level), + dtype=np.uint8, + ) + + +def _unlink_existing(*paths: Path) -> None: + for path in paths: + path.unlink(missing_ok=True) + + +def _text_file_has_any_rows(path: Path) -> bool: + try: + with open(path) as handle: + for line in handle: + if line.strip(): + return True + except FileNotFoundError: + return False + return False + + +def _sanitize_motif_group_label(label: str) -> str: + sanitized = "".join(char if char.isalnum() else "_" for char in label).strip("_") + return sanitized or "motif" + + +def _reference_oriented_read_offset( + *, + pos_in_read: int, + read_length: int, + ref_strand: str, +) -> int: + """ + Convert modkit's read-oriented position into a left-to-right reference offset. + """ + if read_length <= 0: + raise ValueError(f"read length must be positive; got {read_length}.") + if pos_in_read < 0 or pos_in_read >= read_length: + raise ValueError( + f"pos_in_read {pos_in_read} is out of bounds for read length {read_length}." + ) + if ref_strand == "+": + return pos_in_read + if ref_strand == "-": + return read_length - pos_in_read - 1 + raise ValueError(f"Unexpected strand '{ref_strand}' in modkit extract row.") + """ User-facing parse operations: pileup and extract @@ -36,13 +357,15 @@ def pileup( output_directory: str | Path | None = None, regions: str | Path | list[str | Path] | None = None, motifs: list = ["A,0", "CG,0"], - thresh: float | None = None, + thresh: ThresholdInput = None, window_size: int | None = None, cores: int | None = None, log: bool = False, cleanup: bool = True, + overwrite: bool = True, quiet: bool = False, override_checks: bool = False, + modkit_executable: str | Path | None = None, ) -> tuple[Path, Path]: """ Takes a bam file containing long read sequencing data aligned @@ -80,14 +403,21 @@ def pileup( output_directory: optional str or Path pointing to an output directory. If left as None, outputs will be stored in a new folder within the input directory. - regions: TODO + regions: optional region selector passed through to modkit via an include-bed + file. This may be a BED file path, a single region file, or a list of + region files/paths that define the loci to process. When paired with + ``window_size``, the provided regions are expanded around their centers + before being passed to modkit. motifs: a list of strings specifying which base modifications to look for. The basemods are each specified as {sequence_motif},{position_of_modification}. For example, a methylated adenine is specified as 'A,0' and CpG methylation is specified as 'CG,0'. - thresh: float point number specifying the base modification probability threshold - used to delineate modificaton calls as True or False. When set to None, modkit - will select its own threshold automatically based on the data. + thresh: base modification threshold specification. Accepted forms are: + * None: modkit infers thresholds from data + * float/int: one threshold for all motifs + * dict: motif-specific thresholds keyed by exact motif string + (e.g. ``{"A,0": 0.7, "CG,0": 0.8}``), canonical motif key, or + ``default``/``*`` fallback. window_size: an integer specifying a window around the center of each bed_file region. If set to None, the bed_file is used unmodified. If set to a non-zero positive integer, the bed_file regions are replaced by new regions with that @@ -99,7 +429,13 @@ def pileup( cleanup: a boolean specifying whether to clean up to keep intermediate outputs. The final processed files are not human-readable, whereas the intermediate outputs are. However, intermediate outputs can also be quite large. + overwrite: when True (default), existing outputs for `output_name` are + replaced. When False, existing completed outputs are reused and parsing + is skipped. If partial/conflicting outputs exist, raises FileExistsError. override_checks: convert errors from input checking into warnings if True + modkit_executable: optional executable name or path to a specific modkit + binary. If None, dimelo resolves modkit from PATH (or + DIMELO_MODKIT_EXECUTABLE when set). Returns: Path object pointing to the compressed and indexed .bed.gz bedmethyl file, ready @@ -107,155 +443,209 @@ def pileup( Path object pointing to 'regions.processed.bed', the `--include-bed` file used for `modkit pileup` """ - """ - TODO: There are a lot of issues that are all related here: - dimelo/parse_bam.py:150: error: Incompatible types in assignment (expression has type "Path | None", variable has type "str | Path") [assignment] - dimelo/parse_bam.py:169: error: Argument "input_file" to "prep_outputs" has incompatible type "str | Path"; expected "Path" [arg-type] - dimelo/parse_bam.py:256: error: Argument "input_file" to "run_with_progress_bars" has incompatible type "str | Path"; expected "Path" [arg-type] - dimelo/parse_bam.py:257: error: Argument "ref_genome" to "run_with_progress_bars" has incompatible type "str | Path"; expected "Path" [arg-type] - - I'm not sure of the most elegant way to fix it. Come back and address. - """ - ## Verify and prepare inputs and outputs input_file, ref_genome, output_directory = utils.sanitize_path_args( input_file, ref_genome, output_directory ) - - try: - verify_inputs(input_file, motifs, ref_genome) - except Exception as e: - if override_checks: - if not quiet: - print(f"WARNING: {e}") - else: - raise Exception( - f'{e}\nIf you are confident that your inputs are ok, pass "override_checks=True" to convert to warning and proceed with processing.' - ) from e + input_file = Path(input_file) + ref_genome = Path(ref_genome) + output_directory = None if output_directory is None else Path(output_directory) output_path, (output_bedmethyl, output_bedmethyl_sorted, output_pileup_path, _) = ( prep_output_directory( output_directory=output_directory, output_name=output_name, - input_file=input_file, + input_file=input_file.parent, output_file_names=[ "pileup.bed", "pileup.sorted.bed", "pileup.sorted.bed.gz", "pileup.sorted.bed.gz.tbi", ], + overwrite=False, ) ) + output_pileup_tbi = Path(f"{output_pileup_path}.tbi") + processed_regions_candidate = output_path / "regions.processed.bed" + processed_regions_existing = ( + processed_regions_candidate if processed_regions_candidate.exists() else None + ) + + if not overwrite: + final_outputs_exist = output_pileup_path.exists() and output_pileup_tbi.exists() + regions_ready = regions is None or processed_regions_existing is not None + if final_outputs_exist and regions_ready: + if not quiet: + print( + f"overwrite=False and existing outputs found. Reusing {output_pileup_path}." + ) + return output_pileup_path, processed_regions_existing + + existing_paths = [ + output_bedmethyl, + output_bedmethyl_sorted, + output_pileup_path, + output_pileup_tbi, + ] + if processed_regions_existing is not None: + existing_paths.append(processed_regions_existing) + conflicting_paths = [path for path in existing_paths if path.exists()] + if len(conflicting_paths) > 0: + conflict_names = ", ".join(path.name for path in conflicting_paths) + raise FileExistsError( + "overwrite=False but output directory contains existing parse artifacts " + f"for '{output_name}': {conflict_names}. " + "Set overwrite=True to regenerate, or remove stale files." + ) + else: + _unlink_existing( + output_bedmethyl, + output_bedmethyl_sorted, + output_pileup_path, + output_pileup_tbi, + processed_regions_candidate, + ) + + capabilities = run_modkit._ensure_modkit_available( + quiet=quiet, + executable=modkit_executable, + ) + + try: + verify_inputs(input_file, motifs, ref_genome) + except Exception as e: + if override_checks: + if not quiet: + print(f"WARNING: {e}") + else: + raise Exception( + f'{e}\nIf you are confident that your inputs are ok, pass "override_checks=True" to convert to warning and proceed with processing.' + ) from e ## Build up the command list to be sent to modkit, then run modkit - # TODO: This is mildly confusing. I get what it's doing, but it's hard to follow / names are bad. Also, why is it used in cleanup here, but not in extract? region_command_list, processed_regions_path = create_region_command_list( output_path, regions, window_size, ) - motif_command_list = [] - if len(motifs) > 0: - for motif in motifs: - parsed_motif = utils.ParsedMotif(motif) - motif_command_present = False - for a, b in zip(motif_command_list, motif_command_list[1:]): - if a == parsed_motif.motif_seq and b == str(parsed_motif.modified_pos): - # This motif is already going to be processed; we want to skip adding it a second - # time because modkit does not like duplicate motifs. - # It's actually ok if it's a different mod code in the two cases because the pileup - # operation, under the hood, keeps all mod codes. Filtering is only done when loading. - motif_command_present = True - break - if not motif_command_present: - motif_command_list.append("--motif") - motif_command_list.append(parsed_motif.motif_seq) - motif_command_list.append(str(parsed_motif.modified_pos)) - else: + if len(motifs) == 0: raise ValueError("Error: no motifs specified. Nothing to process.") + cores_command_list = _threads_command_list(cores=cores, quiet=quiet) + + motif_groups = _group_motifs_for_pileup(motifs=motifs, capabilities=capabilities) + split_pileup_runs = len(motif_groups) > 1 + if split_pileup_runs and not quiet: + print( + "Detected multiple motif contexts with modkit 0.6.x; running per-motif pileups " + "and merging outputs to avoid mixed-motif empty output behavior." + ) - if log: - if not quiet: - print("Logging to ", Path(output_path) / "pileup-log") - log_command_list = ["--log-filepath", Path(output_path) / "pileup-log"] - else: - log_command_list = [] - - # TODO: This should be a method, like create_region_specifier, or just combined into a prep method for the start... - cores_avail = multiprocessing.cpu_count() - if cores is None: - if not quiet: - print( - f"No specified number of cores requested. {cores_avail} available on machine, allocating all." - ) - cores_command_list = ["--threads", str(cores_avail)] - elif cores > cores_avail: - if not quiet: - print( - f"Warning: {cores} cores request, {cores_avail} available. Allocating {cores_avail}" - ) - cores_command_list = ["--threads", str(cores_avail)] - else: - if not quiet: - print(f"Allocating requested {cores} cores.") - cores_command_list = ["--threads", str(cores)] - - # TODO: This is SO SO SO similar to extract; just the ValueError vs. printing. I think this can be resolved - mod_thresh_command_list: list[str] = [] - if thresh is None: + motif_thresholds = _resolve_motif_thresholds( + motifs=motifs, + thresh=thresh, + quiet=quiet, + ) + if motif_thresholds is None: if not quiet: print( "No base modification threshold provided. Using adaptive threshold selection via modkit." ) else: - adjusted_threshold = utils.adjust_threshold(thresh, quiet=quiet) - if adjusted_threshold < 0.5 and not quiet: + if any(value < 0.5 for value in motif_thresholds.values()) and not quiet: print( f"WARNING: thresh {thresh} is very low and may lead to unexpected behavior. Typical thresholds are at least 0.5 or 128." ) - for motif in motifs: - parsed_motif = utils.ParsedMotif(motif) - for mod_code in parsed_motif.mod_codes: - mod_thresh_command_list = mod_thresh_command_list + [ - "--mod-thresholds", - f"{mod_code}:{adjusted_threshold}", - ] - ref_genome_command_list = ["--ref", ref_genome] filter_command_list = ["--filter-threshold", "0"] + implicit_tag_command_list = _build_implicit_tag_command_list(capabilities) + + intermediate_pileup_files: list[Path] = [] + for index, (group_key, group_motifs) in enumerate(motif_groups, start=1): + group_suffix = _sanitize_motif_group_label(group_key) + group_output_bedmethyl = ( + output_bedmethyl + if not split_pileup_runs + else output_path / f"pileup.part{index}.{group_suffix}.bed" + ) + _unlink_existing(group_output_bedmethyl) + intermediate_pileup_files.append(group_output_bedmethyl) - pileup_command_list = ( - ["modkit", "pileup", input_file, output_bedmethyl] - + region_command_list - + motif_command_list - + ref_genome_command_list - + filter_command_list - + mod_thresh_command_list - + cores_command_list - + log_command_list - ) + group_motif_command_list = _build_pileup_targeting_command_list( + motifs=group_motifs, + capabilities=capabilities, + ) + group_mod_thresh_command_list: list[str] = [] + if motif_thresholds is not None: + group_motif_thresholds = { + motif: motif_thresholds[motif] for motif in group_motifs + } + group_mod_thresh_command_list = _build_mod_threshold_command_list( + motifs=group_motifs, + motif_thresholds=group_motif_thresholds, + capabilities=capabilities, + ) - # TODO: Do we need to store and use the output from this method? Previously was being printed immediately afterward. - _ = run_modkit.run_with_progress_bars( - command_list=pileup_command_list, - input_file=input_file, - ref_genome=ref_genome, - motifs=motifs, - load_fasta_regex=r"\s+\[.*?\]\s+(\d+)\s+Reading", - find_motifs_regex=r"\s+(\d+)/(\d+)\s+finding\s+([A-Za-z0-9,]+)\s+motifs", - contigs_progress_regex=r"\s+(\d+)/(\d+)\s+contigs", - single_contig_regex=r"\s+(\d+)/(\d+)\s+processing\s+([\w]+)[^\w]", - buffer_size=50, - progress_granularity=25, - done_str="Done", - err_str="Error", - expect_done=True, - quiet=quiet, - ) - # print(done_string) + if log: + log_path = ( + Path(output_path) / "pileup-log" + if not split_pileup_runs + else Path(output_path) / f"pileup-log.{index}.{group_suffix}" + ) + log_command_list = ["--log-filepath", log_path] + if not quiet and not split_pileup_runs: + print("Logging to ", log_path) + else: + log_command_list = [] + + base_pileup_command = ( + [capabilities.executable, "pileup", input_file, group_output_bedmethyl] + + region_command_list + + group_motif_command_list + + ref_genome_command_list + + filter_command_list + + implicit_tag_command_list + + cores_command_list + + log_command_list + ) + + def _run_pileup_command( + extra_args: list[str], + *, + base_command: list[str] = base_pileup_command, + motifs_for_progress: list[tuple[str, int, str]] = group_motifs, + ) -> None: + run_modkit.run_with_progress_bars( + command_list=base_command + extra_args, + input_file=input_file, + ref_genome=ref_genome, + motifs=motifs_for_progress, + load_fasta_regex=r"\s+\[.*?\]\s+(\d+)\s+Reading", + find_motifs_regex=r"\s+(\d+)/(\d+)\s+finding\s+([A-Za-z0-9,]+)\s+motifs", + contigs_progress_regex=r"\s+(\d+)/(\d+)\s+contigs", + single_contig_regex=r"\s+(\d+)/(\d+)\s+processing\s+([\w]+)[^\w]", + buffer_size=50, + progress_granularity=25, + done_str="Done", + err_str="Error", + expect_done=True, + quiet=quiet, + ) + + _run_pileup_command(group_mod_thresh_command_list) + + if split_pileup_runs: + with open(output_bedmethyl, "w") as merged_file: + for intermediate_file in intermediate_pileup_files: + if not intermediate_file.exists(): + continue + with open(intermediate_file) as part_file: + for line in part_file: + merged_file.write(line) + for intermediate_file in intermediate_pileup_files: + _unlink_existing(intermediate_file) ## Sort, compress, and index the output bedmethyl file @@ -266,12 +656,8 @@ def pileup( pysam.tabix_compress(output_bedmethyl_sorted, output_pileup_path, force=True) pysam.tabix_index(str(output_pileup_path), preset="bed", force=True) - # TODO: Can cleanup be consolidated? if cleanup: - if output_bedmethyl.exists(): - output_bedmethyl.unlink() - if output_bedmethyl_sorted.exists(): - output_bedmethyl_sorted.unlink() + _unlink_existing(output_bedmethyl, output_bedmethyl_sorted) return output_pileup_path, processed_regions_path @@ -283,13 +669,15 @@ def extract( output_directory: str | Path | None = None, regions: str | Path | list[str | Path] | None = None, motifs: list = ["A,0", "CG,0", "GCH,1"], - thresh: float | None = None, + thresh: ThresholdInput = None, window_size: int | None = None, cores: int | None = None, log: bool = False, cleanup: bool = True, + overwrite: bool = True, quiet: bool = False, override_checks: bool = False, + modkit_executable: str | Path | None = None, ) -> tuple[Path, Path]: """ Takes a bam file containing long read sequencing data aligned @@ -325,14 +713,21 @@ def extract( output_directory: optional str or Path pointing to an output directory. If left as None, outputs will be stored in a new folder within the input directory. - regions: TODO + regions: optional region selector passed through to modkit via an include-bed + file. This may be a BED file path, a single region file, or a list of + region files/paths that define the loci to process. When paired with + ``window_size``, the provided regions are expanded around their centers + before being passed to modkit. motifs: a list of strings specifying which base modifications to look for. The basemods are each specified as {sequence_motif},{position_of_modification}. For example, a methylated adenine is specified as 'A,0' and CpG methylation is specified as 'CG,0'. - thresh: float point number specifying the base modification probability threshold - used to delineate modificaton calls as True or False. When set to None, modkit - will select its own threshold automatically based on the data. + thresh: base modification threshold specification. Accepted forms are: + * None: keep raw probabilities in output vectors + * float/int: one threshold for all motifs + * dict: motif-specific thresholds keyed by exact motif string + (e.g. ``{"A,0": 0.7, "CG,0": 0.8}``), canonical motif key, or + ``default``/``*`` fallback. window_size: an integer specifying a window around the center of each bed_file region. If set to None, the bed_file is used unmodified. If set to a non-zero positive integer, the bed_file regions are replaced by new regions with that @@ -344,7 +739,13 @@ def extract( cleanup: a boolean specifying whether to clean up to keep intermediate outputs. The final processed files are not human-readable, whereas the intermediate outputs are. However, intermediate outputs can also be quite large. + overwrite: when True (default), existing outputs for `output_name` are + replaced. When False, existing completed outputs are reused and parsing + is skipped. If partial/conflicting outputs exist, raises FileExistsError. override_checks: convert errors from input checking into warnings if True + modkit_executable: optional executable name or path to a specific modkit + binary. If None, dimelo resolves modkit from PATH (or + DIMELO_MODKIT_EXECUTABLE when set). Returns: Path object pointing to the compressed and indexed output .h5 file, ready for @@ -352,21 +753,54 @@ def extract( Path object pointing to 'regions.processed.bed', the `--include-bed` file used for `modkit extract` """ - """ - TODO: There are a lot of issues that are all related here: - dimelo/parse_bam.py:374: error: Incompatible types in assignment (expression has type "Path | None", variable has type "str | Path") [assignment] - dimelo/parse_bam.py:393: error: Argument "input_file" to "prep_outputs" has incompatible type "str | Path"; expected "Path" [arg-type] - dimelo/parse_bam.py:480: error: Argument "input_file" to "run_with_progress_bars" has incompatible type "str | Path"; expected "Path" [arg-type] - dimelo/parse_bam.py:481: error: Argument "ref_genome" to "run_with_progress_bars" has incompatible type "str | Path"; expected "Path" [arg-type] - - I'm not sure of the most elegant way to fix it. Come back and address. - """ - ## Verify and prepare inputs and outputs - input_file, ref_genome, output_directory = utils.sanitize_path_args( input_file, ref_genome, output_directory ) + input_file = Path(input_file) + ref_genome = Path(ref_genome) + output_directory = None if output_directory is None else Path(output_directory) + + output_path, (output_reads_path,) = prep_output_directory( + output_directory=output_directory, + output_name=output_name, + input_file=input_file.parent, + output_file_names=["reads.combined_basemods.h5"], + overwrite=False, + ) + processed_regions_candidate = output_path / "regions.processed.bed" + processed_regions_existing = ( + processed_regions_candidate if processed_regions_candidate.exists() else None + ) + + if not overwrite: + final_output_exists = output_reads_path.exists() + regions_ready = regions is None or processed_regions_existing is not None + if final_output_exists and regions_ready: + if not quiet: + print( + f"overwrite=False and existing outputs found. Reusing {output_reads_path}." + ) + return output_reads_path, processed_regions_existing + + existing_paths = [output_reads_path] + if processed_regions_existing is not None: + existing_paths.append(processed_regions_existing) + conflicting_paths = [path for path in existing_paths if path.exists()] + if len(conflicting_paths) > 0: + conflict_names = ", ".join(path.name for path in conflicting_paths) + raise FileExistsError( + "overwrite=False but output directory contains existing parse artifacts " + f"for '{output_name}': {conflict_names}. " + "Set overwrite=True to regenerate, or remove stale files." + ) + else: + _unlink_existing(output_reads_path, processed_regions_candidate) + + capabilities = run_modkit._ensure_modkit_available( + quiet=quiet, + executable=modkit_executable, + ) try: verify_inputs(input_file, motifs, ref_genome) @@ -379,14 +813,6 @@ def extract( f'{e}\nIf you are confident that your inputs are ok, pass "override_checks=True" to convert to warning and proceed with processing.' ) from e - # TODO: Add intermediate mod-specific .txt files? - output_path, (output_reads_path,) = prep_output_directory( - output_directory=output_directory, - output_name=output_name, - input_file=input_file, - output_file_names=["reads.combined_basemods.h5"], - ) - ## Build up the command lists shared across motifs to be sent to modkit region_command_list, processed_regions_path = create_region_command_list( @@ -395,44 +821,27 @@ def extract( window_size, ) - cores_avail = multiprocessing.cpu_count() - if cores is None: - if not quiet: - print( - f"No specified number of cores requested. {cores_avail} available on machine, allocating all." - ) - cores_command_list = ["--threads", str(cores_avail)] - elif cores > cores_avail: - if not quiet: - print( - f"Warning: {cores} cores request, {cores_avail} available. Allocating {cores_avail}" - ) - cores_command_list = ["--threads", str(cores_avail)] - else: - if not quiet: - print(f"Allocating requested {cores} cores.") - cores_command_list = ["--threads", str(cores)] + cores_command_list = _threads_command_list(cores=cores, quiet=quiet) - mod_thresh_command_list: list[str] = [] - if thresh is None: + motif_thresholds = _resolve_motif_thresholds( + motifs=motifs, + thresh=thresh, + quiet=quiet, + ) + if motif_thresholds is None: if not quiet: print( - "No valid base modification threshold provided. Raw probs will be saved." + "No valid base modification threshold provided. Raw probabilities will be saved." ) - adjusted_threshold = None else: - adjusted_threshold = utils.adjust_threshold(thresh, quiet=quiet) - if adjusted_threshold < 0.5 and not quiet: + if any(value < 0.5 for value in motif_thresholds.values()) and not quiet: print( f"WARNING: thresh {thresh} is very low and may lead to unexpected behavior. Typical thresholds are at least 0.5 or 128." ) - for motif in motifs: - parsed_motif = utils.ParsedMotif(motif) - for mod_code in parsed_motif.mod_codes: - mod_thresh_command_list = mod_thresh_command_list + [ - "--mod-thresholds", - f"{mod_code}:{adjusted_threshold}", - ] + if not quiet: + print( + "Threshold provided. The extract text will stay probability-valued and read_by_base_txt_to_hdf5 will binarize at write time." + ) if log: if not quiet: @@ -441,8 +850,16 @@ def extract( else: log_command_list = [] - ref_genome_command_list = ["--ref", ref_genome] - filter_command_list = ["--filter-threshold", "0"] + ref_genome_command_list = _build_extract_reference_command_list( + ref_genome=ref_genome, + capabilities=capabilities, + ) + filter_command_list = ( + [] + if _extract_requires_subcommands(capabilities) + else ["--filter-threshold", "0"] + ) + implicit_tag_command_list = _build_implicit_tag_command_list(capabilities) # Run modkit once for each motif, because the output .txt can be ambiguous otherwise # There is no column currently to specify the motif (e.g. CG,0 vs GCH,1), only canonical @@ -452,30 +869,29 @@ def extract( for motif in motifs: # Here we prepare the motif-specific commands and delete any old .txt file because # modkit will crash otherwise - motif_command_list = [] parsed_motif = utils.ParsedMotif(motif) - motif_command_list.append("--motif") - motif_command_list.append(parsed_motif.motif_seq) - motif_command_list.append(str(parsed_motif.modified_pos)) + motif_command_list = _build_extract_targeting_command_list(parsed_motif) output_txt = Path(output_path) / (f"reads.{motif}.txt") - if os.path.exists(output_txt): - os.remove(output_txt) + output_txt.unlink(missing_ok=True) extract_command_list = ( - ["modkit", "extract", input_file, output_txt] + _build_extract_command_prefix( + input_file=input_file, + output_txt=output_txt, + capabilities=capabilities, + ) + region_command_list + motif_command_list + cores_command_list + log_command_list + ref_genome_command_list + filter_command_list + + implicit_tag_command_list ) - # TODO: Do we need to store and use the output from this method? Previously was being printed immediately afterward. - # This is something the user might want to see - it's the end-of-process message for modkit, says e.g. how many reads were processed and stuff - _ = run_modkit.run_with_progress_bars( + run_modkit.run_with_progress_bars( command_list=extract_command_list, input_file=input_file, ref_genome=ref_genome, @@ -491,19 +907,18 @@ def extract( expect_done=False, quiet=quiet, ) - # print(done_string) # Create the compressed and indexed output read_by_base_txt_to_hdf5( output_txt, output_reads_path, motif, - adjusted_threshold, + None if motif_thresholds is None else motif_thresholds[motif], quiet=quiet, ) # Delete intermediate file if cleanup: - os.remove(output_txt) + output_txt.unlink() return output_reads_path, processed_regions_path @@ -566,7 +981,11 @@ def check_bam_format( input_bam = pysam.AlignmentFile(bam_file) try: - for counter, read in enumerate(input_bam.fetch()): + reads_checked = 0 + for counter, read in enumerate( + itertools.islice(input_bam.fetch(), NUM_READS_TO_CHECK) + ): + reads_checked = counter + 1 read_dict = read.to_dict() for tag_string in read_dict["tags"]: tag = tag_string.split(",")[0].split(":")[0] @@ -611,16 +1030,15 @@ def check_bam_format( # raise ValueError( # f'Base modification name unexpected: {tag_value[2]} to modify {tag_value[0]}, should be in set {valid_mod_codes}. \n\nIf you know what your mod names correspond to in terms of the latest .bam standard, consider using "modkit adjust-mods {str(bam_file)} new_file.bam --convert 5mC_name m --convert N6mA_name a --convert other_basemod_name correct_label" and then trying with the new file. Note: currently supported mod names are {utils.BASEMOD_NAMES_DICT}' # ) - if all(basemods_found_dict.values()): - return - if counter >= NUM_READS_TO_CHECK: - missing_bases = [] - for base, found in basemods_found_dict.items(): - if not found: - missing_bases.append(base) + if reads_checked == NUM_READS_TO_CHECK: + missing_bases = [] + for base, found in basemods_found_dict.items(): + if not found: + missing_bases.append(base) + if missing_bases: print( f""" -WARNING: no modified appropriately-coded values found for {missing_bases} in the first {counter} reads. +WARNING: no modified appropriately-coded values found for {missing_bases} in the first {reads_checked} reads. Do you expect this file to contain these modifications? parse_bam is looking for {motifs} but for {missing_bases} found only found {[f"{base}+{mod_codes}" for base, mod_codes in mod_codes_found_dict.items()]}. Consider passing only the motifs and mod codes (e.g. m,h,a) that you expect to be present in your file. @@ -628,7 +1046,7 @@ def check_bam_format( See https://github.com/nanoporetech/modkit/blob/master/book/src/advanced_usage.md """ ) - return + return except ValueError as e: if "fetch called on bamfile without index" in str(e): raise ValueError( @@ -689,30 +1107,41 @@ def get_alignment_quality( def create_region_command_list( - output_path, - regions, - window_size, -): + output_path: Path, + regions: str | Path | list[str | Path] | None, + window_size: int | None, +) -> tuple[list[str], Path | None]: """ - Creates commands to pass to modkit for specifying genomic regions. - - TODO: Split into two function? Convert to bed, then construct commands + Create modkit `--include-bed` arguments and the processed BED path, if regions are provided. """ - if regions is not None: - bed_filepath_processed = output_path / "regions.processed.bed" - regions_dict = utils.regions_dict_from_input( - regions, - window_size, - ) - utils.bed_from_regions_dict(regions_dict, bed_filepath_processed) - region_specifier = ["--include-bed", str(bed_filepath_processed)] + bed_filepath_processed = _regions_to_processed_bed( + output_path=output_path, + regions=regions, + window_size=window_size, + ) + if bed_filepath_processed is None: + return [], None + return ["--include-bed", str(bed_filepath_processed)], bed_filepath_processed - else: - bed_filepath_processed = None - region_specifier = [] - return region_specifier, bed_filepath_processed +def _regions_to_processed_bed( + output_path: Path, + regions: str | Path | list[str | Path] | None, + window_size: int | None, +) -> Path | None: + """ + Build a normalized BED file from region inputs for downstream modkit calls. + """ + if regions is None: + return None + bed_filepath_processed = output_path / "regions.processed.bed" + regions_dict = utils.regions_dict_from_input( + regions, + window_size, + ) + utils.bed_from_regions_dict(regions_dict, bed_filepath_processed) + return bed_filepath_processed def read_by_base_txt_to_hdf5( @@ -725,26 +1154,27 @@ def read_by_base_txt_to_hdf5( chunk_size: int = 1000, ) -> None: """ - Takes in a txt file generated by modkit extract and appends - all the data from a specified motif into an hdf5 file. If a thresh is specified, it - also binarizes the mod calls. - - If the h5 file does not exist it will be created and datasets will be added for read_name, - chromosome, read_start, read_end, strand, motif, mod_vector, and val_vector. - - All the datasets (exception threshold) are parallel arrays of length num_reads - - Each read's position data is defined in genomic reference coordinates on the positive strand - (i.e. the read_start is the leftmost aligned position, read_end is the rightmost, vectors - are left to right along genomic coordinates) - - TODO: Make a nice key:value map of the h5 file structure, make sure start and end are documented - as reconstructions NOT original cigarstring alignment info. mention pysam + Takes in a txt file generated by modkit extract and appends all the data from a + specified motif into an hdf5 file. If a thresh is specified, the mod calls are + binarized at write time and the threshold is stored alongside the vectors. + + If the h5 file does not exist it will be created with top-level datasets named + ``read_name``, ``chromosome``, ``read_start``, ``read_end``, ``strand``, ``motif``, + ``mod_vector``, and ``val_vector``. Each dataset stores one value per read, so the arrays + are parallel and have length ``num_reads``. The optional ``threshold`` dataset stores the + scalar threshold used for binarization and is not part of the per-read arrays. + + Each read's position data is stored in genomic reference coordinates on the positive strand + convention. ``read_start`` and ``read_end`` are reconstructed from the aligned modkit extract + coordinates, not copied from the original BAM CIGAR string or any raw alignment tag. In this + representation, ``read_start`` is the leftmost aligned reference position for the read and + ``read_end`` is the rightmost aligned reference position observed while iterating through the + read. The modification vectors are ordered left to right along genomic coordinates. Args: input_txt: a string or Path pointing to a modkit extracted base-by-base modifications - file. This file is assumed to have been created by modkit v0.2.4, other versions may - have a different format and may not function normally. + file. This parser supports both legacy and current extract table schemas used by + modkit 0.2.4 and 0.6.x. output_h5: a string or Path pointing to a valid place to save an .h5 file. If this file already exists, it will not be cleared and will simply be appended to. motif: a string specifying a single base modification. Basemods are specified as @@ -752,7 +1182,8 @@ def read_by_base_txt_to_hdf5( a methylated adenine is specified as 'A,0' or 'A,0,a' and CpG methylation is specified as 'CG,0' or 'CG,0,m'. thresh: a floating point threshold for base modification calling, between zero and one. - If specified as None, raw probabilities will be saved in the .h5 output. + If specified as None, raw probabilities will be saved in the .h5 output. If set, + the stored mod_vector values are binary 0/1 values at motif-valid coordinates. quiet: if True, this suppresses outputs compress_level: gzip compression level for datasets, specifically for vectors for now chunk_size: size of write chunks in reads @@ -760,30 +1191,38 @@ def read_by_base_txt_to_hdf5( Returns: None - """ - """ - TODO: There are some issues that are all related here: - dimelo/parse_bam.py:718: error: Incompatible types in assignment (expression has type "Path | None", variable has type "str | Path") [assignment] - dimelo/parse_bam.py:725: error: Item "str" of "str | Path" has no attribute "open" [union-attr] - dimelo/parse_bam.py:890: error: Item "str" of "str | Path" has no attribute "name" [union-attr] - - I'm not sure of the most elegant way to fix it. Come back and address. """ input_txt, output_h5 = utils.sanitize_path_args(input_txt, output_h5) + input_txt = Path(input_txt) + output_h5 = Path(output_h5) parsed_motif = utils.ParsedMotif(motif) read_name = "" num_reads = 0 - # TODO: I think the function calls can be consolidated; lots of repetition - # TODO: Consider opening both files at once - with input_txt.open() as txt: + with input_txt.open(newline="") as txt: + reader = csv.reader(txt, delimiter="\t") + header_fields = next(reader, None) + if header_fields is None: + raise ValueError(f"modkit extract output is empty: {input_txt}") + + if "read_id" in header_fields: + first_pass_read_name_idx = header_fields.index("read_id") + elif "read_name" in header_fields: + first_pass_read_name_idx = header_fields.index("read_name") + else: + first_pass_read_name_idx = 0 + # Check file length - for line_index, line in enumerate(txt): - fields = line.split("\t") - if line_index > 0 and read_name != fields[0]: - read_name = fields[0] + line_index = 0 + for fields in reader: + line_index += 1 + read_id_value = fields[first_pass_read_name_idx] + if read_name != read_id_value: + read_name = read_id_value num_reads += 1 + if line_index == 0: + raise ValueError(f"modkit extract output has no data rows: {input_txt}") num_lines = line_index txt.seek(0) @@ -794,23 +1233,65 @@ def read_by_base_txt_to_hdf5( # h5py does not appear to otherwise support vlen binary dt_vlen = h5py.vlen_dtype(np.dtype("uint8")) - ## Format threshold value and create dataset to store whether this data is thresholded (binary) or raw (float16) - # TODO: should this method thresholding without binarization - # None becomes NaN - threshold_to_store = np.nan if thresh is None else thresh - if "threshold" in h5: - threshold_from_existing = h5["threshold"][()] - if threshold_from_existing != threshold_to_store and not ( - np.isnan(threshold_from_existing) and np.isnan(threshold_to_store) - ): - raise ValueError( - "existing threshold in output_h5 does not match provided threshold for read_by_base_txt_to_hdf5." - ) + # Threshold metadata: + # - `threshold` remains for backwards compatibility and for fast binarized/raw checks. + # - `threshold_by_motif_json` stores per-motif thresholds so different motifs can use + # different binarization cutoffs in one output file. + threshold_for_motif = None if thresh is None else float(thresh) + threshold_by_motif: dict[str, float | None] = {} + if "threshold_by_motif_json" in h5: + raw_threshold_map = h5["threshold_by_motif_json"][()] + if isinstance(raw_threshold_map, bytes): + raw_threshold_map = raw_threshold_map.decode("utf-8") + threshold_by_motif = json.loads(str(raw_threshold_map)) + elif "threshold" in h5 and "motif" in h5 and h5["motif"].shape[0] > 0: + legacy_threshold = h5["threshold"][()] + legacy_threshold_value = ( + None if np.isnan(legacy_threshold) else float(legacy_threshold) + ) + existing_motifs = np.unique(np.array(h5["motif"], dtype=str)) + threshold_by_motif = { + existing_motif: legacy_threshold_value + for existing_motif in existing_motifs + } + + threshold_by_motif[motif] = threshold_for_motif + + has_raw = any(value is None for value in threshold_by_motif.values()) + has_binary = any(value is not None for value in threshold_by_motif.values()) + if has_raw and has_binary: + raise ValueError( + "Cannot mix raw-probability and thresholded motifs in one output_h5 file." + ) + + if has_binary: + unique_thresholds = sorted( + { + float(value) + for value in threshold_by_motif.values() + if value is not None + } + ) + if len(unique_thresholds) == 1: + threshold_to_store = unique_thresholds[0] + else: + # Keep legacy `threshold` dataset non-NaN to indicate binarized vectors. + threshold_to_store = 0.0 else: - h5.create_dataset("threshold", data=threshold_to_store) + threshold_to_store = np.nan + + if "threshold" in h5: + del h5["threshold"] + h5.create_dataset("threshold", data=threshold_to_store) + + if "threshold_by_motif_json" in h5: + del h5["threshold_by_motif_json"] + h5.create_dataset( + "threshold_by_motif_json", + data=json.dumps(threshold_by_motif), + ) ## Create read metadata datasets - # TODO: loop through dict instead? if "read_name" in h5: old_size = h5["read_name"].shape[0] h5["read_name"].resize((old_size + num_reads,)) @@ -824,135 +1305,173 @@ def read_by_base_txt_to_hdf5( compression="gzip", compression_opts=9, ) - if "chromosome" in h5: - if old_size != h5["chromosome"].shape[0]: - print("size mismatch: read_name:chromosome") - else: - h5["chromosome"].resize((old_size + num_reads,)) - else: - h5.create_dataset( - "chromosome", - (num_reads,), - maxshape=(None,), - dtype=dt_str, - compression="gzip", - compression_opts=9, - ) - if "read_start" in h5: - if old_size != h5["read_start"].shape[0]: - print("size mismatch", "read_name", "read_start") - else: - h5["read_start"].resize((old_size + num_reads,)) - else: - h5.create_dataset( - "read_start", - (num_reads,), - maxshape=(None,), - dtype="i", - compression="gzip", - compression_opts=9, - ) - if "read_end" in h5: - if old_size != h5["read_end"].shape[0]: - print("size mismatch", "read_name", "read_end") - else: - h5["read_end"].resize((old_size + num_reads,)) - else: - h5.create_dataset( - "read_end", - (num_reads,), - maxshape=(None,), - dtype="i", - compression="gzip", - compression_opts=9, - ) - if "strand" in h5: - if old_size != h5["strand"].shape[0]: - print("size mismatch", "read_name", "strand") - else: - h5["strand"].resize((old_size + num_reads,)) - else: + + def ensure_dataset_capacity( + *, + name: str, + dtype, + mismatch_message: str, + compression: str | None = None, + compression_opts: int | None = None, + ) -> None: + if name in h5: + if old_size != h5[name].shape[0]: + print(mismatch_message) + else: + h5[name].resize((old_size + num_reads,)) + return + + create_kwargs: dict[str, object] = { + "maxshape": (None,), + "dtype": dtype, + } + if compression is not None: + create_kwargs["compression"] = compression + if compression_opts is not None: + create_kwargs["compression_opts"] = compression_opts + h5.create_dataset( - "strand", + name, (num_reads,), - maxshape=(None,), - dtype=dt_str, - compression="gzip", - compression_opts=9, + **create_kwargs, ) - if "motif" in h5: - if old_size != h5["motif"].shape[0]: - print("size mismatch", "read_name", "motif") - else: - h5["motif"].resize((old_size + num_reads,)) - else: - h5.create_dataset( - "motif", - (num_reads,), - maxshape=(None,), - dtype=dt_str, + + for dataset_name, dataset_dtype, mismatch_message in [ + ("chromosome", dt_str, "size mismatch: read_name:chromosome"), + ("read_start", "i", "size mismatch read_name:read_start"), + ("read_end", "i", "size mismatch read_name:read_end"), + ("strand", dt_str, "size mismatch read_name:strand"), + ("motif", dt_str, "size mismatch read_name:motif"), + ]: + ensure_dataset_capacity( + name=dataset_name, + dtype=dataset_dtype, + mismatch_message=mismatch_message, compression="gzip", compression_opts=9, ) ## Create the vector datasets. These will contain raw bytes formatted into a uint8 array - # TODO: loop through dict instead - if "mod_vector" in h5: - if old_size != h5["mod_vector"].shape[0]: - print("size mismatch read_name:mod_vector") - else: - h5["mod_vector"].resize((old_size + num_reads,)) - else: - h5.create_dataset( - "mod_vector", - (num_reads,), - maxshape=(None,), + for dataset_name in ["mod_vector", "val_vector"]: + ensure_dataset_capacity( + name=dataset_name, dtype=dt_vlen, - # compression='gzip', # we are handling compression ourselves because hdf5 is bad at it - # compression_opts=9, - ) - if "val_vector" in h5: - if old_size != h5["val_vector"].shape[0]: - print("size mismatch read_name:val_vector") - else: - h5["val_vector"].resize((old_size + num_reads,)) - else: - h5.create_dataset( - "val_vector", - (num_reads,), - maxshape=(None,), - dtype=dt_vlen, - # compression='gzip', # we are handling compression ourselves because hdf5 is bad at it - # compression_opts=9, + mismatch_message=f"size mismatch read_name:{dataset_name}", ) ## Add data to datasets from txt file # Initialize loop vars - these will go into datasets - # TODO: initialize read name to actual first read so we can get rid of the logic in the loop - read_name = "" + read_name: str | None = None read_chrom = "" - read_len = 0 ref_strand = "" read_start = 0 read_end = 0 - valid_coordinates_list: list[int] = [] + valid_genomic_positions_list: list[int] = [] mod_values_list: list[float] = [] # Count reads for batched write read_counter = 0 # Keys (strings): dataset names, values: lists of dataset values by read; string or ints or arrays # Contents reset at the end of each chunk, after writing to h5 - chunk_datasets_contents: defaultdict[str, list[str | int | np.ndarray]] = ( - defaultdict(list) + chunk_rows: defaultdict[str, list[str | int | np.ndarray]] = defaultdict( + list ) - # TODO: replace in loop with read_counter%chunk_size as appropriate - reads_in_chunk = 0 + chunk_row_count = 0 + + def flush_pending_chunk_to_h5() -> None: + nonlocal chunk_row_count, chunk_rows + if chunk_row_count <= 0: + return + start_index = old_size + read_counter - chunk_row_count + end_index = old_size + read_counter + for dataset, entry in chunk_rows.items(): + h5[dataset][start_index:end_index] = entry + chunk_rows = defaultdict(list) + chunk_row_count = 0 + + def flush_current_read() -> None: + nonlocal read_counter, chunk_row_count, chunk_rows + + if read_name is None: + return + + read_len_along_ref = max(read_end - read_start, 1) + + # Populate mod vector array appropriately based on thresh settings. + # Option 1: when thresh is provided, write binary 0/1 values; otherwise preserve raw probs. + mod_vector = np.zeros(read_len_along_ref, dtype=np.uint8) + valid_vector = np.zeros(read_len_along_ref, dtype=np.uint8) + + if len(valid_genomic_positions_list) > 0: + valid_coordinates = ( + np.asarray(valid_genomic_positions_list, dtype=int) - read_start + ) + mod_values = np.asarray(mod_values_list, dtype=float) + in_bounds = (valid_coordinates >= 0) & ( + valid_coordinates < read_len_along_ref + ) + valid_coordinates = valid_coordinates[in_bounds] + mod_values = mod_values[in_bounds] + + if thresh is None: + # We subtract 0.25 because in modkit they add 0.5, but our elements are zero when the + # base motif isn't present, so to get things to round to the right integers to match the + # original .bam file, subtracting 0.25 is good. Anything from 0.001 to 0.4999 would work I think + mod_vector[valid_coordinates] = np.rint( + mod_values * 256 - 0.25 + ).astype(np.uint8) + else: + mod_vector[valid_coordinates] = mod_values.astype(np.uint8) + valid_vector[valid_coordinates] = 1 + + mod_vector_compressed = _compress_uint8_vector( + mod_vector, compress_level=compress_level + ) + valid_vector_compressed = _compress_uint8_vector( + valid_vector, compress_level=compress_level + ) + + chunk_rows["read_name"].append(read_name) + chunk_rows["chromosome"].append(read_chrom) + chunk_rows["read_start"].append(read_start) + chunk_rows["read_end"].append(read_end) + chunk_rows["strand"].append(ref_strand) + chunk_rows["motif"].append(motif) + chunk_rows["mod_vector"].append(mod_vector_compressed) + chunk_rows["val_vector"].append(valid_vector_compressed) + + read_counter += 1 + chunk_row_count += 1 + if chunk_row_count >= chunk_size: + flush_pending_chunk_to_h5() # Setting up progress bars if not in quiet mode # Skip header - iterator = enumerate(txt) - next(iterator) - if not quiet: + reader = csv.reader(txt, delimiter="\t") + header = next(reader) + + def _column_index(*candidates: str, fallback: int) -> int: + for candidate in candidates: + if candidate in header: + return header.index(candidate) + return fallback + + read_name_idx = _column_index("read_id", "read_name", fallback=0) + pos_in_read_idx = _column_index( + "forward_read_position", "read_position", fallback=1 + ) + pos_in_genome_idx = _column_index( + "ref_position", "reference_position", fallback=2 + ) + read_chrom_idx = _column_index("chrom", "chromosome", fallback=3) + ref_strand_idx = _column_index("ref_strand", fallback=5) + line_read_len_idx = _column_index("read_length", fallback=9) + prob_idx = _column_index("mod_qual", fallback=10) + mod_code_idx = _column_index("mod_code", fallback=11) + canonical_base_idx = _column_index("canonical_base", fallback=15) + + iterator = reader + if not quiet and _should_render_live_progress(): iterator = tqdm( iterator, total=num_lines, @@ -961,107 +1480,41 @@ def read_by_base_txt_to_hdf5( ) # Loop through txt file - for line_index, line in iterator: - # TODO: use csv module - fields = line.split("\t") - pos_in_genome = int(fields[2]) - canonical_base = fields[15] - prob = float(fields[10]) - mod_code = fields[11] - - if read_name != fields[0]: - # Record the previous read details unless this is the first line - if line_index > 1: - # TODO: Replace this with read_end-read_start; this will pad vectors and require - # regenerating test reference data - if len(valid_coordinates_list) > 0: - read_len_along_ref = max(valid_coordinates_list) + 1 - else: - read_len_along_ref = read_len - - # Populate mod vector array appropriately based on thresh settings - mod_vector = np.zeros(read_len_along_ref, dtype=np.uint8) - if thresh is None: - # We subtract 0.25 because in modkit they add 0.5, but our elements are zero when the - # base motif isn't present, so to get things to round to the right integers to match the - # original .bam file, subtracting 0.25 is good. Anything from 0.001 to 0.4999 would work I think - mod_vector[valid_coordinates_list] = np.rint( - np.array(mod_values_list) * 256 - 0.25 - ).astype(np.uint8) - else: - mod_vector[valid_coordinates_list] = np.array( - mod_values_list - ).astype(np.uint8) - # TODO: consolidate compression into a function shared across - mod_vector_compressed = np.frombuffer( - gzip.compress( - mod_vector.tobytes(), compresslevel=compress_level - ), - dtype=np.uint8, - ) - - # Populate valid vector array - valid_vector = np.zeros(read_len_along_ref, dtype=np.uint8) - valid_vector[valid_coordinates_list] = 1 - valid_vector_compressed = np.frombuffer( - gzip.compress( - valid_vector.tobytes(), compresslevel=compress_level - ), - dtype=np.uint8, - ) - - chunk_datasets_contents["read_name"].append(read_name) - chunk_datasets_contents["chromosome"].append(read_chrom) - chunk_datasets_contents["read_start"].append(read_start) - chunk_datasets_contents["read_end"].append(read_end) - chunk_datasets_contents["strand"].append(ref_strand) - chunk_datasets_contents["motif"].append(motif) - chunk_datasets_contents["mod_vector"].append( - mod_vector_compressed - ) - chunk_datasets_contents["val_vector"].append( - valid_vector_compressed - ) + for fields in iterator: + pos_in_genome = int(fields[pos_in_genome_idx]) + canonical_base = fields[canonical_base_idx] + prob = float(fields[prob_idx]) + mod_code = fields[mod_code_idx] + pos_in_read = int(fields[pos_in_read_idx]) + line_read_len = int(fields[line_read_len_idx]) + line_ref_strand = fields[ref_strand_idx] + pos_in_read_ref = _reference_oriented_read_offset( + pos_in_read=pos_in_read, + read_length=line_read_len, + ref_strand=line_ref_strand, + ) + start_candidate = pos_in_genome - pos_in_read_ref + end_candidate = start_candidate + line_read_len - # Write chunk if enough reads have built up - reads_in_chunk += 1 - if reads_in_chunk >= chunk_size: - for dataset, entry in chunk_datasets_contents.items(): - start_index = ( - old_size + (read_counter // chunk_size) * chunk_size - ) - end_index = old_size + read_counter + 1 - h5[dataset][start_index:end_index] = entry - chunk_datasets_contents = defaultdict(list) - reads_in_chunk = 0 - read_counter += 1 + if read_name != fields[read_name_idx]: + flush_current_read() ## Set up for next read # Metadata - read_name = fields[0] - read_chrom = fields[3] - read_len = int(fields[9]) - ref_strand = fields[5] - # TODO: verify that read position is in the right (ref) coordinate system - if ref_strand == "+": - pos_in_read_ref = int(fields[1]) - elif ref_strand == "-": - pos_in_read_ref = read_len - int(fields[1]) - 1 - # Calculate read start (leftmost position on ref genome) - # TODO: logic can be replaced when we switch to true read start/end from modkit - read_start = pos_in_genome - pos_in_read_ref + read_name = fields[read_name_idx] + read_chrom = fields[read_chrom_idx] + ref_strand = line_ref_strand + read_start = start_candidate + read_end = end_candidate # Instantiate lists mod_values_list = [] - valid_coordinates_list = [] - - # Adjust the read_end (rightmost position on ref genome) each time there's a new mod - # This will lead to the most accurate end positions for gapped reads - # TODO: logic can be replaced when we switch to true read start/end from modkit - if ref_strand == "+": - pos_in_read_ref = int(fields[1]) - elif ref_strand == "-": - pos_in_read_ref = read_len - int(fields[1]) - 1 - read_end = pos_in_genome + (read_len - pos_in_read_ref) + valid_genomic_positions_list = [] + + # keep read extents in reference coordinates by using inferred starts/ends from + # each line, which is robust even when rows are not ordered by genomic position + read_start = min(read_start, start_candidate) + read_end = max(read_end, end_candidate) + # Regardless of whether its a new read or not, # add modification to vector if motif type is correct # for the motif in question @@ -1069,7 +1522,7 @@ def read_by_base_txt_to_hdf5( canonical_base == parsed_motif.modified_base and mod_code in parsed_motif.mod_codes ): - valid_coordinates_list.append(pos_in_genome - read_start) + valid_genomic_positions_list.append(pos_in_genome) if thresh is None: mod_values_list.append(prob) elif prob >= thresh: @@ -1077,56 +1530,8 @@ def read_by_base_txt_to_hdf5( else: mod_values_list.append(0) - # Save the last read - # TODO: try to consolidate - if len(read_name) > 0: - # Build the vectors - if len(valid_coordinates_list) > 0: - read_len_along_ref = max(valid_coordinates_list) + 1 - else: - read_len_along_ref = read_len - - # Populate mod vector array appropriately based on thresh settings - mod_vector = np.zeros(read_len_along_ref, dtype=np.uint8) - if thresh is None: - # We subtract 0.25 because in modkit they add 0.5, but our elements are zero when the - # base motif isn't present, so to get things to round to the right integers to match the - # original .bam file, subtracting 0.25 is good. Anything from 0.001 to 0.4999 would work I think - mod_vector[valid_coordinates_list] = np.rint( - np.array(mod_values_list) * 256 - 0.25 - ).astype(np.uint8) - else: - mod_vector[valid_coordinates_list] = np.array( - mod_values_list - ).astype(np.uint8) - # TODO: consolidate compression into a function shared across - mod_vector_compressed = np.frombuffer( - gzip.compress(mod_vector.tobytes(), compresslevel=compress_level), - dtype=np.uint8, - ) - - # Populate valid vector array - valid_vector = np.zeros(read_len_along_ref, dtype=np.uint8) - valid_vector[valid_coordinates_list] = 1 - valid_vector_compressed = np.frombuffer( - gzip.compress(valid_vector.tobytes(), compresslevel=compress_level), - dtype=np.uint8, - ) - - chunk_datasets_contents["read_name"].append(read_name) - chunk_datasets_contents["chromosome"].append(read_chrom) - chunk_datasets_contents["read_start"].append(read_start) - chunk_datasets_contents["read_end"].append(read_end) - chunk_datasets_contents["strand"].append(ref_strand) - chunk_datasets_contents["motif"].append(motif) - chunk_datasets_contents["mod_vector"].append(mod_vector_compressed) - chunk_datasets_contents["val_vector"].append(valid_vector_compressed) - - for dataset, entry in chunk_datasets_contents.items(): - start_index = old_size + (read_counter // chunk_size) * chunk_size - end_index = old_size + read_counter + 1 - h5[dataset][start_index:end_index] = entry - read_counter += 1 + flush_current_read() + flush_pending_chunk_to_h5() return @@ -1135,37 +1540,38 @@ def prep_output_directory( output_name: str, input_file: Path, output_file_names: list[str], + overwrite: bool = True, ) -> tuple[Path, list[Path]]: """ As a side effect, if files exist that match the requested outputs, they are deleted. - TODO: Is it kind of silly that this takes in input_file? Maybe should take in some generic default parameter, or this default should be set outside this method? - Args: output_directory: Path pointing to an output directory. If left as None, outputs will be stored in a new folder within the input directory. output_name: a string that will be used to create an output folder containing the intermediate and final outputs, along with any logs. - input_file: Path to input file; used to define default output directory + input_file: default output directory when output_directory is None output_file_names: list of names of desired output files + overwrite: when True, existing requested output files are deleted. When + False, existing files are preserved. Returns: * Path to top-level output directory * List of Paths to requested output files """ if output_directory is None: - output_directory = input_file.parent + output_directory = input_file print(f"No output directory provided, using input directory {output_directory}") output_path = output_directory / output_name output_files = [output_path / file_name for file_name in output_file_names] - # Ensure output path exists, and that any of the specified output files do not already exist (necessary for some outputs) - # Delete the files that do already exist + # Ensure output path exists. output_path.mkdir(parents=True, exist_ok=True) - for output_file in output_files: - output_file.unlink(missing_ok=True) + if overwrite: + for output_file in output_files: + output_file.unlink(missing_ok=True) return output_path, output_files diff --git a/dimelo/plot_depth_histogram.py b/dimelo/plot_depth_histogram.py index 95b1f09..93e8909 100644 --- a/dimelo/plot_depth_histogram.py +++ b/dimelo/plot_depth_histogram.py @@ -61,6 +61,7 @@ def plot_depth_histogram( one_depth_per_region=one_depth_per_region, quiet=quiet, cores=cores, + split_large_regions=split_large_regions, ) axes = make_depth_histogram_plot( @@ -96,8 +97,9 @@ def by_modification( def by_regions( mod_file_name: str | Path, - regions_list: list[str | Path | list[str | Path]], - motif: str, + regions_list: list[str | Path | list[str | Path]] | None = None, + motif: str | None = None, + regions: list[str | Path | list[str | Path]] | None = None, sample_names: list[str] | None = None, **kwargs, ) -> Axes: @@ -108,14 +110,28 @@ def by_regions( See plot_depth_histogram for details. """ - if sample_names is None: - sample_names = regions_list + if regions is not None: + if regions_list is not None and regions_list != regions: + raise ValueError( + "Pass either regions_list or regions to by_regions, not both with different values." + ) + regions_list = regions + if regions_list is None: + raise ValueError("by_regions requires regions_list (or alias regions).") + if motif is None: + raise ValueError("by_regions requires motif.") + + sample_names_for_plot = ( + sample_names + if sample_names is not None + else [str(region) for region in regions_list] + ) n_beds = len(regions_list) return plot_depth_histogram( mod_file_names=[mod_file_name] * n_beds, regions_list=regions_list, motifs=[motif] * n_beds, - sample_names=[f"{sample_name} depth" for sample_name in sample_names], + sample_names=[f"{sample_name} depth" for sample_name in sample_names_for_plot], **kwargs, ) @@ -134,14 +150,17 @@ def by_dataset( See plot_depth_histogram for details. """ - if sample_names is None: - sample_names = mod_file_names + sample_names_for_plot = ( + sample_names + if sample_names is not None + else [str(mod_file) for mod_file in mod_file_names] + ) n_mod_files = len(mod_file_names) return plot_depth_histogram( mod_file_names=mod_file_names, regions_list=[regions] * n_mod_files, motifs=[motif] * n_mod_files, - sample_names=[f"{sample_name} depth" for sample_name in sample_names], + sample_names=[f"{sample_name} depth" for sample_name in sample_names_for_plot], **kwargs, ) @@ -155,6 +174,7 @@ def get_depth_counts( one_depth_per_region: bool = False, quiet: bool = False, cores: int | None = 1, + split_large_regions: bool = False, ) -> list[np.ndarray]: """ Get the depth counts, ready for plotting. @@ -171,8 +191,6 @@ def get_depth_counts( the region of interest, False means we always grab both strands within the regions one_depth_per_region: if True, each region will only report a single depth value, averaging across all non-zero depths. If False depths will be reported separately for all nonzero count positions in each region for a more granular view of depth distribution. - regions_5to3prime: True means negative strand regions get flipped, False means no flipping - smooth_window: size of the moving window to use for smoothing. If set to None, no smoothing is performed quiet: disables progress bars cores: CPU cores across which to parallelize processing @@ -181,12 +199,12 @@ def get_depth_counts( """ if not utils.check_len_equal(mod_file_names, regions_list, motifs): raise ValueError("Unequal number of inputs") - # TODO: redefinition error; still need to figure out how to do this elegantly in a way mypy likes - # dimelo/plot_depth_histogram.py:53: error: Item "str" of "str | Path" has no attribute "suffix" [union-attr] - mod_file_names = [Path(fn) for fn in mod_file_names] + mod_file_paths = [Path(fn) for fn in mod_file_names] depth_vectors = [] - for mod_file, regions, motif in zip(mod_file_names, regions_list, motifs): + for mod_file, regions, motif in zip( + mod_file_paths, regions_list, motifs, strict=False + ): match mod_file.suffix: case ".gz": pileup_vectors_list = load_processed.regions_to_list( @@ -198,6 +216,7 @@ def get_depth_counts( single_strand=single_strand, quiet=quiet, cores=cores, + split_large_regions=split_large_regions, ) # places where read depth is zero are assumed to not have the motif present - this may not always be true, # but with the available information in a pileup file it's the best we can do diff --git a/dimelo/plot_depth_profile.py b/dimelo/plot_depth_profile.py index f13962f..6effc62 100644 --- a/dimelo/plot_depth_profile.py +++ b/dimelo/plot_depth_profile.py @@ -89,8 +89,9 @@ def by_modification( def by_regions( mod_file_name: str | Path, - regions_list: list[str | Path | list[str | Path]], - motif: str, + regions_list: list[str | Path | list[str | Path]] | None = None, + motif: str | None = None, + regions: list[str | Path | list[str | Path]] | None = None, sample_names: list[str] | None = None, **kwargs, ) -> Axes: @@ -101,14 +102,28 @@ def by_regions( See plot_depth_profile for details. """ - if sample_names is None: - sample_names = regions_list + if regions is not None: + if regions_list is not None and regions_list != regions: + raise ValueError( + "Pass either regions_list or regions to by_regions, not both with different values." + ) + regions_list = regions + if regions_list is None: + raise ValueError("by_regions requires regions_list (or alias regions).") + if motif is None: + raise ValueError("by_regions requires motif.") + + sample_names_for_plot = ( + sample_names + if sample_names is not None + else [str(region) for region in regions_list] + ) n_beds = len(regions_list) return plot_depth_profile( mod_file_names=[mod_file_name] * n_beds, regions_list=regions_list, motifs=[motif] * n_beds, - sample_names=[f"{sample_name} depth" for sample_name in sample_names], + sample_names=[f"{sample_name} depth" for sample_name in sample_names_for_plot], **kwargs, ) @@ -127,14 +142,17 @@ def by_dataset( See plot_depth_profile for details. """ - if sample_names is None: - sample_names = mod_file_names + sample_names_for_plot = ( + sample_names + if sample_names is not None + else [str(mod_file) for mod_file in mod_file_names] + ) n_mod_files = len(mod_file_names) return plot_depth_profile( mod_file_names=mod_file_names, regions_list=[regions] * n_mod_files, motifs=[motif] * n_mod_files, - sample_names=[f"{sample_name} depth" for sample_name in sample_names], + sample_names=[f"{sample_name} depth" for sample_name in sample_names_for_plot], **kwargs, ) @@ -173,12 +191,12 @@ def get_depth_profiles( """ if not utils.check_len_equal(mod_file_names, regions_list, motifs): raise ValueError("Unequal number of inputs") - # TODO: redefinition error; still need to figure out how to do this elegantly in a way mypy likes - # dimelo/plot_depth_profile.py:53: error: Item "str" of "str | Path" has no attribute "suffix" [union-attr] - mod_file_names = [Path(fn) for fn in mod_file_names] + mod_file_paths = [Path(fn) for fn in mod_file_names] trace_vectors = [] - for mod_file, regions, motif in zip(mod_file_names, regions_list, motifs): + for mod_file, regions, motif in zip( + mod_file_paths, regions_list, motifs, strict=False + ): match mod_file.suffix: case ".gz": _, valid_base_counts = load_processed.pileup_vectors_from_bedmethyl( @@ -229,15 +247,22 @@ def make_depth_profile_plot( """ if not utils.check_len_equal(trace_vectors, sample_names): raise ValueError("Unequal number of inputs") + x_axis_label = kwargs.pop("x_label", "Position (bp)") + legend_title = kwargs.pop("legend_title", "Mod, index") + vector_len = len(trace_vectors[0]) + half = vector_len // 2 + if vector_len % 2 == 0: + indep_vector = np.arange(-half, half) + else: + indep_vector = np.arange(-half, half + 1) axes = utils.line_plot( - indep_vector=np.arange( - -len(trace_vectors[0]) // 2, - len(trace_vectors[0]) // 2 + len(trace_vectors[0]) % 2, - ), - indep_name="pos", + indep_vector=indep_vector, + indep_name="position_bp", dep_vectors=trace_vectors, dep_names=sample_names, y_label="per strand reads\nwith motif and mod info", + legend_title=legend_title, **kwargs, ) + axes.set_xlabel(x_axis_label) return axes diff --git a/dimelo/plot_enrichment.py b/dimelo/plot_enrichment.py index d747b91..6fd8bba 100644 --- a/dimelo/plot_enrichment.py +++ b/dimelo/plot_enrichment.py @@ -82,19 +82,11 @@ def by_modification( ) -""" -TODO: Re-assignment issue: -dimelo/plot_enrichment.py:115: error: Incompatible types in assignment (expression has type "list[str | Path | list[str | Path]]", variable has type "list[str] | None") [assignment] -dimelo/plot_enrichment.py:121: error: Argument "sample_names" to "plot_enrichment" has incompatible type "list[str] | None"; expected "list[str]" [arg-type] -dimelo/plot_enrichment.py:141: error: Incompatible types in assignment (expression has type "list[str | Path]", variable has type "list[str] | None") [assignment] -dimelo/plot_enrichment.py:147: error: Argument "sample_names" to "plot_enrichment" has incompatible type "list[str] | None"; expected "list[str]" [arg-type] -""" - - def by_regions( mod_file_name: str | Path, - regions_list: list[str | Path | list[str | Path]], - motif: str, + regions_list: list[str | Path | list[str | Path]] | None = None, + motif: str | None = None, + regions: list[str | Path | list[str | Path]] | None = None, sample_names: list[str] | None = None, **kwargs, ) -> Axes: @@ -105,14 +97,28 @@ def by_regions( See plot_enrichment for details. """ - if sample_names is None: - sample_names = regions_list + if regions is not None: + if regions_list is not None and regions_list != regions: + raise ValueError( + "Pass either regions_list or regions to by_regions, not both with different values." + ) + regions_list = regions + if regions_list is None: + raise ValueError("by_regions requires regions_list (or alias regions).") + if motif is None: + raise ValueError("by_regions requires motif.") + + sample_names_for_plot = ( + sample_names + if sample_names is not None + else [str(region) for region in regions_list] + ) n_beds = len(regions_list) return plot_enrichment( mod_file_names=[mod_file_name] * n_beds, regions_list=regions_list, motifs=[motif] * n_beds, - sample_names=sample_names, + sample_names=sample_names_for_plot, **kwargs, ) @@ -131,14 +137,17 @@ def by_dataset( See plot_enrichment for details. """ - if sample_names is None: - sample_names = mod_file_names + sample_names_for_plot = ( + sample_names + if sample_names is not None + else [str(mod_file) for mod_file in mod_file_names] + ) n_mod_files = len(mod_file_names) return plot_enrichment( mod_file_names=mod_file_names, regions_list=[regions] * n_mod_files, motifs=[motif] * n_mod_files, - sample_names=sample_names, + sample_names=sample_names_for_plot, **kwargs, ) @@ -158,9 +167,6 @@ def get_enrichments( This helper function can be useful during plot prototyping, when repeatedly building plots from the same data. Its outputs can be passed as the first argument to make_enrichment_plot(). - TODO: I feel like this should be able to take in data directly as vectors/other datatypes, not just read from files. - TODO: Style-wise, is it cleaner to have it be a match statement or calling a method from a global dict? Cleaner here with a dict, cleaner overall with the match statements? - Args: mod_file_names: list of paths to modified base pileup data files regions_list: list of paths to bed files specifying regions to extract @@ -176,12 +182,12 @@ def get_enrichments( """ if not utils.check_len_equal(mod_file_names, regions_list, motifs): raise ValueError("Unequal number of inputs") - # TODO: redefinition error; still need to figure out how to do this elegantly in a way mypy likes - # dimelo/plot_enrichment.py:45: error: Item "str" of "str | Path" has no attribute "suffix" [union-attr] - mod_file_names = [Path(fn) for fn in mod_file_names] + mod_file_paths = [Path(fn) for fn in mod_file_names] mod_fractions = [] - for mod_file, regions, motif in zip(mod_file_names, regions_list, motifs): + for mod_file, regions, motif in zip( + mod_file_paths, regions_list, motifs, strict=False + ): match mod_file.suffix: case ".gz": n_mod, n_total = load_processed.pileup_counts_from_bedmethyl( diff --git a/dimelo/plot_enrichment_profile.py b/dimelo/plot_enrichment_profile.py index 20a625c..0a9a504 100644 --- a/dimelo/plot_enrichment_profile.py +++ b/dimelo/plot_enrichment_profile.py @@ -1,9 +1,106 @@ from pathlib import Path import numpy as np +import pandas as pd from matplotlib.axes import Axes -from . import load_processed, utils +from . import load_processed, plotting, utils + + +def _looks_like_motif_spec(label: str) -> bool: + parts = str(label).split(",") + if len(parts) < 2: + return False + return bool(parts[0]) and parts[1].strip().isdigit() + + +def _legacy_aggregate_axis_spec( + *, + window_size: int, + relative: bool, + regions_5to3prime: bool, +) -> plotting.AxisSpec: + return plotting.AxisSpec( + orientation="region_5to3" if regions_5to3prime else "genomic", + coordinate_mode="fixed_window", + anchor="center" if relative else "absolute", + upstream_bp=window_size, + downstream_bp=window_size, + ) + + +def _legacy_aggregate_profile_table( + trace_vectors: list[np.ndarray], + sample_names: list[str], +) -> pd.DataFrame: + rows: list[dict[str, object]] = [] + + for sample_name, trace_vector in zip(sample_names, trace_vectors, strict=True): + positions = np.arange( + -(len(trace_vector) // 2), + len(trace_vector) // 2 + len(trace_vector) % 2, + ) + for position, value in zip(positions, trace_vector, strict=True): + rows.append( + { + "sample_name": sample_name, + "position": float(position), + "anchor": 0.0, + "region_strand": "+", + "value": float(value), + } + ) + + return pd.DataFrame(rows) + + +def _prepare_legacy_aggregate_profile_vectors( + prepared_table: pd.DataFrame, + *, + sample_names: list[str], +) -> list[np.ndarray]: + prepared_vectors: list[np.ndarray] = [] + for sample_name in sample_names: + sample_table = prepared_table.loc[ + prepared_table["sample_name"] == sample_name + ].copy() + sample_table = sample_table.sort_values("plot_x") + prepared_vectors.append(sample_table["value"].to_numpy()) + return prepared_vectors + + +def _route_legacy_aggregate_axis_through_shared_core( + *, + window_size: int, + relative: bool, + regions_5to3prime: bool, + trace_vectors: list[np.ndarray], + sample_names: list[str], +) -> list[np.ndarray]: + axis = _legacy_aggregate_axis_spec( + window_size=window_size, + relative=relative, + regions_5to3prime=regions_5to3prime, + ) + aggregation = plotting.AggregationSpec() + aggregate_table = _legacy_aggregate_profile_table( + trace_vectors, + sample_names, + ) + prepared_payload = plotting.prepare_aggregate_plot_data( + aggregate_table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="value", + position_column="position", + anchor_column="anchor", + region_strand_column="region_strand", + ) + return _prepare_legacy_aggregate_profile_vectors( + prepared_payload["plot_table"], + sample_names=sample_names, + ) def plot_enrichment_profile( @@ -29,11 +126,6 @@ def plot_enrichment_profile( This is the most flexible method for enrichment profile plotting. For most use cases, consider using one of the plot_enrichment_profile.by_* methods. - TODO: I think it's reasonable for smoothing min_periods to be always set to 1 for this method, as it's a visualization tool, not quantitative. Is this unreasonable? - TODO: Should the more restrictive meta versions allow *args, or only **kwargs? - No, we want to be able to pass kwargs down to the line plotter, I think. Especially if we swap it out for one that takes more different standard args. - TODO: It's mildly confusing that there are required args that are only seen as *args or **kwargs in the more restrictive meta versions... But this is so much cleaner... - Args: mod_file_names: list of paths to modified base data files bed_file_names: list of paths to bed files specifying centered equal-length regions @@ -67,6 +159,14 @@ def plot_enrichment_profile( cores=cores, ) + trace_vectors = _route_legacy_aggregate_axis_through_shared_core( + window_size=window_size, + relative=relative, + regions_5to3prime=regions_5to3prime, + trace_vectors=trace_vectors, + sample_names=sample_names, + ) + if relative: offset_center = 0 else: @@ -112,21 +212,11 @@ def by_modification( ) -""" -TODO: Re-assignment issue: -dimelo/plot_enrichment_profile.py:142: error: Incompatible types in assignment (expression has type "list[str | Path | list[str | Path]]", variable has type "list[str] | None") [assignment] -dimelo/plot_enrichment_profile.py:148: error: Argument "sample_names" to "plot_enrichment_profile" has incompatible type "list[str] | None"; expected "list[str]" [arg-type] -dimelo/plot_enrichment_profile.py:168: error: Incompatible types in assignment (expression has type "list[str | Path]", variable has type "list[str] | None") [assignment] -dimelo/plot_enrichment_profile.py:174: error: Argument "sample_names" to "plot_enrichment_profile" has incompatible type "list[str] | None"; expected "list[str]" [arg-type] - -If sample names is None we assign it non-None values, so it's not clear what the problem is to me. We could make an intermediate dummy variable I guess? If that is the complaint? -""" - - def by_regions( mod_file_name: str | Path, - regions_list: list[str | Path | list[str | Path]], - motif: str, + regions_list: list[str | Path | list[str | Path]] | None = None, + motif: str | None = None, + regions: list[str | Path | list[str | Path]] | None = None, sample_names: list[str] | None = None, **kwargs, ) -> Axes: @@ -137,14 +227,28 @@ def by_regions( See plot_enrichment_profile for details. """ - if sample_names is None: - sample_names = regions_list + if regions is not None: + if regions_list is not None and regions_list != regions: + raise ValueError( + "Pass either regions_list or regions to by_regions, not both with different values." + ) + regions_list = regions + if regions_list is None: + raise ValueError("by_regions requires regions_list (or alias regions).") + if motif is None: + raise ValueError("by_regions requires motif.") + + sample_names_for_plot = ( + sample_names + if sample_names is not None + else [str(region) for region in regions_list] + ) n_beds = len(regions_list) return plot_enrichment_profile( mod_file_names=[mod_file_name] * n_beds, regions_list=regions_list, motifs=[motif] * n_beds, - sample_names=sample_names, + sample_names=sample_names_for_plot, **kwargs, ) @@ -163,14 +267,17 @@ def by_dataset( See plot_enrichment_profile for details. """ - if sample_names is None: - sample_names = mod_file_names + sample_names_for_plot = ( + sample_names + if sample_names is not None + else [str(mod_file) for mod_file in mod_file_names] + ) n_mod_files = len(mod_file_names) return plot_enrichment_profile( mod_file_names=mod_file_names, regions_list=[regions] * n_mod_files, motifs=[motif] * n_mod_files, - sample_names=sample_names, + sample_names=sample_names_for_plot, **kwargs, ) @@ -192,10 +299,6 @@ def get_enrichment_profiles( This helper function can be useful during plot prototyping, when repeatedly building plots from the same data. Its outputs can be passed as the first argument to make_enrichment_profile_plot(). - TODO: I feel like this should be able to take in data directly as vectors/other datatypes, not just read from files. - TODO: Style-wise, is it cleaner to have it be a match statement or calling a method from a global dict? Cleaner here with a dict, cleaner overall with the match statements? - TODO: I think it's reasonable for smoothing min_periods to be always set to 1 for this method, as it's a visualization tool, not quantitative. Is this unreasonable? - Args: mod_file_names: list of paths to modified base data files bed_file_names: list of paths to bed files specifying centered equal-length regions @@ -213,12 +316,12 @@ def get_enrichment_profiles( """ if not utils.check_len_equal(mod_file_names, regions_list, motifs): raise ValueError("Unequal number of inputs") - # TODO: redefinition error; still need to figure out how to do this elegantly in a way mypy likes - # dimelo/plot_enrichment_profile.py:53: error: Item "str" of "str | Path" has no attribute "suffix" [union-attr] - mod_file_names = [Path(fn) for fn in mod_file_names] + mod_file_paths = [Path(fn) for fn in mod_file_names] trace_vectors = [] - for mod_file, regions, motif in zip(mod_file_names, regions_list, motifs): + for mod_file, regions, motif in zip( + mod_file_paths, regions_list, motifs, strict=False + ): match mod_file.suffix: case ".gz": modified_base_counts, valid_base_counts = ( @@ -281,6 +384,14 @@ def make_enrichment_profile_plot( """ if not utils.check_len_equal(trace_vectors, sample_names): raise ValueError("Unequal number of inputs") + legend_title = kwargs.pop("legend_title", None) + if ( + legend_title is None + and sample_names + and all(_looks_like_motif_spec(name) for name in sample_names) + ): + legend_title = "Modifications (motif, mod_index)" + resolved_legend_title = "variable" if legend_title is None else str(legend_title) axes = utils.line_plot( indep_vector=np.arange( offset_center - len(trace_vectors[0]) // 2, @@ -290,6 +401,7 @@ def make_enrichment_profile_plot( dep_vectors=trace_vectors, dep_names=sample_names, y_label="fraction modified bases", + legend_title=resolved_legend_title, **kwargs, ) return axes diff --git a/dimelo/plot_read_browser.py b/dimelo/plot_read_browser.py index febccac..95b330f 100644 --- a/dimelo/plot_read_browser.py +++ b/dimelo/plot_read_browser.py @@ -154,15 +154,19 @@ def format_browser_data( read_tuples, columns=entry_labels, exclude=to_exclude ) - # For each row, pull out just the positions of valid bases and the probabilities at those positions - # TODO: I don't like using iterrows, but it seems silly to have two calls to apply that do basically the same thing redundantly; just looping once instead + # For each row, pull out just the positions of valid bases and the probabilities at those positions. + # Iterate over NumPy-backed column arrays to avoid iterrows() per-row Series overhead. + mod_vectors = read_df["mod_vector"].to_numpy(copy=False) + val_vectors = read_df["val_vector"].to_numpy(copy=False) + read_starts = read_df["read_start"].to_numpy(copy=False) prob_vectors = [] pos_vectors = [] - for _, row in read_df.iterrows(): - selector = row.val_vector == 1 - all_positions = np.arange(len(row.mod_vector)) + row.read_start - prob_vectors.append(row.mod_vector[selector]) - pos_vectors.append(all_positions[selector]) + for mod_vector, val_vector, read_start in zip( + mod_vectors, val_vectors, read_starts, strict=False + ): + selected_indices = np.flatnonzero(val_vector == 1) + prob_vectors.append(mod_vector[selected_indices]) + pos_vectors.append(selected_indices + read_start) read_df["prob_vector"] = prob_vectors read_df["pos_vector"] = pos_vectors @@ -237,32 +241,56 @@ def collapse_rows( TODO: This could be improved by checking for overlaps on both ends of the seed read for each row. This might allow other types of pre-sorting to work more effectively. """ + read_count = len(read_extent_df) # Sentinel value for un-indexed reads is -1 - collapsed_indices = -np.ones(len(read_extent_df), dtype=int) + collapsed_indices = -np.ones(read_count, dtype=int) + if read_count == 0: + return pd.Series(collapsed_indices, index=read_extent_df["y_index"]) + + starts = read_extent_df["read_start"].to_numpy(copy=False) + ends = read_extent_df["read_end"].to_numpy(copy=False) + + # parent[i] points to the next unassigned index at or after i; parent[n] is a sentinel. + parent = np.arange(read_count + 1, dtype=int) + + def _find_next_unassigned(index: int) -> int: + while parent[index] != index: + parent[index] = parent[parent[index]] + index = parent[index] + return index + + def _mark_assigned(index: int) -> None: + parent[index] = _find_next_unassigned(index + 1) - # Collapse reads curr_y_idx = 0 - for seed_read_idx in range(len(read_extent_df)): - # If seed read has been indexed already, move on - if collapsed_indices[seed_read_idx] != -1: - continue + seed_search_start = 0 + while True: + seed_read_idx = _find_next_unassigned(seed_search_start) + if seed_read_idx >= read_count: + break + collapsed_indices[seed_read_idx] = curr_y_idx + _mark_assigned(seed_read_idx) + + curr_row_end = int(ends[seed_read_idx]) + scan_cursor = seed_read_idx + while True: + # Preserve the original one-pass scan semantics while skipping impossible ranges. + start_threshold = int( + np.searchsorted(starts, curr_row_end + minimum_gap, side="right") + ) + candidate_start = max(scan_cursor + 1, start_threshold) + candidate_idx = _find_next_unassigned(candidate_start) + if candidate_idx >= read_count: + break - # Add any other non-indexed reads that fit onto the current row - curr_row_end = read_extent_df.iloc[seed_read_idx]["read_end"] - for other_read_idx in range(seed_read_idx + 1, len(read_extent_df)): - # If other read has been indexed already, move on - if collapsed_indices[other_read_idx] != -1: - continue - # If other read fits onto the current row, index it - if ( - read_extent_df.iloc[other_read_idx]["read_start"] - > curr_row_end + minimum_gap - ): - collapsed_indices[other_read_idx] = curr_y_idx - curr_row_end = read_extent_df.iloc[other_read_idx]["read_end"] + collapsed_indices[candidate_idx] = curr_y_idx + _mark_assigned(candidate_idx) + curr_row_end = int(ends[candidate_idx]) + scan_cursor = candidate_idx curr_y_idx += 1 + seed_search_start = seed_read_idx # Series mapping original indices to collapsed indices idx_map_orig2collapse = pd.Series( @@ -274,14 +302,18 @@ def collapse_rows( match meta_sort: case "full_extent": # sort by full extent of the covered reads in the row (max end - min start) - idx_map_meta2collapse = ( - read_extent_df.groupby(collapsed_indices) - .apply( - lambda row_group: row_group.read_end.max() - - row_group.read_start.min() + grouped = ( + read_extent_df.assign(_collapsed=collapsed_indices) + .groupby("_collapsed", sort=False) + .agg( + start_min=("read_start", "min"), + end_max=("read_end", "max"), ) + ) + idx_map_meta2collapse = ( + (grouped["end_max"] - grouped["start_min"]) .sort_values(ascending=False) - .reset_index()["index"] + .index ) case "covered_bases": # sort by number of bases covered by reads in the row @@ -290,14 +322,15 @@ def collapse_rows( read_lengths.groupby(collapsed_indices) .sum() .sort_values(ascending=False) - .reset_index()["index"] + .index ) case _: raise ValueError(f"Invalid meta sorting option: {meta_sort}") # Series mapping collapsed indices to meta indices idx_map_collapse2meta = pd.Series( - idx_map_meta2collapse.index.values, index=idx_map_meta2collapse + np.arange(len(idx_map_meta2collapse), dtype=int), + index=idx_map_meta2collapse, ) # Return Series mapping original indices to meta indices @@ -365,21 +398,43 @@ def make_browser_figure( plot_bgcolor="rgba(0,0,0,0)", xaxis=dict(range=[region_start, region_end]), ) - # TODO: I feel like there has to be a cleaner way to do this, maybe using plotly express, but I dont know and I'm just trying to get this done first. Lots of iterrows. Sad. fig = plotly.graph_objects.Figure(layout=layout) - for _, row in read_extent_df.iterrows(): - # TODO: How can I get the hover information for the reads to match the ones for the mod events? Not sure how to get customdata and hovertemplate working here. + if not read_extent_df.empty: + starts = read_extent_df["read_start"].to_numpy(copy=False) + ends = read_extent_df["read_end"].to_numpy(copy=False) + y_values = read_extent_df["y_index"].to_numpy(copy=False) + read_names = read_extent_df["read_name"].astype(str).to_numpy(copy=False) + + n_reads = len(read_extent_df) + x_coords = np.empty(n_reads * 3, dtype=object) + y_coords = np.empty(n_reads * 3, dtype=float) + hover_text = np.empty(n_reads * 3, dtype=object) + + x_coords[0::3] = starts + x_coords[1::3] = ends + x_coords[2::3] = None + + y_coords[0::3] = y_values + y_coords[1::3] = y_values + y_coords[2::3] = np.nan + + hover_text[0::3] = read_names + hover_text[1::3] = read_names + hover_text[2::3] = "" + fig.add_trace( plotly.graph_objects.Scatter( - x=[row.read_start, row.read_end], - y=[row.y_index, row.y_index], + x=x_coords, + y=y_coords, mode="lines", line=dict(width=width, color="lightgrey"), showlegend=False, hoverinfo="text", - hovertext=row.read_name, + hovertext=hover_text, + connectgaps=False, ) ) + for motif_idx, (motif, motif_df) in enumerate(mod_event_df.groupby("motif")): min_overall = motif_df["prob"].min() max_overall = motif_df["prob"].max() diff --git a/dimelo/plot_reads.py b/dimelo/plot_reads.py index c6a80f2..45bb00b 100644 --- a/dimelo/plot_reads.py +++ b/dimelo/plot_reads.py @@ -1,22 +1,75 @@ -""" -I'm conflicted about how to handle some of this. - -There are two different ways of doing single read plotting: "rectangular" and "whole read". -"rectangular" means displaying exactly the requested region. -"whole read" means displaying the entirety of any read overlapping the requested region. -Probably need separate methods for all of this? Is there shared functionality? Do they live in the same file? Etc. - -I'm beginning to lose the thread of where we check for regions making sense. -Maybe this is an argument for an internal region class that makes checking easy? I don't know. -""" +"""Single-read plotting entrypoints and legacy axis routing helpers.""" +import math +from collections.abc import Sequence from pathlib import Path import pandas as pd import seaborn as sns from matplotlib.axes import Axes -from . import load_processed, utils +from . import load_processed, plotting, utils + + +def _legacy_single_read_axis_spec( + *, + relative: bool, + regions_5to3prime: bool, + window_size: int | None, + reads: Sequence[Sequence[object]], + regions_dict: dict | None, +) -> plotting.AxisSpec: + if relative: + if window_size is not None: + half_window = window_size + elif regions_dict is not None and len(regions_dict) > 0: + region_start, region_end, _ = next(iter(regions_dict.values()))[0] + half_window = (region_end - region_start) // 2 + else: + half_window = 0 + return plotting.AxisSpec( + orientation="region_5to3" if regions_5to3prime else "genomic", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=half_window, + downstream_bp=half_window, + ) + + absolute_bound = 0.0 + for read_positions in reads: + for position in read_positions: + absolute_bound = max(absolute_bound, abs(float(position))) + + return plotting.AxisSpec( + orientation="genomic", + coordinate_mode="fixed_window", + anchor="absolute", + upstream_bp=math.ceil(absolute_bound), + downstream_bp=math.ceil(absolute_bound), + ) + + +def _legacy_single_read_plot_table( + reads: Sequence[Sequence[object]], + read_names: Sequence[object], + mods: Sequence[object], + *, + relative: bool, + regions_5to3prime: bool, +) -> pd.DataFrame: + plot_table = pd.DataFrame( + { + "read_name": read_names, + "read_index": list(range(len(read_names))), + "mod": mods, + "pos": reads, + } + ).explode("pos") + plot_table = plot_table.reset_index(drop=True) + plot_table["anchor"] = 0.0 + if relative and regions_5to3prime: + plot_table["region_strand"] = "+" + return plot_table def plot_reads( @@ -34,10 +87,6 @@ def plot_reads( """ Plots centered single reads as a scatterplot, cut off at the boundaries of the requested regions? - TODO: I feel like this should be able to take in data directly as vectors/other datatypes, not just read from files. - TODO: Style-wise, is it cleaner to have it be a match statement or calling a method from a global dict? Cleaner here with a dict, cleaner overall with the match statements? - TODO: So far, this is the only method to do plotting without utility methods. Is this reasonable? Is it that unique? - Args: mod_file_name: path to file containing modification data for single reads regions: path to bed file specifying regions to extract @@ -56,16 +105,20 @@ def plot_reads( mod_file_name = Path(mod_file_name) # bed_file_name = Path(bed_file_name) size = kwargs.pop("s", 0.5) + legend_title = kwargs.pop("legend_title", "Mod, index") + x_axis_label = kwargs.pop("x_label", "Position (bp)") + y_axis_mode = kwargs.pop("y_axis", "read_index") + y_axis_label = kwargs.pop( + "y_label", + "Read index" if y_axis_mode == "read_index" else "Read name", + ) palette = kwargs.pop("palette", {}) merged_palette = {**utils.DEFAULT_COLORS, **palette} match mod_file_name.suffix: - # TODO: Fix how the fake reads options work, and make sure they have the same interface as the real ones. - # dimelo/plot_reads.py:63: error: Argument "regions" to "reads_from_fake" has incompatible type "str | Path | list[str | Path]"; expected "Path" [arg-type] - # Will also fix the following error: - # dimelo/plot_reads.py:68: error: Incompatible types in assignment (expression has type "dict[Any, Any] | None", variable has type "dict[Any, Any]") [assignment] + # Keep fake-file handling aligned with the non-fake return contract. case ".fake": reads, read_names, mods, regions_dict = load_processed.reads_from_fake( file=mod_file_name, @@ -87,14 +140,41 @@ def plot_reads( ) ) - # Convert data frame where each row represents a read to a data frame where each row represents a single modified position in a read - df = pd.DataFrame({"read_name": read_names, "mod": mods, "pos": reads}).explode( - "pos" + plot_table = _legacy_single_read_plot_table( + reads, + read_names, + mods, + relative=relative, + regions_5to3prime=regions_5to3prime, + ) + if y_axis_mode not in plot_table.columns: + raise ValueError( + f"Unsupported y_axis {y_axis_mode!r}. Use one of: {sorted(plot_table.columns)}" + ) + axis = _legacy_single_read_axis_spec( + relative=relative, + regions_5to3prime=regions_5to3prime, + window_size=window_size, + reads=reads, + regions_dict=regions_dict, + ) + prep_kwargs: dict[str, str] = { + "position_column": "pos", + "anchor_column": "anchor", + } + if axis.orientation == "region_5to3": + prep_kwargs["region_strand_column"] = "region_strand" + + prepared = plotting.prepare_single_read_plot_data( + plot_table, + plot_family="single_read_raster", + axis=axis, + **prep_kwargs, ) axes = sns.scatterplot( - data=df, - x="pos", - y="read_name", + data=prepared["plot_table"], + x="plot_x", + y=y_axis_mode, hue="mod", # palette=colors, s=size, @@ -109,25 +189,24 @@ def plot_reads( # Retrieve legend handles and labels handles, labels = axes.get_legend_handles_labels() - # Update legend properties - # TODO: Do we need to do this now and after? + # Update legend properties. if legend is not None: - legend.set_title("Mod") + legend.set_title(legend_title) # Update marker size for all handles for handle in handles: if hasattr(handle, "set_markersize"): handle.set_markersize(10) # Set a larger marker size for legend - # Re-apply the legend with updated handles - # TODO: Is this step necessary? - axes.legend(handles, labels, title="Mod") + # Re-apply the legend with updated marker size. + axes.legend(handles, labels, title=legend_title) + + if hasattr(axes, "set_xlabel"): + axes.set_xlabel(x_axis_label) + if hasattr(axes, "set_ylabel"): + axes.set_ylabel(y_axis_label) - # TODO: Technically, regions_dict can be None by this point. In that scenario, it will error out when checking the length. - # It can be None according to type hints but in the actual logical flow I believe it cannot be None - # However, we can easily just check whether it is None here as well, in case we change behavior elsewhere. - # Identified with mypy through the following error: - # dimelo/plot_reads.py:101: error: Argument 1 to "len" has incompatible type "dict[Any, Any] | None"; expected "Sized" [arg-type] + # regions_dict may be absent for some loader paths, so guard before reading bounds. if relative and regions_dict is not None and len(regions_dict) > 0: region1_start, region1_end, _ = next(iter(regions_dict.values()))[0] effective_window_size = (region1_end - region1_start) // 2 diff --git a/dimelo/plotting.py b/dimelo/plotting.py new file mode 100644 index 0000000..53806c9 --- /dev/null +++ b/dimelo/plotting.py @@ -0,0 +1,2166 @@ +from __future__ import annotations + +from collections import Counter +from collections.abc import Sequence +from dataclasses import dataclass + +import numpy as np +import pandas as pd + +from dimelo.distribution import _require_columns + +_SEGMENT_AXIS_INTERNAL_SEGMENT_ID = "__dimelo_segment_axis_segment_id" +_SEGMENT_AXIS_INTERNAL_PLOT_START = "__dimelo_segment_axis_plot_start" +_SEGMENT_AXIS_INTERNAL_PLOT_END = "__dimelo_segment_axis_plot_end" +_SEGMENT_AXIS_INTERNAL_SEGMENT_SPAN = "__dimelo_segment_axis_segment_span" +_SEGMENT_AXIS_INTERNAL_COLUMNS = { + _SEGMENT_AXIS_INTERNAL_SEGMENT_ID, + _SEGMENT_AXIS_INTERNAL_PLOT_START, + _SEGMENT_AXIS_INTERNAL_PLOT_END, + _SEGMENT_AXIS_INTERNAL_SEGMENT_SPAN, +} + + +@dataclass(frozen=True) +class SegmentSpec: + segment_id: str + label: str + start_ref: int + end_ref: int + mode: str = "raw" + bins: int | None = None + plot_gap_after: bool = False + contiguous_with_previous: bool = True + + +@dataclass(frozen=True) +class AxisSpec: + orientation: str + coordinate_mode: str + anchor: str | None = None + upstream_bp: int | None = None + downstream_bp: int | None = None + segments: list[SegmentSpec] | None = None + + +@dataclass(frozen=True) +class AggregationSpec: + weighting: str = "equal_region" + within_region_summary: str = "mean" + signal_normalization: str = "none" + layout: str = "faceted" + + +def validate_axis_spec(axis: AxisSpec, *, plot_family: str) -> None: + if axis.orientation not in {"genomic", "region_5to3"}: + raise ValueError("AxisSpec.orientation must be 'genomic' or 'region_5to3'.") + + if axis.coordinate_mode not in {"fixed_window", "segment_map"}: + raise ValueError( + "AxisSpec.coordinate_mode must be 'fixed_window' or 'segment_map'." + ) + + if axis.coordinate_mode == "segment_map" and not axis.segments: + raise ValueError("segment_map requires segments.") + + if plot_family == "single_read_raster" and axis.coordinate_mode == "segment_map": + raise ValueError("segment_map is aggregate-only for single_read_raster plots.") + + if axis.coordinate_mode == "fixed_window": + if axis.upstream_bp is None or axis.downstream_bp is None: + raise ValueError("fixed_window requires upstream_bp and downstream_bp.") + if axis.upstream_bp < 0 or axis.downstream_bp < 0: + raise ValueError( + "fixed_window upstream_bp and downstream_bp must be non-negative." + ) + + if ( + plot_family == "single_read_raster" + and axis.segments + and any(segment.mode == "scaled" for segment in axis.segments) + ): + raise ValueError( + "single_read_raster plots must preserve coordinates and cannot use scaled segments." + ) + + +def validate_aggregation_spec(spec: AggregationSpec) -> None: + if spec.weighting not in {"equal_region", "equal_observation", "coverage_weighted"}: + raise ValueError("Unsupported weighting mode.") + + if spec.within_region_summary not in {"mean", "fraction", "density"}: + raise ValueError("Unsupported within_region_summary.") + + if spec.signal_normalization not in { + "none", + "per_region", + "global", + "control_regions", + }: + raise ValueError("Unsupported signal_normalization.") + + if spec.layout not in {"concatenated", "faceted"}: + raise ValueError("Unsupported layout mode.") + + +def _region_contrast_grouping_key(result, position_table: pd.DataFrame) -> str: + available_keys = [ + column + for column in ("sample_id", "condition") + if column in position_table.columns + ] + if not available_keys: + raise ValueError( + "region contrast plotting requires position_table to include sample_id or condition." + ) + if len(available_keys) == 1: + return available_keys[0] + + contrast_values: set[str] = set() + contrast = getattr(result, "contrast", None) + if contrast is not None: + contrast_values.update(str(value) for value in (contrast.numerator or [])) + contrast_values.update(str(value) for value in (contrast.denominator or [])) + + sample_ids = {str(value) for value in position_table["sample_id"].dropna().unique()} + conditions = {str(value) for value in position_table["condition"].dropna().unique()} + sample_match = bool(contrast_values) and contrast_values.issubset(sample_ids) + condition_match = bool(contrast_values) and contrast_values.issubset(conditions) + + if sample_match and not condition_match: + return "sample_id" + if condition_match and not sample_match: + return "condition" + + raise ValueError( + "region contrast plotting could not resolve a unique grouping key from " + "position_table columns sample_id and condition." + ) + + +def _region_contrast_metadata(result) -> dict[str, object]: + metadata = getattr(result, "metadata", {}) or {} + return { + "analysis_unit": metadata.get("analysis_unit"), + "representation": metadata.get("representation"), + "signal_source": metadata.get("signal_source"), + "test": metadata.get("test"), + } + + +def _validate_global_analysis_result(result) -> None: + if result is None: + raise ValueError("plotting helpers require a GlobalAnalysisResult.") + required_attrs = ("summary", "windows", "normalization_factors", "metadata") + if not all(hasattr(result, attr) for attr in required_attrs): + raise TypeError("plotting helpers require a GlobalAnalysisResult-like object.") + + +def _validate_shared_cluster_result(result) -> None: + if result is None: + raise ValueError("plotting helpers require a SharedClusterResult.") + required_attrs = ( + "model", + "cluster_distribution", + "condition_distribution", + "cluster_profiles", + "plot_data", + "metadata", + ) + if not all(hasattr(result, attr) for attr in required_attrs): + raise TypeError("plotting helpers require a SharedClusterResult-like object.") + if not hasattr(result.model, "mode") or not hasattr(result.model, "cluster_labels"): + raise TypeError("plotting helpers require a SharedClusterResult-like object.") + + +def _empty_distribution_change_table() -> pd.DataFrame: + return pd.DataFrame( + columns=[ + "condition", + "cluster", + "count", + "fraction", + "replicate_n", + "reference_fraction", + "delta_fraction", + "log2_fc", + ] + ) + + +def _ordered_non_null_values(table: pd.DataFrame, column: str) -> list[object]: + if column not in table.columns: + return [] + return table.loc[table[column].notna(), column].drop_duplicates().tolist() + + +def _filter_motif_table( + table: pd.DataFrame, motifs: list[str] | None, *, owner: str +) -> pd.DataFrame: + _require_columns(table, ("motif",), owner) + if motifs is None: + return table.copy() + + filtered = table.loc[table["motif"].isin(motifs)].copy() + if filtered.empty: + raise ValueError(f"Requested motifs are not present in {owner}.") + return filtered + + +def _validate_region_discovery_result(result) -> None: + if result is None: + raise ValueError("plotting helpers require a RegionDiscoveryResult.") + if ( + not hasattr(result, "windows") + or not hasattr(result, "hits") + or not hasattr(result, "metadata") + ): + raise TypeError("plotting helpers require a RegionDiscoveryResult-like object.") + + +def _select_discovery_score_column( + windows: pd.DataFrame, score_column: str | None +) -> str: + if score_column is not None: + if score_column not in windows.columns: + raise ValueError(f"Unknown discovery score column: {score_column}") + return score_column + if "score_value" in windows.columns or windows.empty: + return "score_value" + raise ValueError( + "Could not infer a discovery score column from RegionDiscoveryResult.windows." + ) + + +def _empty_discovery_scan_table(score_column: str) -> pd.DataFrame: + return pd.DataFrame( + columns=[ + "window_id", + "contig", + "start", + "end", + "strand", + score_column, + "window_midpoint", + "is_hit", + ] + ) + + +def _discovery_contig_column(table: pd.DataFrame, *, owner: str) -> str: + if "chromosome" in table.columns: + return "chromosome" + if "chrom" in table.columns: + return "chrom" + raise ValueError(f"{owner} must include either 'chromosome' or 'chrom'.") + + +def _sort_discovery_table( + table: pd.DataFrame, + *, + contig_order: list[str], + sort_columns: list[str], +) -> pd.DataFrame: + if table.empty: + return table.reset_index(drop=True) + + sorted_table = table.copy() + sorted_table["__dimelo_contig_order"] = pd.Categorical( + sorted_table["contig"], + categories=contig_order, + ordered=True, + ) + sorted_table = sorted_table.sort_values( + ["__dimelo_contig_order", *sort_columns], + kind="stable", + ).drop(columns="__dimelo_contig_order") + return sorted_table.reset_index(drop=True) + + +def _select_region_discovery_hits( + hits: pd.DataFrame, + *, + top_n: int | None, +) -> tuple[pd.DataFrame, str]: + if top_n is not None and top_n < 0: + raise ValueError("top_n must be non-negative.") + if hits.empty: + return hits.copy(), "top_n" + + if "rank" in hits.columns: + selected = hits.sort_values(["rank"], kind="stable") + else: + selected = hits.copy() + if top_n is not None: + selected = selected.head(top_n) + return selected.copy().reset_index(drop=True), "top_n" + + +def _relative_position(position: float, anchor: float) -> float: + return float(position) - float(anchor) + + +def _orient_position( + relative_position: float, region_strand: str, orientation: str +) -> float: + if orientation != "region_5to3": + return relative_position + + if region_strand not in {"+", "-"}: + raise ValueError( + "region_5to3 fixed_window prep requires region_strand values of '+' or '-'." + ) + + if region_strand == "-": + return -relative_position + return relative_position + + +def _prepare_fixed_window_plot_data( + table: pd.DataFrame, + *, + axis: AxisSpec, + position_column: str, + anchor_column: str, + region_strand_column: str | None = None, +) -> tuple[pd.DataFrame, pd.DataFrame]: + if axis.coordinate_mode != "fixed_window": + raise ValueError( + "fixed_window prep only supports coordinate_mode='fixed_window'." + ) + + _require_columns(table, (position_column, anchor_column), "plot_table") + if axis.orientation == "region_5to3": + if region_strand_column is None: + raise ValueError( + "region_5to3 fixed_window prep requires region_strand_column." + ) + _require_columns(table, (region_strand_column,), "plot_table") + + plot_table = table.copy() + relative_position = plot_table[position_column].astype(float) - plot_table[ + anchor_column + ].astype(float) + if axis.orientation == "region_5to3": + plot_table["plot_x"] = [ + _orient_position( + relative_position=rel, + region_strand=strand, + orientation=axis.orientation, + ) + for rel, strand in zip( + relative_position, plot_table[region_strand_column], strict=True + ) + ] + else: + plot_table["plot_x"] = relative_position + + window_mask = plot_table["plot_x"].between(-axis.upstream_bp, axis.downstream_bp) + plot_table = plot_table.loc[window_mask].copy().reset_index(drop=True) + + axis_table = pd.DataFrame( + [ + { + "segment_id": "window", + "axis_min": -axis.upstream_bp, + "axis_max": axis.downstream_bp, + } + ] + ) + return plot_table, axis_table + + +def _build_segment_axis_table(segments: list[SegmentSpec]) -> pd.DataFrame: + segment_id_counts = Counter(segment.segment_id for segment in segments) + duplicate_segment_ids = sorted( + segment_id for segment_id, count in segment_id_counts.items() if count > 1 + ) + if duplicate_segment_ids: + raise ValueError( + "segment_map axis.segments contains duplicate segment_id values: " + f"{', '.join(duplicate_segment_ids)}." + ) + + rows: list[dict[str, object]] = [] + running_start = 0 + + for segment in segments: + if segment.mode not in {"raw", "scaled"}: + raise ValueError( + "segment_map axis.segments contains invalid mode values: " + f"{segment.segment_id} has mode={segment.mode!r}; mode must be 'raw' or 'scaled'." + ) + if segment.bins is not None and segment.bins <= 0: + raise ValueError( + "segment_map axis.segments contains invalid bins values: " + f"{segment.segment_id} has bins={segment.bins!r}." + ) + if ( + segment.mode == "scaled" + and segment.bins is None + and segment.end_ref <= segment.start_ref + ): + raise ValueError( + "segment_map axis.segments contains invalid scaled span values: " + f"{segment.segment_id} has start_ref={segment.start_ref!r} and end_ref={segment.end_ref!r}." + ) + if segment.mode == "raw" and segment.end_ref <= segment.start_ref: + raise ValueError( + "segment_map axis.segments contains invalid raw span values: " + f"{segment.segment_id} has start_ref={segment.start_ref!r} and end_ref={segment.end_ref!r}." + ) + + span = ( + segment.bins + if segment.bins is not None + else segment.end_ref - segment.start_ref + ) + rows.append( + { + "segment_id": segment.segment_id, + "label": segment.label, + "start_ref": segment.start_ref, + "end_ref": segment.end_ref, + "mode": segment.mode, + "bins": segment.bins, + "plot_start": running_start, + "plot_end": running_start + span, + "contiguous_with_previous": segment.contiguous_with_previous, + "plot_gap_after": segment.plot_gap_after, + "discontinuity_before": not segment.contiguous_with_previous, + "discontinuity_after": segment.plot_gap_after, + } + ) + running_start += span + + return pd.DataFrame(rows) + + +def _prepare_segment_map_plot_data( + table: pd.DataFrame, + *, + axis: AxisSpec, + segment_id_column: str, + segment_position_column: str, +) -> tuple[pd.DataFrame, pd.DataFrame]: + if not axis.segments: + raise ValueError("segment_map requires segments.") + + _require_columns(table, (segment_id_column, segment_position_column), "plot_table") + reserved_columns = _SEGMENT_AXIS_INTERNAL_COLUMNS.intersection(table.columns) + if reserved_columns: + raise ValueError( + "segment_map plotting received plot_table columns reserved for internal use: " + f"{', '.join(sorted(reserved_columns))}." + ) + + axis_table = _build_segment_axis_table(axis.segments) + plot_table = table.copy() + axis_lookup = axis_table.loc[:, ["segment_id", "plot_start", "plot_end"]].rename( + columns={ + "segment_id": _SEGMENT_AXIS_INTERNAL_SEGMENT_ID, + "plot_start": _SEGMENT_AXIS_INTERNAL_PLOT_START, + "plot_end": _SEGMENT_AXIS_INTERNAL_PLOT_END, + } + ) + axis_lookup[_SEGMENT_AXIS_INTERNAL_SEGMENT_SPAN] = ( + axis_lookup[_SEGMENT_AXIS_INTERNAL_PLOT_END] + - axis_lookup[_SEGMENT_AXIS_INTERNAL_PLOT_START] + ) + plot_table = plot_table.merge( + axis_lookup, + left_on=segment_id_column, + right_on=_SEGMENT_AXIS_INTERNAL_SEGMENT_ID, + how="left", + ) + unknown_segment_mask = plot_table[_SEGMENT_AXIS_INTERNAL_PLOT_START].isna() + if unknown_segment_mask.any(): + unknown_segment_ids = sorted( + plot_table.loc[unknown_segment_mask, segment_id_column].astype(str).unique() + ) + raise ValueError( + "segment_map plotting received unknown segment_id values: " + f"{', '.join(unknown_segment_ids)}." + ) + + segment_positions = pd.to_numeric( + plot_table[segment_position_column], errors="raise" + ) + missing_position_mask = segment_positions.isna() + if missing_position_mask.any(): + invalid_rows = plot_table.loc[ + missing_position_mask, [segment_id_column, segment_position_column] + ] + raise ValueError( + "segment_position_column contains missing or NaN values. " + f"Invalid rows: {invalid_rows.to_dict(orient='records')}." + ) + + invalid_position_mask = (segment_positions < 0) | ( + segment_positions >= plot_table[_SEGMENT_AXIS_INTERNAL_SEGMENT_SPAN] + ) + if invalid_position_mask.any(): + invalid_rows = plot_table.loc[ + invalid_position_mask, [segment_id_column, segment_position_column] + ].copy() + invalid_rows["segment_span"] = plot_table.loc[ + invalid_position_mask, _SEGMENT_AXIS_INTERNAL_SEGMENT_SPAN + ].values + raise ValueError( + "segment_position_column values must stay within the declared segment span " + "for each segment." + f" Invalid rows: {invalid_rows.to_dict(orient='records')}." + ) + + plot_table["plot_x"] = ( + plot_table[_SEGMENT_AXIS_INTERNAL_PLOT_START] + segment_positions + ) + plot_table = plot_table.drop( + columns=[ + _SEGMENT_AXIS_INTERNAL_SEGMENT_ID, + _SEGMENT_AXIS_INTERNAL_PLOT_START, + _SEGMENT_AXIS_INTERNAL_PLOT_END, + _SEGMENT_AXIS_INTERNAL_SEGMENT_SPAN, + ] + ) + + return plot_table, axis_table + + +def _prepare_region_contrast_position_table( + *, + result, + position_table: pd.DataFrame, + grouping_key: str, +) -> pd.DataFrame: + _require_columns( + position_table, + ("region_id", grouping_key, "position", "anchor", "value", "region_strand"), + "position_table", + ) + summary_region_ids = result.summary["region_id"].dropna().astype(str) + filtered = position_table[ + position_table["region_id"].astype(str).isin(summary_region_ids) + ].copy() + if filtered.empty: + raise ValueError( + "position_table does not contain any region_id values present in the contrast result." + ) + return filtered.reset_index(drop=True) + + +def _prepare_region_contrast_value_modes( + *, + result, + position_table: pd.DataFrame, + grouping_key: str, +) -> pd.DataFrame: + contrast = result.contrast + numerator_mask = position_table[grouping_key].isin(contrast.numerator or []) + denominator_mask = position_table[grouping_key].isin(contrast.denominator or []) + + numerator = position_table.loc[numerator_mask].copy() + denominator = position_table.loc[denominator_mask].copy() + if numerator.empty or denominator.empty: + raise ValueError( + "position_table does not contain rows for both contrast sides." + ) + + join_keys = ["region_id", "position", "anchor", "region_strand"] + numerator = ( + numerator.loc[:, join_keys + ["value"]] + .groupby(join_keys, as_index=False, sort=False) + .mean(numeric_only=True) + ) + denominator = ( + denominator.loc[:, join_keys + ["value"]] + .groupby(join_keys, as_index=False, sort=False) + .mean(numeric_only=True) + ) + + coordinate_match = numerator.loc[:, join_keys].merge( + denominator.loc[:, join_keys], + on=join_keys, + how="outer", + indicator=True, + ) + if not (coordinate_match["_merge"] == "both").all(): + mismatched_rows = coordinate_match.loc[ + coordinate_match["_merge"] != "both", join_keys + ["_merge"] + ].copy() + raise ValueError( + "position_table contains mismatched coordinates between contrast sides. " + f"Mismatched rows: {mismatched_rows.to_dict(orient='records')}." + ) + + paired = numerator.merge( + denominator, + on=join_keys, + suffixes=("_numerator", "_denominator"), + how="inner", + ) + if paired.empty: + raise ValueError( + "Unable to compute delta because numerator and denominator positions do not align." + ) + + if "rank" in result.summary.columns: + _require_columns(result.summary, ("region_id", "rank"), "result.summary") + rank_table = result.summary.loc[:, ["region_id", "rank"]].drop_duplicates( + subset=["region_id"] + ) + else: + rank_table = None + + def _attach_rank(table: pd.DataFrame) -> pd.DataFrame: + if rank_table is None: + return table + return table.merge(rank_table, on="region_id", how="left") + + numerator_table = numerator.loc[ + :, ["region_id", "position", "anchor", "value", "region_strand"] + ].copy() + numerator_table["value_mode"] = "numerator" + numerator_table[grouping_key] = "numerator" + numerator_table = _attach_rank(numerator_table) + + denominator_table = denominator.loc[ + :, ["region_id", "position", "anchor", "value", "region_strand"] + ].copy() + denominator_table["value_mode"] = "denominator" + denominator_table[grouping_key] = "denominator" + denominator_table = _attach_rank(denominator_table) + + delta_table = paired.loc[ + :, + [ + "region_id", + "position", + "anchor", + "region_strand", + "value_numerator", + "value_denominator", + ], + ].copy() + delta_table["value"] = ( + delta_table["value_numerator"] - delta_table["value_denominator"] + ) + delta_table[grouping_key] = "delta" + delta_table["value_mode"] = "delta" + delta_table = delta_table.loc[ + :, + [ + "region_id", + grouping_key, + "position", + "anchor", + "value", + "region_strand", + "value_mode", + ], + ] + delta_table = _attach_rank(delta_table) + + ordered_columns = [ + "region_id", + grouping_key, + "position", + "anchor", + "value", + "region_strand", + "value_mode", + ] + if rank_table is not None: + ordered_columns.append("rank") + + return pd.concat( + [ + numerator_table.loc[:, ordered_columns], + denominator_table.loc[:, ordered_columns], + delta_table.loc[:, ordered_columns], + ], + ignore_index=True, + ) + + +def prepare_region_contrast_profile_data( + *, + result, + position_table: pd.DataFrame, + axis: AxisSpec, + aggregation: AggregationSpec, + value_mode: str = "all", +) -> dict[str, pd.DataFrame | dict[str, object]]: + validate_axis_spec(axis, plot_family="region_contrast_profile") + validate_aggregation_spec(aggregation) + + grouping_key = _region_contrast_grouping_key(result, position_table) + prepared_position_table = _prepare_region_contrast_position_table( + result=result, + position_table=position_table, + grouping_key=grouping_key, + ) + contrast_table = _prepare_region_contrast_value_modes( + result=result, + position_table=prepared_position_table, + grouping_key=grouping_key, + ) + + if value_mode != "all": + if value_mode not in {"numerator", "denominator", "delta"}: + raise ValueError("Unsupported region contrast value_mode.") + contrast_table = contrast_table.loc[ + contrast_table["value_mode"] == value_mode + ].copy() + + prepared = prepare_aggregate_plot_data( + contrast_table, + plot_family="region_contrast_profile", + axis=axis, + aggregation=aggregation, + value_column="value", + position_column="position", + anchor_column="anchor", + region_strand_column="region_strand", + ) + metadata = dict(prepared["metadata"]) + metadata.update(_region_contrast_metadata(result)) + metadata["value_mode"] = value_mode + prepared["metadata"] = metadata + return prepared + + +def prepare_region_contrast_heatmap_data( + *, + result, + position_table: pd.DataFrame, + axis: AxisSpec, + aggregation: AggregationSpec, + value_mode: str = "all", +) -> dict[str, pd.DataFrame | dict[str, object]]: + profile_payload = prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode=value_mode, + ) + + _require_columns(result.summary, ("region_id", "rank"), "result.summary") + plot_region_ids = ( + profile_payload["plot_table"] + .loc[:, ["region_id"]] + .drop_duplicates() + .reset_index(drop=True) + ) + summary_ranks = plot_region_ids.merge( + result.summary.loc[:, ["region_id", "rank"]], + on="region_id", + how="left", + ) + summary_ranks = summary_ranks.groupby("region_id", as_index=False, sort=False).agg( + rank=( + "rank", + lambda values: ( + lambda non_na: non_na.iloc[0] if not non_na.empty else pd.NA + )(pd.Series(values).dropna()), + ), + rank_count=("rank", lambda values: pd.Series(values).dropna().nunique()), + ) + conflicting_rank_ids = sorted( + summary_ranks.loc[summary_ranks["rank_count"] > 1, "region_id"] + .astype(str) + .unique() + ) + if conflicting_rank_ids: + raise ValueError( + "result.summary must provide exactly one rank value per plotted region. " + f"Conflicting region_id values: {', '.join(conflicting_rank_ids)}." + ) + + row_order = summary_ranks.loc[:, ["region_id", "rank"]].copy() + missing_rank_ids = sorted( + summary_ranks.loc[summary_ranks["rank_count"] == 0, "region_id"] + .astype(str) + .unique() + ) + if missing_rank_ids: + raise ValueError( + "result.summary does not provide rank values for all plotted regions. " + f"Missing region_id values: {', '.join(missing_rank_ids)}." + ) + row_order = row_order.sort_values(["rank", "region_id"], kind="stable").reset_index( + drop=True + ) + row_order["row_order"] = range(len(row_order)) + + plot_table = profile_payload["plot_table"].merge( + row_order.loc[:, ["region_id", "row_order"]], + on="region_id", + how="left", + ) + plot_table = plot_table.sort_values( + ["row_order", "value_mode", "position"], kind="stable" + ).reset_index(drop=True) + + profile_payload["plot_table"] = plot_table + profile_payload["metadata"] = { + **profile_payload["metadata"], + "plot_family": "region_contrast_heatmap", + } + profile_payload["summary_table"] = row_order + return profile_payload + + +def prepare_global_analysis_summary_data( + *, + result, + motifs: list[str] | None = None, + aggregate_conditions: bool = True, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_global_analysis_result(result) + _require_columns( + result.summary, + ( + "sample_id", + "condition", + "motif", + "modified_count", + "valid_count", + "global_fraction", + ), + "result.summary", + ) + _require_columns( + result.normalization_factors, + ( + "sample_id", + "condition", + "motif", + "global_fraction", + "reference_fraction", + "global_offset", + ), + "result.normalization_factors", + ) + + sample_summary = _filter_motif_table(result.summary, motifs, owner="result.summary") + normalization_table = _filter_motif_table( + result.normalization_factors, + motifs, + owner="result.normalization_factors", + ) + + if sample_summary.empty: + condition_summary = pd.DataFrame( + columns=[ + "condition", + "motif", + "global_fraction_mean", + "global_fraction_median", + "sample_n", + ] + ) + motif_values = list(motifs) if motifs is not None else [] + else: + motif_values = sample_summary["motif"].drop_duplicates().tolist() + if aggregate_conditions: + condition_summary = ( + sample_summary.groupby( + ["condition", "motif"], as_index=False, sort=False + ) + .agg( + global_fraction_mean=("global_fraction", "mean"), + global_fraction_median=("global_fraction", "median"), + sample_n=("sample_id", "nunique"), + ) + .copy() + ) + else: + condition_summary = pd.DataFrame( + columns=[ + "condition", + "motif", + "global_fraction_mean", + "global_fraction_median", + "sample_n", + ] + ) + + return { + "sample_summary": sample_summary.reset_index(drop=True), + "condition_summary": condition_summary.reset_index(drop=True), + "normalization_table": normalization_table.reset_index(drop=True), + "metadata": { + "motifs": motif_values, + "window_size": result.metadata.get("window_size"), + "step_size": result.metadata.get("step_size"), + "aggregate_conditions": aggregate_conditions, + }, + } + + +def prepare_global_analysis_window_data( + *, + result, + contigs: list[str] | None = None, + motifs: list[str] | None = None, + aggregate_conditions: bool = False, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_global_analysis_result(result) + + condition_window_columns = [ + "condition", + "motif", + "contig", + "start", + "end", + "window_midpoint", + "window_fraction_mean", + "window_fraction_median", + "sample_n", + ] + empty_window_columns = [ + "sample_id", + "condition", + "replicate", + "motif", + "window_id", + "contig", + "start", + "end", + "strand", + "window_fraction", + "window_midpoint", + ] + + if result.windows is None: + return { + "window_table": pd.DataFrame(columns=empty_window_columns), + "condition_window_table": pd.DataFrame(columns=condition_window_columns), + "metadata": { + "contig_order": [], + "motifs": [] if motifs is None else list(motifs), + "aggregate_conditions": aggregate_conditions, + }, + } + + _require_columns( + result.windows, + ( + "sample_id", + "condition", + "replicate", + "motif", + "window_id", + "start", + "end", + "strand", + "window_fraction", + ), + "result.windows", + ) + window_table = _filter_motif_table(result.windows, motifs, owner="result.windows") + + if window_table.empty: + return { + "window_table": pd.DataFrame(columns=empty_window_columns), + "condition_window_table": pd.DataFrame(columns=condition_window_columns), + "metadata": { + "contig_order": [], + "motifs": [] if motifs is None else list(motifs), + "aggregate_conditions": aggregate_conditions, + }, + } + + window_table = window_table.copy() + windows_contig_column = _discovery_contig_column( + window_table, owner="result.windows" + ) + window_table["contig"] = window_table[windows_contig_column] + + if contigs is not None: + window_table = window_table.loc[window_table["contig"].isin(contigs)].copy() + if window_table.empty: + raise ValueError("Requested contigs are not present in result.windows.") + contig_order = list(contigs) + else: + contig_order = window_table["contig"].drop_duplicates().tolist() + + window_table["window_midpoint"] = ( + window_table["start"] + window_table["end"] + ) / 2.0 + window_table = _sort_discovery_table( + window_table, + contig_order=contig_order, + sort_columns=["start", "end", "sample_id"], + ) + + if aggregate_conditions: + sample_window_table = ( + window_table.groupby( + [ + "sample_id", + "condition", + "motif", + "window_id", + "contig", + "start", + "end", + "window_midpoint", + ], + as_index=False, + sort=False, + ) + .agg(window_fraction=("window_fraction", "mean")) + .copy() + ) + condition_window_table = ( + sample_window_table.groupby( + ["condition", "motif", "contig", "start", "end", "window_midpoint"], + as_index=False, + sort=False, + ) + .agg( + window_fraction_mean=("window_fraction", "mean"), + window_fraction_median=("window_fraction", "median"), + sample_n=("sample_id", "nunique"), + ) + .copy() + ) + condition_window_table = _sort_discovery_table( + condition_window_table, + contig_order=contig_order, + sort_columns=["start", "end", "condition"], + ) + else: + condition_window_table = pd.DataFrame(columns=condition_window_columns) + + return { + "window_table": window_table.reset_index(drop=True), + "condition_window_table": condition_window_table.reset_index(drop=True), + "metadata": { + "contig_order": contig_order, + "motifs": window_table["motif"].drop_duplicates().tolist(), + "aggregate_conditions": aggregate_conditions, + }, + } + + +def prepare_region_discovery_scan_data( + *, + result, + contigs: list[str] | None = None, + top_n_hits: int | None = 100, + score_column: str | None = None, + include_all_windows: bool = True, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_region_discovery_result(result) + if top_n_hits is not None and top_n_hits < 0: + raise ValueError("top_n_hits must be non-negative.") + + windows = result.windows.copy() + hits = result.hits.copy() + active_score_column = _select_discovery_score_column(windows, score_column) + + if windows.empty: + empty_scan = _empty_discovery_scan_table(active_score_column) + empty_hits = pd.DataFrame( + columns=[ + "window_id", + "contig", + "start", + "end", + "strand", + active_score_column, + ] + ) + return { + "scan_table": empty_scan, + "hit_table": empty_hits, + "metadata": { + "contig_order": [], + "score_column": active_score_column, + "score_mode": result.metadata.get("score"), + "contrast_mode": result.metadata.get("contrast_mode"), + "merge_hits": result.metadata.get("merge_hits"), + "top_n_hits": top_n_hits, + }, + } + + _require_columns(windows, ("window_id", "start", "end", "strand"), "result.windows") + windows_contig_column = _discovery_contig_column(windows, owner="result.windows") + windows["contig"] = windows[windows_contig_column] + + if hits.empty: + hits = pd.DataFrame(columns=windows.columns) + else: + _require_columns(hits, ("window_id", "start", "end", "strand"), "result.hits") + hits_contig_column = _discovery_contig_column(hits, owner="result.hits") + hits["contig"] = hits[hits_contig_column] + + if contigs is not None: + windows = windows.loc[windows["contig"].isin(contigs)].copy() + hits = hits.loc[hits["contig"].isin(contigs)].copy() + if windows.empty: + raise ValueError( + "Requested contigs are not present in RegionDiscoveryResult.windows." + ) + contig_order = list(contigs) + else: + contig_order = windows["contig"].drop_duplicates().tolist() + + if top_n_hits is not None and not hits.empty and "rank" in hits.columns: + hits = hits.sort_values(["rank"], kind="stable").head(top_n_hits).copy() + + filtered_window_ids = set(windows["window_id"].tolist()) + missing_hit_window_ids = sorted( + str(window_id) + for window_id in hits["window_id"].dropna().unique() + if window_id not in filtered_window_ids + ) + if missing_hit_window_ids: + raise ValueError( + "result.hits contains window_id values not present in the filtered windows table: " + f"{', '.join(missing_hit_window_ids)}." + ) + + hit_window_ids = set(hits.get("window_id", pd.Series(dtype="object")).tolist()) + windows["window_midpoint"] = (windows["start"] + windows["end"]) / 2.0 + windows["is_hit"] = windows["window_id"].isin(hit_window_ids) + + scan_table = ( + windows if include_all_windows else windows.loc[windows["is_hit"]].copy() + ) + scan_table = _sort_discovery_table( + scan_table, + contig_order=contig_order, + sort_columns=["start", "end"], + ) + + hit_sort_columns = ["start", "end"] + if "rank" in hits.columns: + hit_sort_columns = ["rank", *hit_sort_columns] + hit_table = _sort_discovery_table( + hits, + contig_order=contig_order, + sort_columns=hit_sort_columns, + ) + + return { + "scan_table": scan_table, + "hit_table": hit_table, + "metadata": { + "contig_order": contig_order, + "score_column": active_score_column, + "score_mode": result.metadata.get("score"), + "contrast_mode": result.metadata.get("contrast_mode"), + "merge_hits": result.metadata.get("merge_hits"), + "top_n_hits": top_n_hits, + }, + } + + +def prepare_region_discovery_hit_context_data( + *, + result, + top_n: int | None = 12, + hit_ids: list[str] | None = None, + padding_windows: int | None = 5, + padding_bp: int | None = None, + score_column: str | None = None, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_region_discovery_result(result) + if padding_bp is not None: + raise ValueError( + "padding_bp is not supported for region discovery hit context prep." + ) + if padding_windows is not None and padding_windows < 0: + raise ValueError("padding_windows must be non-negative.") + + windows = result.windows.copy() + hits = result.hits.copy() + active_score_column = _select_discovery_score_column(windows, score_column) + + context_columns = [ + "window_id", + "contig", + "start", + "end", + "strand", + active_score_column, + "window_midpoint", + "selected_hit_id", + "selected_hit_rank", + "relative_window_offset", + "is_selected_hit", + ] + selection_mode = "top_n" if hit_ids is None else "explicit_ids" + + if hits.empty or windows.empty: + return { + "context_table": pd.DataFrame(columns=context_columns), + "selected_hits": hits.copy().reset_index(drop=True), + "metadata": { + "selection_mode": selection_mode, + "top_n": top_n, + "padding_windows": padding_windows, + "padding_bp": padding_bp, + "score_column": active_score_column, + }, + } + + _require_columns(windows, ("window_id", "start", "end", "strand"), "result.windows") + _require_columns(hits, ("window_id", "start", "end", "strand"), "result.hits") + windows_contig_column = _discovery_contig_column(windows, owner="result.windows") + hits_contig_column = _discovery_contig_column(hits, owner="result.hits") + windows["contig"] = windows[windows_contig_column] + hits["contig"] = hits[hits_contig_column] + windows["window_midpoint"] = (windows["start"] + windows["end"]) / 2.0 + + contig_order = windows["contig"].drop_duplicates().tolist() + windows = _sort_discovery_table( + windows, + contig_order=contig_order, + sort_columns=["start", "end"], + ) + + if hit_ids is not None: + selected_hits = ( + hits.loc[hits["window_id"].isin(hit_ids)].copy().reset_index(drop=True) + ) + else: + selected_hits, selection_mode = _select_region_discovery_hits(hits, top_n=top_n) + + if selected_hits.empty: + return { + "context_table": pd.DataFrame(columns=context_columns), + "selected_hits": selected_hits, + "metadata": { + "selection_mode": selection_mode, + "top_n": top_n, + "padding_windows": padding_windows, + "padding_bp": padding_bp, + "score_column": active_score_column, + }, + } + + available_window_ids = set(windows["window_id"].dropna().tolist()) + missing_selected_hit_ids = sorted( + str(window_id) + for window_id in selected_hits["window_id"].dropna().unique() + if window_id not in available_window_ids + ) + if missing_selected_hit_ids: + raise ValueError( + "selected hits contain window_id values not present in result.windows: " + f"{', '.join(missing_selected_hit_ids)}." + ) + + windows_by_contig: dict[str, pd.DataFrame] = {} + window_position_by_contig: dict[str, dict[str, int]] = {} + for contig, contig_windows in windows.groupby("contig", sort=False): + contig_table = contig_windows.reset_index(drop=True) + windows_by_contig[contig] = contig_table + window_position_by_contig[contig] = dict( + zip(contig_table["window_id"], contig_table.index, strict=False) + ) + + context_frames: list[pd.DataFrame] = [] + padding = int(padding_windows or 0) + has_rank = "rank" in selected_hits.columns + + for hit in selected_hits.itertuples(index=False): + hit_contig = hit.contig + hit_window_id = hit.window_id + contig_windows = windows_by_contig.get(hit_contig) + if contig_windows is None: + continue + + hit_position = window_position_by_contig[hit_contig].get(hit_window_id) + if hit_position is None: + continue + + start_index = max(0, hit_position - padding) + end_index = min(len(contig_windows), hit_position + padding + 1) + context = contig_windows.iloc[start_index:end_index].copy() + context["selected_hit_id"] = hit_window_id + context["selected_hit_rank"] = hit.rank if has_rank else np.nan + context["relative_window_offset"] = np.arange( + start_index - hit_position, end_index - hit_position + ) + context["is_selected_hit"] = context["window_id"] == hit_window_id + context_frames.append(context) + + if context_frames: + context_table = pd.concat(context_frames, ignore_index=True) + else: + context_table = pd.DataFrame(columns=context_columns) + + return { + "context_table": context_table, + "selected_hits": selected_hits, + "metadata": { + "selection_mode": selection_mode, + "top_n": top_n, + "padding_windows": padding_windows, + "padding_bp": padding_bp, + "score_column": active_score_column, + }, + } + + +def prepare_single_read_plot_data( + table: pd.DataFrame, + *, + plot_family: str, + axis: AxisSpec, + position_column: str, + anchor_column: str, + region_strand_column: str | None = None, +) -> dict[str, pd.DataFrame | dict[str, object]]: + validate_axis_spec(axis, plot_family=plot_family) + plot_table, axis_table = _prepare_fixed_window_plot_data( + table, + axis=axis, + position_column=position_column, + anchor_column=anchor_column, + region_strand_column=region_strand_column, + ) + metadata = { + "plot_family": plot_family, + "orientation": axis.orientation, + "coordinate_mode": axis.coordinate_mode, + } + return {"plot_table": plot_table, "axis_table": axis_table, "metadata": metadata} + + +def prepare_aggregate_plot_data( + table: pd.DataFrame, + *, + plot_family: str, + axis: AxisSpec, + aggregation: AggregationSpec, + value_column: str, + position_column: str | None = None, + anchor_column: str | None = None, + region_strand_column: str | None = None, + segment_id_column: str | None = None, + segment_position_column: str | None = None, +) -> dict[str, pd.DataFrame | dict[str, object]]: + validate_axis_spec(axis, plot_family=plot_family) + validate_aggregation_spec(aggregation) + _require_columns(table, (value_column,), "plot_table") + + if axis.coordinate_mode == "segment_map": + if segment_id_column is None or segment_position_column is None: + raise ValueError( + "segment_map plotting requires segment_id_column and segment_position_column." + ) + plot_table, axis_table = _prepare_segment_map_plot_data( + table, + axis=axis, + segment_id_column=segment_id_column, + segment_position_column=segment_position_column, + ) + else: + if position_column is None or anchor_column is None: + raise ValueError( + "fixed_window plotting requires position_column and anchor_column." + ) + plot_table, axis_table = _prepare_fixed_window_plot_data( + table, + axis=axis, + position_column=position_column, + anchor_column=anchor_column, + region_strand_column=region_strand_column, + ) + + metadata = { + "plot_family": plot_family, + "orientation": axis.orientation, + "coordinate_mode": axis.coordinate_mode, + "weighting": aggregation.weighting, + "within_region_summary": aggregation.within_region_summary, + "signal_normalization": aggregation.signal_normalization, + "layout": aggregation.layout, + } + if axis.coordinate_mode == "segment_map": + metadata["segment_count"] = len(axis_table) + return {"plot_table": plot_table, "axis_table": axis_table, "metadata": metadata} + + +def prepare_cluster_distribution_bar_data( + cluster_distribution: pd.DataFrame, +) -> pd.DataFrame: + _require_columns( + cluster_distribution, + ("sample_id", "condition", "cluster", "count", "fraction"), + "cluster_distribution", + ) + if cluster_distribution.empty: + return cluster_distribution.loc[ + :, ["sample_id", "condition", "cluster", "count", "fraction"] + ].copy() + + return ( + cluster_distribution.loc[ + :, ["sample_id", "condition", "cluster", "count", "fraction"] + ] + .sort_values(["sample_id", "condition", "cluster"], kind="stable") + .reset_index(drop=True) + ) + + +def prepare_cluster_distribution_heatmap_data( + condition_distribution: pd.DataFrame, +) -> pd.DataFrame: + _require_columns( + condition_distribution, + ("condition", "cluster", "fraction"), + "condition_distribution", + ) + if condition_distribution.empty: + return pd.DataFrame(columns=["condition"]) + + heatmap = ( + condition_distribution.pivot_table( + index="condition", + columns="cluster", + values="fraction", + aggfunc="sum", + fill_value=0.0, + ) + .sort_index(axis=0) + .reindex( + sorted(condition_distribution["cluster"].unique()), axis=1, fill_value=0.0 + ) + .reset_index() + ) + heatmap.columns.name = None + return heatmap + + +def prepare_shared_cluster_distribution_data( + *, + result, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_shared_cluster_result(result) + + sample_distribution = prepare_cluster_distribution_bar_data( + result.cluster_distribution + ) + + _require_columns( + result.condition_distribution, + ("condition", "cluster", "count", "fraction", "replicate_n"), + "result.condition_distribution", + ) + condition_distribution = ( + result.condition_distribution.loc[ + :, ["condition", "cluster", "count", "fraction", "replicate_n"] + ] + .sort_values(["condition", "cluster"], kind="stable") + .reset_index(drop=True) + ) + + if result.distribution_change is None: + distribution_change = _empty_distribution_change_table() + else: + _require_columns( + result.distribution_change, + ( + "condition", + "cluster", + "count", + "fraction", + "replicate_n", + "reference_fraction", + "delta_fraction", + "log2_fc", + ), + "result.distribution_change", + ) + distribution_change = ( + result.distribution_change.loc[ + :, + [ + "condition", + "cluster", + "count", + "fraction", + "replicate_n", + "reference_fraction", + "delta_fraction", + "log2_fc", + ], + ] + .sort_values(["condition", "cluster"], kind="stable") + .reset_index(drop=True) + ) + + metadata = { + "mode": result.model.mode, + "cluster_labels": list(result.model.cluster_labels), + "has_distribution_change": not distribution_change.empty, + "change_condition_order": distribution_change["condition"] + .drop_duplicates() + .tolist(), + } + return { + "sample_distribution": sample_distribution, + "condition_distribution": condition_distribution, + "distribution_change": distribution_change, + "metadata": metadata, + } + + +def prepare_shared_cluster_profile_data( + *, + result, + features: list[str] | None = None, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_shared_cluster_result(result) + if not hasattr(result.model, "feature_names"): + raise TypeError("plotting helpers require a SharedClusterResult-like object.") + + cluster_profiles = result.cluster_profiles.copy() + feature_names = list(result.model.feature_names) + + requested_features = feature_names + if features is not None: + missing = [feature for feature in features if feature not in feature_names] + if missing: + raise ValueError( + "Requested features are not present in cluster_profiles: " + f"{', '.join(missing)}" + ) + requested_features = [ + feature for feature in feature_names if feature in features + ] + + _require_columns( + cluster_profiles, + ("cluster", "count", *feature_names), + "result.cluster_profiles", + ) + + if cluster_profiles.empty: + return { + "profile_table": pd.DataFrame( + columns=["cluster", "feature", "value", "count"] + ), + "metadata": { + "feature_names": requested_features, + "cluster_labels": list(result.model.cluster_labels), + }, + } + + profile_table = ( + cluster_profiles.loc[:, ["cluster", "count", *requested_features]] + .melt(id_vars=["cluster", "count"], var_name="feature", value_name="value") + .sort_values(["cluster", "feature"], kind="stable") + .reset_index(drop=True) + ) + return { + "profile_table": profile_table, + "metadata": { + "feature_names": requested_features, + "cluster_labels": list(result.model.cluster_labels), + }, + } + + +def prepare_shared_cluster_region_data( + *, + result, + aggregate_conditions: bool = True, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_shared_cluster_result(result) + if result.region_summaries is None: + raise ValueError( + "SharedClusterResult.region_summaries is required for region plotting." + ) + + _require_columns( + result.region_summaries, + ("region_id", "sample_id", "condition", "cluster", "count", "fraction"), + "result.region_summaries", + ) + region_table = ( + result.region_summaries.loc[ + :, ["region_id", "sample_id", "condition", "cluster", "count", "fraction"] + ] + .sort_values(["region_id", "sample_id", "cluster"], kind="stable") + .reset_index(drop=True) + ) + + if aggregate_conditions and not region_table.empty: + condition_region_table = ( + region_table.groupby( + ["region_id", "condition", "cluster"], as_index=False, sort=False + ) + .agg( + count=("count", "sum"), + fraction_mean=("fraction", "mean"), + fraction_median=("fraction", "median"), + sample_n=("sample_id", "nunique"), + ) + .sort_values(["region_id", "condition", "cluster"], kind="stable") + .reset_index(drop=True) + ) + else: + condition_region_table = pd.DataFrame( + columns=[ + "region_id", + "condition", + "cluster", + "count", + "fraction_mean", + "fraction_median", + "sample_n", + ] + ) + + return { + "region_table": region_table, + "condition_region_table": condition_region_table, + "metadata": { + "mode": result.model.mode, + "cluster_labels": list(result.model.cluster_labels), + "has_condition_aggregation": aggregate_conditions, + }, + } + + +_READ_CLUSTER_ASSOCIATION_VALUE_MODES = {"fraction", "log2_enrichment"} +_READ_CLUSTER_ASSOCIATION_REGION_COLUMNS = ( + "region_id", + "chrom", + "chromosome", + "start", + "end", + "strand", +) +_READ_CLUSTER_ASSOCIATION_LEGACY_EXCLUDE_COLUMNS = { + "count", + "fraction", + "log2_enrichment", + "total_reads", + "dominant_cluster", + "dominant_fraction", + "entropy", + "value", + "value_mode", +} + + +def _format_region_coordinate(value) -> str: + if pd.isna(value): + raise ValueError("region coordinates must not be missing.") + if isinstance(value, (np.integer, int)): + return str(int(value)) + if isinstance(value, float) and value.is_integer(): + return str(int(value)) + return str(value) + + +def _synthesize_read_cluster_region_id(table: pd.DataFrame, *, owner: str) -> pd.Series: + if "chrom" not in table.columns and "chromosome" not in table.columns: + raise ValueError( + f"{owner} requires region_id or chrom/chromosome, start, end, and strand columns." + ) + _require_columns(table, ("start", "end", "strand"), owner) + + chrom_column = "chrom" if "chrom" in table.columns else "chromosome" + if table[[chrom_column, "start", "end", "strand"]].isna().any().any(): + raise ValueError( + f"{owner} requires region_id or chrom/chromosome, start, end, and strand columns." + ) + + return table.loc[:, [chrom_column, "start", "end", "strand"]].apply( + lambda row: ( + f"{_format_region_coordinate(row.iloc[0])}:" + f"{_format_region_coordinate(row.iloc[1])}-" + f"{_format_region_coordinate(row.iloc[2])}:" + f"{_format_region_coordinate(row.iloc[3])}" + ), + axis=1, + ) + + +def _ensure_read_cluster_region_ids(table: pd.DataFrame, *, owner: str) -> pd.DataFrame: + normalized = table.copy() + if "region_id" not in normalized.columns: + normalized["region_id"] = _synthesize_read_cluster_region_id( + normalized, owner=owner + ) + return normalized + + missing_region_ids = normalized["region_id"].isna() + if missing_region_ids.any(): + replacement_ids = _synthesize_read_cluster_region_id( + normalized.loc[missing_region_ids], owner=owner + ) + normalized.loc[missing_region_ids, "region_id"] = replacement_ids + normalized["region_id"] = normalized["region_id"].astype(str) + return normalized + + +def _normalize_long_form_read_cluster_region_association( + table: pd.DataFrame, + *, + value_mode: str, +) -> pd.DataFrame: + _require_columns(table, ("cluster",), "association_table") + if value_mode not in _READ_CLUSTER_ASSOCIATION_VALUE_MODES: + raise ValueError("Unsupported read-cluster association value_mode.") + if value_mode not in table.columns: + raise ValueError( + f"association_table does not include the requested {value_mode} column." + ) + + normalized = _ensure_read_cluster_region_ids(table, owner="association_table") + if normalized["region_id"].isna().any(): + raise ValueError( + "association_table requires region_id or chrom/chromosome, start, end, and strand columns." + ) + + normalized = normalized.copy() + normalized["value"] = normalized[value_mode] + normalized["value_mode"] = value_mode + normalized["source_format"] = "long_form" + return normalized + + +def _infer_read_cluster_columns(table: pd.DataFrame) -> list[object]: + excluded_columns = { + column + for column in _READ_CLUSTER_ASSOCIATION_REGION_COLUMNS + if column in table.columns + } + excluded_columns.update( + col + for col in _READ_CLUSTER_ASSOCIATION_LEGACY_EXCLUDE_COLUMNS + if col in table.columns + ) + cluster_columns = [ + column for column in table.columns if column not in excluded_columns + ] + if "region_id" in cluster_columns: + cluster_columns.remove("region_id") + return cluster_columns + + +def _normalize_legacy_read_cluster_region_association( + table: pd.DataFrame, + *, + value_mode: str, +) -> pd.DataFrame: + if value_mode != "fraction": + raise ValueError( + "Legacy read-cluster association tables only support value_mode='fraction'." + ) + + cluster_columns = _infer_read_cluster_columns(table) + if not cluster_columns: + raise ValueError( + "Could not infer cluster columns from the legacy association table." + ) + + normalized = _ensure_read_cluster_region_ids(table, owner="association_table") + if normalized["region_id"].isna().any(): + raise ValueError( + "association_table requires region_id or chrom/chromosome, start, end, and strand columns." + ) + + if "total_reads" not in normalized.columns: + normalized["total_reads"] = normalized.loc[:, cluster_columns].sum(axis=1) + if (normalized["total_reads"] <= 0).any(): + raise ValueError( + "Legacy association rows must have positive total_reads or cluster counts." + ) + + id_vars = [column for column in normalized.columns if column not in cluster_columns] + melted = normalized.melt( + id_vars=id_vars, + value_vars=cluster_columns, + var_name="cluster", + value_name="count", + ) + melted["fraction"] = melted["count"] / melted["total_reads"] + melted["value"] = melted["fraction"] + melted["value_mode"] = "fraction" + melted["source_format"] = "legacy_wide" + return melted + + +def _read_cluster_region_association_cluster_order( + table: pd.DataFrame, + *, + cluster_sort: str = "input", + cluster_order: Sequence[object] | None = None, +) -> list[object]: + observed = table.loc[:, "cluster"].drop_duplicates().tolist() + if cluster_order is not None: + requested = list(cluster_order) + missing = [cluster for cluster in observed if cluster not in requested] + return requested + missing + if cluster_sort == "input": + return observed + if cluster_sort == "natural": + return sorted(observed, key=lambda value: str(value)) + if cluster_sort in {"total_association", "max_association"}: + values = pd.to_numeric(table["value"], errors="coerce").fillna(0.0) + grouped = values.groupby(table["cluster"], sort=False) + score = grouped.sum() if cluster_sort == "total_association" else grouped.max() + return score.sort_values(ascending=False, kind="stable").index.tolist() + raise ValueError( + "cluster_sort must be 'input', 'natural', 'total_association', or 'max_association'." + ) + + +def _parse_region_id_components(region_id: object) -> tuple[str, int, int, str]: + text = str(region_id) + strand = "." + core = text + if "," in text: + candidate_core, candidate_strand = text.rsplit(",", 1) + if candidate_strand in {"+", "-", "."}: + core = candidate_core + strand = candidate_strand + if ":" not in core or "-" not in core: + return text, -1, -1, strand + chrom, coords = core.split(":", 1) + start_s, end_s = coords.split("-", 1) + try: + start = int(start_s) + end = int(end_s) + except ValueError: + start = -1 + end = -1 + return chrom, start, end, strand + + +def _region_coordinate_table_for_association(table: pd.DataFrame) -> pd.DataFrame: + regions = table.loc[:, ["region_id"]].drop_duplicates().copy() + has_chrom = "chrom" in table.columns or "chromosome" in table.columns + has_start_end = "start" in table.columns and "end" in table.columns + if has_chrom and has_start_end: + chrom_col = "chrom" if "chrom" in table.columns else "chromosome" + coords = ( + table.loc[ + :, + ["region_id", chrom_col, "start", "end"] + + (["strand"] if "strand" in table.columns else []), + ] + .drop_duplicates(subset=["region_id"]) + .copy() + ) + coords = coords.rename(columns={chrom_col: "chrom"}) + if "strand" not in coords.columns: + coords["strand"] = "." + coords["start"] = ( + pd.to_numeric(coords["start"], errors="coerce").fillna(-1).astype(int) + ) + coords["end"] = ( + pd.to_numeric(coords["end"], errors="coerce").fillna(-1).astype(int) + ) + return coords.loc[:, ["region_id", "chrom", "start", "end", "strand"]] + + parsed = regions["region_id"].map(_parse_region_id_components) + regions["chrom"] = parsed.map(lambda x: x[0]) + regions["start"] = parsed.map(lambda x: x[1]) + regions["end"] = parsed.map(lambda x: x[2]) + regions["strand"] = parsed.map(lambda x: x[3]) + return regions + + +def _chromosome_sort_key(chrom: object) -> tuple[int, object]: + text = str(chrom) + if text.lower().startswith("chr"): + suffix = text[3:] + if suffix.isdigit(): + return (0, int(suffix)) + if suffix in {"X", "x"}: + return (1, 23) + if suffix in {"Y", "y"}: + return (1, 24) + if suffix in {"M", "MT", "m", "mt"}: + return (1, 25) + return (2, suffix) + return (3, text) + + +def _read_cluster_region_association_region_order( + table: pd.DataFrame, + *, + region_sort: str | Sequence[str], + strength_aggregate: str, + cluster_sort: str = "input", + cluster_order: Sequence[object] | None = None, +) -> list[object]: + sort_keys = [region_sort] if isinstance(region_sort, str) else list(region_sort) + if not sort_keys: + raise ValueError("region_sort must contain at least one sort key.") + valid_keys = {"cluster_fraction", "input", "genomic", "association_strength"} + invalid = [key for key in sort_keys if key not in valid_keys] + if invalid: + raise ValueError( + "region_sort keys must be 'cluster_fraction', 'input', 'genomic', " + f"or 'association_strength'. Invalid keys: {', '.join(map(str, invalid))}." + ) + + input_rank = { + region_id: rank + for rank, region_id in enumerate(table.loc[:, "region_id"].drop_duplicates()) + } + coords = _region_coordinate_table_for_association(table) + coords = coords.copy() + coords["input_rank"] = coords["region_id"].map(input_rank).fillna(len(input_rank)) + coords["chrom_sort"] = coords["chrom"].map(_chromosome_sort_key) + + if "association_strength" in sort_keys: + grouped = table.groupby("region_id", sort=False)["value"] + strength = grouped.mean() if strength_aggregate == "mean" else grouped.max() + coords = coords.merge( + strength.rename("association_strength"), + left_on="region_id", + right_index=True, + how="left", + ) + else: + coords["association_strength"] = 0.0 + + if "cluster_fraction" in sort_keys: + # Assign each region to its dominant cluster, then rank by dominant fraction. + ordered_clusters = _read_cluster_region_association_cluster_order( + table, + cluster_sort=cluster_sort, + cluster_order=cluster_order, + ) + cluster_rank = {cluster: index for index, cluster in enumerate(ordered_clusters)} + dominant_rows = table.copy() + dominant_rows["cluster_rank"] = ( + dominant_rows["cluster"] + .map(cluster_rank) + .fillna(len(ordered_clusters)) + .astype(int) + ) + dominant_rows["value"] = pd.to_numeric( + dominant_rows["value"], errors="coerce" + ).fillna(0.0) + dominant_rows = dominant_rows.sort_values( + ["region_id", "value", "cluster_rank"], + ascending=[True, False, True], + kind="stable", + ) + dominant_rows = dominant_rows.drop_duplicates( + subset=["region_id"], keep="first" + ) + dominant_rows = dominant_rows.rename( + columns={"cluster": "dominant_cluster", "value": "dominant_sort_value"} + ).loc[ + :, ["region_id", "dominant_cluster", "dominant_sort_value", "cluster_rank"] + ] + + coords = coords.merge(dominant_rows, on="region_id", how="left") + coords["cluster_rank"] = ( + pd.to_numeric(coords["cluster_rank"], errors="coerce") + .fillna(len(ordered_clusters)) + .astype(int) + ) + coords["dominant_sort_value"] = pd.to_numeric( + coords["dominant_sort_value"], errors="coerce" + ).fillna(0.0) + else: + coords["cluster_rank"] = 0 + coords["dominant_sort_value"] = 0.0 + + sort_columns: list[str] = [] + ascending: list[bool] = [] + for key in sort_keys: + if key == "input": + sort_columns.append("input_rank") + ascending.append(True) + elif key == "genomic": + sort_columns.extend(["chrom_sort", "start", "end"]) + ascending.extend([True, True, True]) + elif key == "association_strength": + sort_columns.append("association_strength") + ascending.append(False) + elif key == "cluster_fraction": + sort_columns.extend(["cluster_rank", "dominant_sort_value"]) + ascending.extend([True, False]) + + sort_columns.append("region_id") + ascending.append(True) + coords = coords.sort_values(sort_columns, ascending=ascending, kind="stable") + return coords["region_id"].tolist() + + +def prepare_read_cluster_region_association_data( + association_table: pd.DataFrame, + *, + value_mode: str = "fraction", + top_n_regions_per_cluster: int | None = 5, + region_sort: str | Sequence[str] = "cluster_fraction", + association_strength_aggregate: str = "max", + cluster_sort: str = "input", + cluster_order: Sequence[object] | None = None, +) -> dict[str, pd.DataFrame | dict[str, object]]: + if not isinstance(association_table, pd.DataFrame): + raise TypeError("association_table must be a pandas DataFrame.") + if value_mode not in _READ_CLUSTER_ASSOCIATION_VALUE_MODES: + raise ValueError("value_mode must be 'fraction' or 'log2_enrichment'.") + if top_n_regions_per_cluster is not None and top_n_regions_per_cluster < 0: + raise ValueError("top_n_regions_per_cluster must be non-negative.") + region_sort_keys = [region_sort] if isinstance(region_sort, str) else list(region_sort) + valid_region_sort_keys = { + "cluster_fraction", + "input", + "genomic", + "association_strength", + } + if not region_sort_keys or any( + key not in valid_region_sort_keys for key in region_sort_keys + ): + raise ValueError( + "region_sort must be 'cluster_fraction', 'input', 'genomic', " + "'association_strength', or a non-empty list of those keys." + ) + if association_strength_aggregate not in {"max", "mean"}: + raise ValueError("association_strength_aggregate must be 'max' or 'mean'.") + if cluster_sort not in {"input", "natural", "total_association", "max_association"}: + raise ValueError( + "cluster_sort must be 'input', 'natural', 'total_association', or 'max_association'." + ) + + if "cluster" in association_table.columns: + normalized = _normalize_long_form_read_cluster_region_association( + association_table, + value_mode=value_mode, + ) + else: + normalized = _normalize_legacy_read_cluster_region_association( + association_table, + value_mode=value_mode, + ) + + normalized = normalized.copy() + if normalized.duplicated(["region_id", "cluster"]).any(): + raise ValueError( + "association_table contains duplicate region and cluster rows." + ) + + region_order = _read_cluster_region_association_region_order( + normalized, + region_sort=region_sort, + strength_aggregate=association_strength_aggregate, + cluster_sort=cluster_sort, + cluster_order=cluster_order, + ) + resolved_cluster_order = _read_cluster_region_association_cluster_order( + normalized, + cluster_sort=cluster_sort, + cluster_order=cluster_order, + ) + normalized["region_id"] = pd.Categorical( + normalized["region_id"], categories=region_order, ordered=True + ) + normalized["cluster"] = pd.Categorical( + normalized["cluster"], categories=resolved_cluster_order, ordered=True + ) + normalized = normalized.sort_values( + ["region_id", "cluster"], kind="stable" + ).reset_index(drop=True) + normalized["region_id"] = normalized["region_id"].astype(str) + normalized["cluster"] = normalized["cluster"].astype(object) + + matrix_table = ( + normalized.loc[:, ["region_id", "cluster", "value"]] + .pivot(index="region_id", columns="cluster", values="value") + .reindex(index=region_order, columns=resolved_cluster_order) + .reset_index() + ) + matrix_table.columns.name = None + region_axis_table = _region_coordinate_table_for_association(normalized).copy() + region_axis_table = ( + region_axis_table.set_index("region_id").reindex(region_order).reset_index() + ) + region_axis_table["axis_index"] = np.arange(len(region_axis_table)) + chromosome_blocks: list[dict[str, object]] = [] + if not region_axis_table.empty: + grouped = region_axis_table.groupby("chrom", sort=False)["axis_index"] + for chrom, values in grouped: + chromosome_blocks.append( + { + "chrom": str(chrom), + "start_index": int(values.min()), + "end_index": int(values.max()), + "n_regions": int(values.max() - values.min() + 1), + } + ) + + top_columns = [ + column + for column in [ + "region_id", + "chrom", + "chromosome", + "start", + "end", + "strand", + "cluster", + "count", + "fraction", + "log2_enrichment", + "total_reads", + "value", + ] + if column in normalized.columns + ] + if top_n_regions_per_cluster is None or top_n_regions_per_cluster == 0: + top_regions_table = normalized.loc[:, top_columns].head(0).copy() + top_regions_table["rank"] = pd.Series(dtype="int64") + else: + top_regions_table = ( + normalized.loc[:, top_columns] + .sort_values( + ["cluster", "value", "region_id"], + ascending=[True, False, True], + kind="stable", + ) + .groupby("cluster", as_index=False, sort=False, group_keys=False) + .head(top_n_regions_per_cluster) + .copy() + ) + top_regions_table["rank"] = ( + top_regions_table.groupby("cluster", sort=False).cumcount() + 1 + ) + top_regions_table = top_regions_table.sort_values( + ["cluster", "rank"], kind="stable" + ).reset_index(drop=True) + + metadata = { + "plot_family": "read_cluster_region_association_heatmap", + "value_mode": value_mode, + "source_format": normalized["source_format"].iloc[0] + if not normalized.empty + else "long_form", + "region_order": [str(value) for value in region_order], + "cluster_order": resolved_cluster_order, + "top_n_regions_per_cluster": top_n_regions_per_cluster, + "has_top_regions_table": not top_regions_table.empty, + "region_sort": region_sort if isinstance(region_sort, str) else list(region_sort), + "association_strength_aggregate": association_strength_aggregate, + "cluster_sort": cluster_sort, + "chromosome_blocks": chromosome_blocks, + } + return { + "association_table": normalized.reset_index(drop=True), + "matrix_table": matrix_table, + "region_axis_table": region_axis_table, + "top_regions_table": top_regions_table, + "metadata": metadata, + } diff --git a/dimelo/plotting_matplotlib.py b/dimelo/plotting_matplotlib.py new file mode 100644 index 0000000..9b95c40 --- /dev/null +++ b/dimelo/plotting_matplotlib.py @@ -0,0 +1,1372 @@ +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from pathlib import Path + +import numpy as np +import pandas as pd + + +def save_figure( + fig, + path, + *, + dpi: int = 300, + bbox_inches: str = "tight", + transparent: bool = False, +) -> Path: + output_path = Path(path) + output_path.parent.mkdir(parents=True, exist_ok=True) + fig.savefig( + output_path, + dpi=dpi, + bbox_inches=bbox_inches, + transparent=transparent, + ) + return output_path + + +def _require_payload_keys( + payload: Mapping[str, object], keys: tuple[str, ...], *, owner: str +) -> None: + missing = [key for key in keys if key not in payload] + if missing: + raise ValueError( + f"{owner} payload is missing required keys: {', '.join(missing)}" + ) + + +def _require_payload_table(payload: Mapping[str, object], key: str) -> pd.DataFrame: + if key not in payload: + raise ValueError(f"plot payload is missing required key: {key}") + table = payload[key] + if not isinstance(table, pd.DataFrame): + raise TypeError(f"plot payload key {key!r} must be a pandas DataFrame.") + return table + + +def _import_pyplot(): + from matplotlib import pyplot as plt + + return plt + + +def _make_axis(*, ax=None, figsize=(8, 4)): + plt = _import_pyplot() + if ax is not None: + return ax.figure, ax + fig, created_ax = plt.subplots(figsize=figsize) + return fig, created_ax + + +def _make_axes(*, axes=None, n_axes: int, figsize=(8, 4)): + plt = _import_pyplot() + n_axes = max(int(n_axes), 1) + if axes is not None: + if isinstance(axes, (list, tuple)): + normalized_axes = list(axes) + else: + try: + normalized_axes = list(axes.ravel()) + except AttributeError: + normalized_axes = [axes] + if not normalized_axes: + raise ValueError("axes must contain at least one Matplotlib axis.") + return normalized_axes[0].figure, normalized_axes + + fig, created_axes = plt.subplots( + n_axes, + 1, + figsize=(figsize[0], max(figsize[1], 2.5 * n_axes)), + squeeze=False, + ) + return fig, list(created_axes.ravel()) + + +def _ordered_cluster_labels( + metadata: Mapping[str, object], observed_clusters: list[object] +) -> list[object]: + cluster_labels = list(metadata.get("cluster_labels") or []) + if not cluster_labels: + return list(observed_clusters) + + observed_cluster_set = set(observed_clusters) + ordered_clusters = [ + cluster for cluster in cluster_labels if cluster in observed_cluster_set + ] + ordered_clusters.extend( + [cluster for cluster in observed_clusters if cluster not in cluster_labels] + ) + return ordered_clusters + + +def _ordered_unique_values(table: pd.DataFrame, column: str) -> list[object]: + return table.loc[:, column].drop_duplicates().tolist() + + +def _format_sort_value(value: object) -> str: + if isinstance(value, (list, tuple)): + return " > ".join(str(item) for item in value) + return str(value) + + +def _read_cluster_association_title( + metadata: Mapping[str, object], + *, + row_annotation_columns: Sequence[str], + group_region_labels: bool | None, + region_label_mode: str, +) -> str: + value_mode = str(metadata.get("value_mode") or "fraction").replace("_", " ") + region_sort = _format_sort_value(metadata.get("region_sort", "input")) + cluster_sort = str(metadata.get("cluster_sort", "input")) + parts = [ + f"Read-cluster association ({value_mode})", + f"regions: {region_sort}", + f"clusters: {cluster_sort}", + ] + metadata_region_sort = metadata.get("region_sort") + if metadata_region_sort == "association_strength" or ( + isinstance(metadata_region_sort, (list, tuple)) + and "association_strength" in metadata_region_sort + ): + parts.append( + f"strength: {metadata.get('association_strength_aggregate', 'max')}" + ) + if row_annotation_columns: + parts.append(f"annotated by {', '.join(row_annotation_columns)}") + if group_region_labels: + parts.append("grouped labels") + elif region_label_mode != "auto": + parts.append(f"labels: {region_label_mode}") + return " | ".join(parts) + + +def _normalize_row_annotation_columns( + *, + row_annotation_column: str | None, + row_annotation_columns: Sequence[str] | None, +) -> list[str]: + columns: list[str] = [] + if row_annotation_columns is not None: + if isinstance(row_annotation_columns, str): + raise TypeError( + "row_annotation_columns must be a sequence of column names, not a string." + ) + columns.extend(str(column) for column in row_annotation_columns) + if row_annotation_column is not None: + legacy_column = str(row_annotation_column) + if legacy_column not in columns: + columns.insert(0, legacy_column) + return list(dict.fromkeys(columns)) + + +def _row_annotation_title( + column: str, + *, + row_annotation_title: str | None, + row_annotation_titles: Mapping[str, str] | None, + n_columns: int, +) -> str: + if row_annotation_titles is not None and column in row_annotation_titles: + return str(row_annotation_titles[column]) + if n_columns == 1 and row_annotation_title is not None: + return row_annotation_title + return column + + +def _row_annotation_palette( + column: str, + *, + row_annotation_palette: Mapping[str, str] | None, + row_annotation_palettes: Mapping[str, Mapping[str, str]] | None, + n_columns: int, +) -> Mapping[str, str] | None: + if row_annotation_palettes is not None and column in row_annotation_palettes: + return row_annotation_palettes[column] + if n_columns == 1: + return row_annotation_palette + return None + + +def _prepare_region_contrast_value_mode_table( + plot_table: pd.DataFrame, + *, + value_mode: str, +) -> pd.DataFrame: + if value_mode not in {"numerator", "denominator", "delta"}: + raise ValueError("Unsupported region contrast value_mode.") + + if "value_mode" not in plot_table.columns: + return plot_table.copy() + + filtered = plot_table.loc[plot_table["value_mode"] == value_mode].copy() + if filtered.empty: + raise ValueError( + f"plot payload does not contain rows for value_mode={value_mode!r}." + ) + return filtered + + +def plot_region_contrast_profile_matplotlib( + payload: Mapping[str, object], + *, + value_mode: str = "delta", + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("plot_table", "metadata"), + owner="plot_region_contrast_profile_matplotlib", + ) + plot_table = _require_payload_table(payload, "plot_table") + metadata = ( + payload.get("metadata", {}) + if isinstance(payload.get("metadata", {}), Mapping) + else {} + ) + plot_table = _prepare_region_contrast_value_mode_table( + plot_table, value_mode=value_mode + ) + + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + if plot_table.empty: + ax.set_title(title or "Region contrast profile") + ax.set_xlabel("position") + ax.set_ylabel(value_mode.replace("_", " ").title()) + return fig, ax + + x_column = "plot_x" if "plot_x" in plot_table.columns else "position" + grouped = ( + plot_table.loc[:, [x_column, "value"]] + .groupby(x_column, as_index=False, sort=True) + .mean(numeric_only=True) + ) + ax.plot(grouped[x_column], grouped["value"], marker="o", linewidth=1.5) + + axis_table = payload.get("axis_table") + if ( + isinstance(axis_table, pd.DataFrame) + and not axis_table.empty + and {"axis_min", "axis_max"}.issubset(axis_table.columns) + ): + ax.set_xlim(axis_table["axis_min"].min(), axis_table["axis_max"].max()) + + ax.set_title(title or "Region contrast profile") + ax.set_xlabel("position" if x_column == "position" else x_column) + ax.set_ylabel(value_mode.replace("_", " ").title()) + + if metadata.get("orientation") == "region_5to3": + ax.set_xlabel("oriented position") + + return fig, ax + + +def plot_region_contrast_heatmap_matplotlib( + payload: Mapping[str, object], + *, + value_mode: str = "delta", + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("plot_table", "metadata"), + owner="plot_region_contrast_heatmap_matplotlib", + ) + plot_table = _require_payload_table(payload, "plot_table") + summary_table = payload.get("summary_table") + if summary_table is not None and not isinstance(summary_table, pd.DataFrame): + raise TypeError("plot payload key 'summary_table' must be a pandas DataFrame.") + plot_table = _prepare_region_contrast_value_mode_table( + plot_table, value_mode=value_mode + ) + + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + if plot_table.empty: + ax.set_title(title or "Region contrast heatmap") + ax.set_xlabel("position") + ax.set_ylabel("region") + return fig, ax + + x_column = "plot_x" if "plot_x" in plot_table.columns else "position" + y_column = "row_order" if "row_order" in plot_table.columns else "region_id" + + heatmap_table = plot_table.loc[:, [x_column, y_column, "value"]].copy() + heatmap = ( + heatmap_table.pivot_table( + index=y_column, + columns=x_column, + values="value", + aggfunc="mean", + ) + .sort_index(axis=0) + .sort_index(axis=1) + ) + + image = ax.imshow( + heatmap.to_numpy(), + aspect="auto", + origin="lower", + interpolation="nearest", + ) + ax.figure.colorbar(image, ax=ax, label="value") + + if y_column == "row_order": + if isinstance(summary_table, pd.DataFrame) and { + "region_id", + "row_order", + }.issubset(summary_table.columns): + summary_lookup = ( + summary_table.loc[:, ["region_id", "row_order"]] + .drop_duplicates() + .sort_values("row_order", kind="stable") + ) + elif "region_id" in plot_table.columns: + summary_lookup = ( + plot_table.loc[:, ["region_id", "row_order"]] + .drop_duplicates() + .sort_values("row_order", kind="stable") + ) + else: + summary_lookup = None + if summary_lookup is not None: + ax.set_yticks(range(len(summary_lookup))) + ax.set_yticklabels(summary_lookup["region_id"].astype(str).tolist()) + else: + ax.set_ylabel("region") + else: + ax.set_ylabel("region") + + ax.set_xticks(range(len(heatmap.columns))) + ax.set_xticklabels( + [str(value) for value in heatmap.columns.tolist()], rotation=45, ha="right" + ) + ax.set_title(title or "Region contrast heatmap") + ax.set_xlabel("position" if x_column == "position" else x_column) + + return fig, ax + + +def plot_region_discovery_scan_matplotlib( + payload: Mapping[str, object], + *, + axes=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("scan_table", "hit_table", "metadata"), + owner="plot_region_discovery_scan_matplotlib", + ) + scan_table = _require_payload_table(payload, "scan_table") + hit_table = _require_payload_table(payload, "hit_table") + metadata = ( + payload.get("metadata", {}) + if isinstance(payload.get("metadata", {}), Mapping) + else {} + ) + score_column = str(metadata.get("score_column") or "score_value") + contigs = list( + metadata.get("contig_order") + or scan_table.get("contig", pd.Series(dtype="object")).dropna().unique() + ) + + fig, axes = _make_axes(axes=axes, n_axes=len(contigs) or 1, figsize=(8, 3)) + + if not contigs: + axes[0].set_title(title or "Region discovery scan") + axes[0].set_xlabel("Window midpoint") + axes[0].set_ylabel(score_column.replace("_", " ").title()) + return fig, axes + + for index, contig in enumerate(contigs): + ax = axes[index] + contig_table = scan_table.loc[scan_table["contig"] == contig] + if not contig_table.empty: + ax.plot( + contig_table["window_midpoint"], + contig_table[score_column], + marker="o", + linewidth=1.5, + ) + contig_hits = ( + hit_table.loc[hit_table["contig"] == contig] + if not hit_table.empty + else hit_table + ) + if not contig_hits.empty and {"start", "end"}.issubset(contig_hits.columns): + for _, hit in contig_hits.iterrows(): + ax.axvspan( + float(hit["start"]), float(hit["end"]), color="tab:red", alpha=0.15 + ) + ax.set_title(str(contig)) + ax.set_xlabel("Window midpoint") + ax.set_ylabel(score_column.replace("_", " ").title()) + + for ax in axes[len(contigs) :]: + ax.set_visible(False) + + if title: + fig.suptitle(title) + return fig, axes + + +def plot_region_discovery_hit_context_matplotlib( + payload: Mapping[str, object], + *, + axes=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("context_table", "selected_hits", "metadata"), + owner="plot_region_discovery_hit_context_matplotlib", + ) + context_table = _require_payload_table(payload, "context_table") + selected_hits = _require_payload_table(payload, "selected_hits") + metadata = ( + payload.get("metadata", {}) + if isinstance(payload.get("metadata", {}), Mapping) + else {} + ) + score_column = str(metadata.get("score_column") or "score_value") + hit_ids = list( + selected_hits.get("window_id", pd.Series(dtype="object")) + .dropna() + .astype(str) + .tolist() + ) + + fig, axes = _make_axes(axes=axes, n_axes=len(hit_ids) or 1, figsize=(8, 3)) + + if not hit_ids: + axes[0].set_title(title or "Region discovery hit context") + axes[0].set_xlabel("Window midpoint") + axes[0].set_ylabel(score_column.replace("_", " ").title()) + return fig, axes + + for index, hit_id in enumerate(hit_ids): + ax = axes[index] + hit_context = context_table.loc[ + context_table["selected_hit_id"].astype(str) == hit_id + ] + if not hit_context.empty: + ax.plot( + hit_context["window_midpoint"], + hit_context[score_column], + marker="o", + linewidth=1.5, + ) + selected_rows = hit_context.loc[hit_context["is_selected_hit"]] + if not selected_rows.empty: + ax.scatter( + selected_rows["window_midpoint"], + selected_rows[score_column], + color="tab:red", + zorder=3, + ) + ax.set_title(hit_id) + ax.set_xlabel("Window midpoint") + ax.set_ylabel(score_column.replace("_", " ").title()) + + for ax in axes[len(hit_ids) :]: + ax.set_visible(False) + + if title: + fig.suptitle(title) + return fig, axes + + +def plot_shared_cluster_distribution_matplotlib( + payload: Mapping[str, object], + *, + level: str = "condition", + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, ("metadata",), owner="plot_shared_cluster_distribution_matplotlib" + ) + if level not in {"sample", "condition"}: + raise ValueError("level must be 'sample' or 'condition'.") + + table = _require_payload_table( + payload, + "sample_distribution" if level == "sample" else "condition_distribution", + ) + metadata = payload.get("metadata") + if not isinstance(metadata, Mapping): + raise TypeError("plot payload key 'metadata' must be a mapping.") + + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + if not table.empty: + x_column = "sample_id" if level == "sample" else "condition" + value_table = table.loc[:, [x_column, "cluster", "fraction"]].copy() + if value_table.duplicated([x_column, "cluster"]).any(): + raise ValueError( + "plot payload contains duplicate cluster fractions for the same x value." + ) + x_order = _ordered_unique_values(value_table, x_column) + cluster_order = _ordered_cluster_labels( + metadata, _ordered_unique_values(value_table, "cluster") + ) + pivot = value_table.set_index([x_column, "cluster"])["fraction"].unstack( + "cluster", fill_value=0.0 + ) + pivot = pivot.reindex(index=x_order, columns=cluster_order, fill_value=0.0) + pivot.plot(kind="bar", stacked=True, ax=ax) + ax.tick_params(axis="x", rotation=45) + + ax.set_xlabel("Sample" if level == "sample" else "Condition") + ax.set_ylabel("Fraction") + ax.set_title(title or "Shared cluster distribution") + return fig, ax + + +def plot_shared_cluster_change_matplotlib( + payload: Mapping[str, object], + *, + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, ("metadata",), owner="plot_shared_cluster_change_matplotlib" + ) + change_table = _require_payload_table(payload, "distribution_change") + metadata = payload.get("metadata") + if not isinstance(metadata, Mapping): + raise TypeError("plot payload key 'metadata' must be a mapping.") + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + if not change_table.empty: + value_table = change_table.loc[ + :, ["condition", "cluster", "delta_fraction"] + ].copy() + if value_table.duplicated(["condition", "cluster"]).any(): + raise ValueError( + "plot payload contains duplicate delta fractions for the same condition." + ) + condition_order = list(metadata.get("change_condition_order") or []) + if not condition_order: + condition_order = _ordered_unique_values(value_table, "condition") + observed_clusters = _ordered_unique_values(value_table, "cluster") + cluster_order = list(metadata.get("cluster_labels") or []) + if cluster_order: + cluster_order.extend( + [ + cluster + for cluster in observed_clusters + if cluster not in cluster_order + ] + ) + else: + cluster_order = observed_clusters + matrix = value_table.set_index(["condition", "cluster"])[ + "delta_fraction" + ].unstack("cluster") + matrix = matrix.reindex(index=condition_order, columns=cluster_order) + finite_values = pd.Series(matrix.abs().to_numpy().ravel()).dropna() + max_abs = float(finite_values.max()) if not finite_values.empty else 0.0 + if max_abs == 0.0: + max_abs = 1.0 + image = ax.imshow( + matrix.to_numpy(), + aspect="auto", + origin="upper", + interpolation="nearest", + cmap="coolwarm", + vmin=-max_abs, + vmax=max_abs, + ) + ax.figure.colorbar(image, ax=ax, label="Delta Fraction") + ax.set_xticks(range(len(matrix.columns))) + ax.set_xticklabels( + [str(value) for value in matrix.columns.tolist()], rotation=45, ha="right" + ) + ax.set_yticks(range(len(matrix.index))) + ax.set_yticklabels([str(value) for value in matrix.index.tolist()]) + + ax.set_xlabel("Cluster") + ax.set_ylabel("Condition") + ax.set_title(title or "Shared cluster change") + return fig, ax + + +def plot_shared_cluster_profile_heatmap_matplotlib( + payload: Mapping[str, object], + *, + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("profile_table", "metadata"), + owner="plot_shared_cluster_profile_heatmap_matplotlib", + ) + profile_table = _require_payload_table(payload, "profile_table") + metadata = payload.get("metadata") + if not isinstance(metadata, Mapping): + raise TypeError("plot payload key 'metadata' must be a mapping.") + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + value_table = profile_table.loc[:, ["cluster", "feature", "value"]].copy() + if value_table.duplicated(["cluster", "feature"]).any(): + raise ValueError( + "plot payload contains duplicate profile values for the same cluster and feature." + ) + + feature_order = list(metadata.get("feature_names") or []) + observed_features = _ordered_unique_values(value_table, "feature") + if feature_order: + feature_order.extend( + [feature for feature in observed_features if feature not in feature_order] + ) + else: + feature_order = observed_features + + observed_clusters = _ordered_unique_values(value_table, "cluster") + cluster_order = list(metadata.get("cluster_labels") or []) + if cluster_order: + cluster_order.extend( + [cluster for cluster in observed_clusters if cluster not in cluster_order] + ) + else: + cluster_order = observed_clusters + + if feature_order: + ax.set_xticks(range(len(feature_order))) + ax.set_xticklabels( + [str(value) for value in feature_order], rotation=45, ha="right" + ) + if cluster_order: + ax.set_yticks(range(len(cluster_order))) + ax.set_yticklabels([str(value) for value in cluster_order]) + + if feature_order and cluster_order: + if value_table.empty: + matrix = pd.DataFrame( + index=cluster_order, columns=feature_order, dtype=float + ) + else: + matrix = value_table.set_index(["cluster", "feature"])["value"].unstack( + "feature" + ) + matrix = matrix.reindex(index=cluster_order, columns=feature_order) + if matrix.notna().any().any(): + image = ax.imshow( + matrix.to_numpy(), + aspect="auto", + origin="upper", + interpolation="nearest", + ) + ax.figure.colorbar(image, ax=ax, label="Value") + ax.set_xticks(range(len(matrix.columns))) + ax.set_xticklabels( + [str(value) for value in matrix.columns.tolist()], + rotation=45, + ha="right", + ) + ax.set_yticks(range(len(matrix.index))) + ax.set_yticklabels([str(value) for value in matrix.index.tolist()]) + + ax.set_xlabel("Feature") + ax.set_ylabel("Cluster") + ax.set_title(title or "Shared cluster profiles") + return fig, ax + + +def plot_shared_cluster_profile_series_matplotlib( + payload: Mapping[str, object], + *, + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("profile_table", "metadata"), + owner="plot_shared_cluster_profile_series_matplotlib", + ) + profile_table = _require_payload_table(payload, "profile_table") + metadata = payload.get("metadata") + if not isinstance(metadata, Mapping): + raise TypeError("plot payload key 'metadata' must be a mapping.") + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + value_table = profile_table.loc[:, ["cluster", "feature", "value"]].copy() + if value_table.duplicated(["cluster", "feature"]).any(): + raise ValueError( + "plot payload contains duplicate profile values for the same cluster and feature." + ) + + feature_order = list(metadata.get("feature_names") or []) + observed_features = _ordered_unique_values(value_table, "feature") + if feature_order: + feature_order.extend( + [feature for feature in observed_features if feature not in feature_order] + ) + else: + feature_order = observed_features + feature_lookup = {feature: index for index, feature in enumerate(feature_order)} + + observed_clusters = _ordered_unique_values(value_table, "cluster") + cluster_order = list(metadata.get("cluster_labels") or []) + if cluster_order: + cluster_order.extend( + [cluster for cluster in observed_clusters if cluster not in cluster_order] + ) + else: + cluster_order = observed_clusters + + for cluster in cluster_order: + cluster_table = value_table.loc[ + value_table["cluster"] == cluster, ["feature", "value"] + ].copy() + if feature_order: + cluster_table["feature_order"] = cluster_table["feature"].map( + feature_lookup + ) + cluster_table = cluster_table.sort_values("feature_order", kind="stable") + cluster_series = cluster_table.set_index("feature_order")["value"].reindex( + range(len(feature_order)) + ) + cluster_series = cluster_series.astype(float) + x_values = cluster_series.index.tolist() + y_values = cluster_series.to_numpy() + else: + x_values = [] + y_values = [] + ax.plot( + x_values, + y_values, + marker="o", + linewidth=1.5, + label=str(cluster), + ) + + if feature_order: + ax.set_xticks(range(len(feature_order))) + ax.set_xticklabels(feature_order, rotation=45, ha="right") + if ax.lines: + ax.legend(title="Cluster") + + ax.set_xlabel("Feature") + ax.set_ylabel("Value") + ax.set_title(title or "Shared cluster profile series") + return fig, ax + + +def plot_shared_cluster_region_matplotlib( + payload: Mapping[str, object], + *, + level: str = "condition", + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("region_table", "condition_region_table", "metadata"), + owner="plot_shared_cluster_region_matplotlib", + ) + if level not in {"sample", "condition"}: + raise ValueError("level must be 'sample' or 'condition'.") + + table = _require_payload_table( + payload, + "condition_region_table" if level == "condition" else "region_table", + ) + metadata = payload.get("metadata") + if not isinstance(metadata, Mapping): + raise TypeError("plot payload key 'metadata' must be a mapping.") + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + value_column = "fraction_mean" if level == "condition" else "fraction" + label_column = "condition" if level == "condition" else "sample_id" + value_table = table.loc[ + :, ["region_id", label_column, "cluster", value_column] + ].copy() + value_table["row_id"] = ( + value_table.loc[:, ["region_id", label_column]] + .astype(str) + .agg(" | ".join, axis=1) + ) + if value_table.duplicated(["row_id", "cluster"]).any(): + raise ValueError( + "plot payload contains duplicate region occupancy values for the same row and cluster." + ) + + row_order = _ordered_unique_values(value_table, "row_id") + observed_clusters = _ordered_unique_values(value_table, "cluster") + cluster_order = list(metadata.get("cluster_labels") or []) + if cluster_order: + cluster_order.extend( + [cluster for cluster in observed_clusters if cluster not in cluster_order] + ) + else: + cluster_order = observed_clusters + + if cluster_order: + ax.set_xticks(range(len(cluster_order))) + ax.set_xticklabels( + [str(value) for value in cluster_order], rotation=45, ha="right" + ) + if row_order: + ax.set_yticks(range(len(row_order))) + ax.set_yticklabels([str(value) for value in row_order]) + + if row_order and cluster_order: + matrix = value_table.set_index(["row_id", "cluster"])[value_column].unstack( + "cluster" + ) + matrix = matrix.reindex(index=row_order, columns=cluster_order) + if matrix.notna().any().any(): + image = ax.imshow( + matrix.to_numpy(), + aspect="auto", + origin="upper", + interpolation="nearest", + ) + ax.figure.colorbar( + image, ax=ax, label=value_column.replace("_", " ").title() + ) + + ax.set_xlabel("Cluster") + ax.set_ylabel("Region / Condition" if level == "condition" else "Region / Sample") + ax.set_title(title or "Shared cluster region occupancy") + return fig, ax + + +def plot_read_cluster_region_association_heatmap_matplotlib( + payload: Mapping[str, object], + *, + ax=None, + title: str | None = None, + region_sort: str | Sequence[str] | None = None, + association_strength_aggregate: str | None = None, + cluster_sort: str | None = None, + cluster_order: Sequence[object] | None = None, + region_label_mode: str = "auto", + max_region_labels: int = 50, + row_annotation_column: str | None = None, + row_annotation_columns: Sequence[str] | None = None, + row_annotation_title: str | None = None, + row_annotation_titles: Mapping[str, str] | None = None, + row_annotation_palette: Mapping[str, str] | None = None, + row_annotation_palettes: Mapping[str, Mapping[str, str]] | None = None, + group_region_labels: bool | None = None, + group_label_columns: Sequence[str] | None = None, +): + _require_payload_keys( + payload, + ("matrix_table", "metadata"), + owner="plot_read_cluster_region_association_heatmap_matplotlib", + ) + matrix_table = _require_payload_table(payload, "matrix_table") + metadata = payload.get("metadata") + if not isinstance(metadata, Mapping): + raise TypeError("plot payload key 'metadata' must be a mapping.") + + if region_sort is not None: + association_table = payload.get("association_table") + if not isinstance(association_table, pd.DataFrame): + raise ValueError( + "region_sort override requires payload['association_table']; " + "build payload with prepare_read_cluster_region_association_data(...)." + ) + from dimelo import plotting as plotting_api + + value_mode = str(metadata.get("value_mode") or "fraction") + top_n_regions_per_cluster = metadata.get("top_n_regions_per_cluster") + aggregate_mode = ( + association_strength_aggregate + if association_strength_aggregate is not None + else metadata.get("association_strength_aggregate", "max") + ) + cluster_sort_mode = ( + cluster_sort if cluster_sort is not None else metadata.get("cluster_sort", "input") + ) + override_payload = plotting_api.prepare_read_cluster_region_association_data( + association_table=association_table, + value_mode=value_mode, + top_n_regions_per_cluster=top_n_regions_per_cluster, + region_sort=region_sort, + association_strength_aggregate=str(aggregate_mode), + cluster_sort=str(cluster_sort_mode), + cluster_order=cluster_order, + ) + + matrix_table = _require_payload_table(override_payload, "matrix_table") + metadata = override_payload.get("metadata") + if not isinstance(metadata, Mapping): + raise TypeError( + "region_sort override produced payload metadata that is not a mapping." + ) + + original_axis_table = payload.get("region_axis_table") + region_axis_table = override_payload.get("region_axis_table") + if ( + isinstance(original_axis_table, pd.DataFrame) + and isinstance(region_axis_table, pd.DataFrame) + and "region_id" in original_axis_table.columns + and "region_id" in region_axis_table.columns + ): + extra_cols = [ + c + for c in original_axis_table.columns + if c not in region_axis_table.columns + ] + if extra_cols: + extras = original_axis_table.loc[ + :, ["region_id", *extra_cols] + ].drop_duplicates(subset=["region_id"]) + region_axis_table = region_axis_table.merge( + extras, on="region_id", how="left" + ) + elif cluster_sort is not None or cluster_order is not None: + association_table = payload.get("association_table") + if not isinstance(association_table, pd.DataFrame): + raise ValueError( + "cluster_sort or cluster_order override requires payload['association_table']; " + "build payload with prepare_read_cluster_region_association_data(...)." + ) + from dimelo import plotting as plotting_api + + override_payload = plotting_api.prepare_read_cluster_region_association_data( + association_table=association_table, + value_mode=str(metadata.get("value_mode") or "fraction"), + top_n_regions_per_cluster=metadata.get("top_n_regions_per_cluster"), + region_sort=metadata.get("region_sort") or "cluster_fraction", + association_strength_aggregate=str( + metadata.get("association_strength_aggregate", "max") + ), + cluster_sort=str(cluster_sort or metadata.get("cluster_sort", "input")), + cluster_order=cluster_order, + ) + matrix_table = _require_payload_table(override_payload, "matrix_table") + metadata = override_payload.get("metadata") + original_axis_table = payload.get("region_axis_table") + region_axis_table = override_payload.get("region_axis_table") + if ( + isinstance(original_axis_table, pd.DataFrame) + and isinstance(region_axis_table, pd.DataFrame) + and "region_id" in original_axis_table.columns + and "region_id" in region_axis_table.columns + ): + extra_cols = [ + c + for c in original_axis_table.columns + if c not in region_axis_table.columns + ] + if extra_cols: + extras = original_axis_table.loc[ + :, ["region_id", *extra_cols] + ].drop_duplicates(subset=["region_id"]) + region_axis_table = region_axis_table.merge( + extras, on="region_id", how="left" + ) + else: + region_axis_table = payload.get("region_axis_table") + + if matrix_table.empty: + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + ax.set_title(title or "Read-cluster association heatmap") + ax.set_xlabel("Cluster") + ax.set_ylabel("Region") + return fig, ax + + annotation_columns = _normalize_row_annotation_columns( + row_annotation_column=row_annotation_column, + row_annotation_columns=row_annotation_columns, + ) + region_column = ( + "region_id" if "region_id" in matrix_table.columns else matrix_table.columns[0] + ) + cluster_columns = [ + column for column in matrix_table.columns if column != region_column + ] + fig_width = max(8.0, 4.5 + (0.75 * len(cluster_columns))) + if annotation_columns: + fig_width += 1.1 + (0.45 * len(annotation_columns)) + fig_height = max(4.5, min(16.0, 2.0 + (0.10 * len(matrix_table)))) + fig, ax = _make_axis(ax=ax, figsize=(fig_width, fig_height)) + value_mode = str(metadata.get("value_mode") or "fraction") + if region_axis_table is not None and not isinstance( + region_axis_table, pd.DataFrame + ): + raise TypeError( + "plot payload key 'region_axis_table' must be a pandas DataFrame when provided." + ) + if region_label_mode not in {"auto", "region_id", "genomic", "chromosome"}: + raise ValueError( + "region_label_mode must be 'auto', 'region_id', 'genomic', or 'chromosome'." + ) + max_region_labels = max(1, int(max_region_labels)) + + heatmap = matrix_table.loc[:, cluster_columns].copy() + image = ax.imshow( + heatmap.to_numpy(), + aspect="auto", + origin="upper", + interpolation="nearest", + ) + colorbar = ax.figure.colorbar( + image, + ax=ax, + label=value_mode.replace("_", " ").title(), + fraction=0.045, + pad=0.05, + ) + ax.set_xticks(range(len(cluster_columns))) + ax.set_xticklabels( + [str(value) for value in cluster_columns], rotation=45, ha="right" + ) + + n_regions = len(matrix_table) + default_ticks = np.arange(n_regions) + default_labels = matrix_table[region_column].astype(str).tolist() + ticks = default_ticks + labels = default_labels + step = max(1, int(np.ceil(n_regions / max_region_labels))) + if step > 1: + ticks = default_ticks[::step] + labels = [default_labels[idx] for idx in ticks] + annotation_values_by_column: dict[str, list[str]] = {} + + axis_table = None + if isinstance(region_axis_table, pd.DataFrame) and not region_axis_table.empty: + axis_table = region_axis_table.copy() + if {"chrom", "start", "end"}.issubset(axis_table.columns): + genomic_labels = [ + f"{chrom}:{int(start):,}-{int(end):,}" + for chrom, start, end in axis_table[ + ["chrom", "start", "end"] + ].itertuples(index=False) + ] + chrom_labels = axis_table["chrom"].astype(str).tolist() + effective_mode = region_label_mode + if effective_mode == "auto": + default_region_sort = str(metadata.get("region_sort") or "input") + if default_region_sort == "genomic": + effective_mode = ( + "chromosome" if n_regions > max_region_labels else "genomic" + ) + else: + effective_mode = "region_id" + if effective_mode == "genomic": + labels = [genomic_labels[idx] for idx in ticks] + elif effective_mode == "chromosome": + labels = [chrom_labels[idx] for idx in ticks] + # Mark chromosome boundaries to visually group regions. + chrom_codes = pd.Categorical(axis_table["chrom"].astype(str)).codes + boundaries = np.flatnonzero(np.diff(chrom_codes)) + 1 + for boundary in boundaries: + ax.axhline(boundary - 0.5, color="white", linewidth=0.6, alpha=0.6) + elif effective_mode == "region_id": + labels = [default_labels[idx] for idx in ticks] + for annotation_column in annotation_columns: + if annotation_column in axis_table.columns: + annotation_values_by_column[annotation_column] = ( + axis_table[annotation_column].astype(str).tolist() + ) + + if group_region_labels is None: + available_annotation_columns = [ + column for column in annotation_columns if column in annotation_values_by_column + ] + group_region_labels = ( + axis_table is not None + and region_label_mode in {"genomic", "chromosome", "auto"} + and n_regions > max_region_labels + and ( + ("chrom" in axis_table.columns) + or bool(available_annotation_columns) + ) + ) + + if axis_table is not None and group_region_labels: + requested_group_cols = list(group_label_columns or []) + if not requested_group_cols: + available_annotation_columns = [ + column for column in annotation_columns if column in axis_table.columns + ] + if available_annotation_columns: + requested_group_cols = [available_annotation_columns[0], "chrom"] + elif "chrom" in axis_table.columns: + requested_group_cols = ["chrom"] + requested_group_cols = [ + col for col in requested_group_cols if col in axis_table.columns + ] + if requested_group_cols: + group_parts = axis_table.loc[:, requested_group_cols].astype(str) + group_key = group_parts.agg(" | ".join, axis=1).tolist() + boundaries = ( + np.flatnonzero(np.array(group_key[1:]) != np.array(group_key[:-1])) + 1 + ) + group_starts = np.concatenate(([0], boundaries)) + group_ends = np.concatenate((boundaries, [len(group_key)])) + centers = ((group_starts + group_ends - 1) / 2.0).astype(float) + group_labels = [group_key[int(start)] for start in group_starts] + # Downsample grouped labels when there are still too many. + if len(group_labels) > max_region_labels: + gstep = max(1, int(np.ceil(len(group_labels) / max_region_labels))) + centers = centers[::gstep] + group_labels = group_labels[::gstep] + ticks = centers + labels = group_labels + for boundary in boundaries: + ax.axhline(boundary - 0.5, color="white", linewidth=0.6, alpha=0.6) + + ax.set_yticks(np.asarray(ticks).tolist()) + use_legacy_annotation_labels = ( + bool(annotation_values_by_column) + and row_annotation_columns is None + and row_annotation_column is not None + and len(annotation_values_by_column) == 1 + and not group_region_labels + ) + if use_legacy_annotation_labels: + legacy_values = next(iter(annotation_values_by_column.values())) + labels = [f"{labels[pos]} | {legacy_values[idx]}" for pos, idx in enumerate(ticks)] + ytick_fontsize = 8 if len(labels) <= 30 else 7 + ax.set_yticklabels(labels, fontsize=ytick_fontsize) + if labels: + max_label_len = max(len(str(label)) for label in labels) + left_margin = min(0.42, max(0.14, 0.08 + (0.0035 * max_label_len))) + fig.subplots_adjust(left=left_margin) + ax.set_xlabel("Cluster") + ax.set_ylabel("Region") + ax.set_title( + title + or _read_cluster_association_title( + metadata, + row_annotation_columns=annotation_columns, + group_region_labels=group_region_labels, + region_label_mode=region_label_mode, + ), + pad=14, + ) + + if annotation_values_by_column and n_regions > 0: + from matplotlib.colors import ListedColormap + from matplotlib.patches import Patch + + ax.set_ylabel("") + # Place the annotation strip in figure coordinates so it never overlaps + # the main heatmap region, regardless of y-label length. + fig.canvas.draw() + renderer = fig.canvas.get_renderer() + ax_pos = ax.get_position() + tick_labels = [tick for tick in ax.get_yticklabels() if tick.get_text()] + if tick_labels: + tick_left = min( + tick.get_window_extent(renderer) + .transformed(fig.transFigure.inverted()) + .x0 + for tick in tick_labels + ) + else: + tick_left = ax_pos.x0 + + strip_width = 0.012 + strip_gap = 0.006 + label_gap = 0.018 + n_annotation_strips = len(annotation_values_by_column) + total_strip_width = ( + n_annotation_strips * strip_width + + max(0, n_annotation_strips - 1) * strip_gap + ) + strip_x1 = tick_left - label_gap + first_strip_x0 = strip_x1 - total_strip_width + min_left = 0.012 + if first_strip_x0 < min_left: + needed_left = min(0.56, ax_pos.x0 + (min_left - first_strip_x0) + 0.02) + fig.subplots_adjust(left=needed_left) + fig.canvas.draw() + renderer = fig.canvas.get_renderer() + ax_pos = ax.get_position() + tick_labels = [tick for tick in ax.get_yticklabels() if tick.get_text()] + if tick_labels: + tick_left = min( + tick.get_window_extent(renderer) + .transformed(fig.transFigure.inverted()) + .x0 + for tick in tick_labels + ) + else: + tick_left = ax_pos.x0 + strip_x1 = tick_left - label_gap + first_strip_x0 = max(min_left, strip_x1 - total_strip_width) + + plt = _import_pyplot() + cmap = plt.get_cmap("tab10") + for strip_idx, (annotation_column, annotation_values) in enumerate( + annotation_values_by_column.items() + ): + ordered_annotations = _ordered_unique_values( + pd.DataFrame({"annotation": annotation_values}), + "annotation", + ) + annotation_palette = _row_annotation_palette( + annotation_column, + row_annotation_palette=row_annotation_palette, + row_annotation_palettes=row_annotation_palettes, + n_columns=len(annotation_values_by_column), + ) + if annotation_palette is None: + annotation_palette = { + value: cmap(i % 10) for i, value in enumerate(ordered_annotations) + } + annotation_colors = [ + annotation_palette.get(value, "0.6") for value in ordered_annotations + ] + color_lookup = {value: i for i, value in enumerate(ordered_annotations)} + color_codes = np.array( + [color_lookup.get(value, 0) for value in annotation_values], dtype=int + ) + strip_x0 = first_strip_x0 + strip_idx * (strip_width + strip_gap) + strip_ax = fig.add_axes( + [strip_x0, ax_pos.y0, strip_width, ax_pos.height] + ) + strip_ax.imshow( + color_codes.reshape(-1, 1), + aspect="auto", + origin="upper", + interpolation="nearest", + cmap=ListedColormap(annotation_colors), + vmin=0, + vmax=max(1, len(annotation_colors) - 1), + ) + strip_ax.set_xticks([]) + strip_ax.set_yticks([]) + strip_title = _row_annotation_title( + annotation_column, + row_annotation_title=row_annotation_title, + row_annotation_titles=row_annotation_titles, + n_columns=len(annotation_values_by_column), + ) + strip_ax.set_xlabel(strip_title, fontsize=7, labelpad=8) + strip_ax.xaxis.label.set_rotation(90) + strip_ax.xaxis.label.set_verticalalignment("top") + strip_ax.xaxis.label.set_horizontalalignment("center") + + legend_handles = [ + Patch( + facecolor=annotation_palette.get(value, "0.6"), + edgecolor="none", + label=str(value), + ) + for value in ordered_annotations + ] + if legend_handles: + fig.canvas.draw() + legend_x = max( + colorbar.ax.get_position().x1 + 0.055, + ax.get_position().x1 + 0.12, + ) + legend = fig.legend( + handles=legend_handles, + title=strip_title, + loc="upper left", + bbox_to_anchor=(legend_x, ax.get_position().y1 - (0.16 * strip_idx)), + bbox_transform=fig.transFigure, + frameon=False, + ) + fig.add_artist(legend) + return fig, ax + + +def plot_global_analysis_summary_matplotlib( + payload: Mapping[str, object], + *, + level: str = "condition", + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("sample_summary", "condition_summary", "normalization_table", "metadata"), + owner="plot_global_analysis_summary_matplotlib", + ) + if level not in {"sample", "condition"}: + raise ValueError("level must be 'sample' or 'condition'.") + + table = _require_payload_table( + payload, "sample_summary" if level == "sample" else "condition_summary" + ) + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + if not table.empty: + if level == "sample": + x_values = table["sample_id"].astype(str) + y_values = table["global_fraction"] + else: + x_values = table["condition"].astype(str) + y_values = table["global_fraction_mean"] + ax.bar(x_values, y_values) + ax.tick_params(axis="x", rotation=45) + + ax.set_ylabel("Global Fraction") + ax.set_xlabel("Sample" if level == "sample" else "Condition") + ax.set_title(title or "Global analysis summary") + return fig, ax + + +def plot_global_analysis_window_matplotlib( + payload: Mapping[str, object], + *, + axes=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("window_table", "metadata"), + owner="plot_global_analysis_window_matplotlib", + ) + window_table = _require_payload_table(payload, "window_table") + metadata = ( + payload.get("metadata", {}) + if isinstance(payload.get("metadata", {}), Mapping) + else {} + ) + contigs = list( + metadata.get("contig_order") + or window_table.get("contig", pd.Series(dtype="object")).dropna().unique() + ) + + fig, axes = _make_axes(axes=axes, n_axes=len(contigs) or 1, figsize=(8, 3)) + + if not contigs: + axes[0].set_title(title or "Global analysis window") + axes[0].set_xlabel("Window midpoint") + axes[0].set_ylabel("Window Fraction") + return fig, axes + + for index, contig in enumerate(contigs): + ax = axes[index] + contig_table = window_table.loc[window_table["contig"] == contig] + if not contig_table.empty: + grouped = ( + contig_table.loc[:, ["window_midpoint", "window_fraction"]] + .groupby("window_midpoint", as_index=False, sort=True) + .mean(numeric_only=True) + ) + ax.plot( + grouped["window_midpoint"], + grouped["window_fraction"], + marker="o", + linewidth=1.5, + ) + ax.set_title(str(contig)) + ax.set_xlabel("Window midpoint") + ax.set_ylabel("Window Fraction") + + for ax in axes[len(contigs) :]: + ax.set_visible(False) + + if title: + fig.suptitle(title) + return fig, axes diff --git a/dimelo/region_analysis.py b/dimelo/region_analysis.py new file mode 100644 index 0000000..5eb6034 --- /dev/null +++ b/dimelo/region_analysis.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +import concurrent.futures +from collections.abc import Sequence +from pathlib import Path + +import numpy as np +from tqdm.auto import tqdm + +from . import cluster, utils +from .models import SampleSpec + + +def _build_sample_region_features( + *, + sample: SampleSpec, + motif: str, + matched_regions: str | Path | list[str | Path] | None, + pileup_paths: dict[str, str | Path] | None, + cores: int | None, + regions_executor: concurrent.futures.Executor | None = None, +) -> tuple[np.ndarray, list[dict[str, object]]]: + pileup_path = None + if pileup_paths is not None: + pileup_path = pileup_paths.get(sample.sample_id) + if pileup_path is None: + if not sample.metadata or "pileup_path" not in sample.metadata: + raise ValueError( + f"Sample {sample.sample_id!r} is missing a pileup_path for region_anchored mode." + ) + pileup_path = sample.metadata["pileup_path"] + + regions = matched_regions or sample.regions_bed + if regions is None: + raise ValueError( + f"Sample {sample.sample_id!r} requires matched_regions or regions_bed." + ) + + matrix, region_metadata = cluster.region_feature_matrix_from_pileup( + bedmethyl_file=pileup_path, + motif=motif, + regions=regions, + cores=cores, + quiet=True, + regions_executor=regions_executor, + ) + + matrix_float32 = np.asarray(matrix, dtype=np.float32) + metadata_rows = [ + { + "sample_id": sample.sample_id, + "condition": sample.condition, + "replicate": sample.replicate, + "region_id": f"{chrom}:{start}-{end}:{strand}", + "chromosome": chrom, + "start": start, + "end": end, + "strand": strand, + } + for chrom, start, end, strand in region_metadata + ] + return matrix_float32, metadata_rows + + +def build_region_feature_table( + *, + samples: Sequence[SampleSpec], + motifs: Sequence[str], + matched_regions: str | Path | list[str | Path] | None, + pileup_paths: dict[str, str | Path] | None = None, + cores: int | None = None, + quiet: bool = True, +) -> tuple[np.ndarray, list[dict[str, object]]]: + """ + Build one region-level feature row per sample and matched region. + """ + + if not motifs: + raise ValueError("motifs must contain at least one motif.") + if len(motifs) != 1: + raise ValueError( + "build_region_feature_table currently supports exactly one motif for " + "region_anchored mode." + ) + + sample_list = list(samples) + if not sample_list: + raise ValueError("No region feature rows were generated.") + + if cores is None: + sample_workers = 1 + per_sample_cores: int | None = None + else: + cores_to_run = max(1, utils.cores_to_run(cores)) + sample_workers = min(len(sample_list), cores_to_run) + per_sample_cores = max(1, cores_to_run // sample_workers) + + if sample_workers > 1: + ordered_results: list[tuple[np.ndarray, list[dict[str, object]]] | None] = [ + None + ] * len(sample_list) + with concurrent.futures.ThreadPoolExecutor( + max_workers=sample_workers + ) as executor: + future_by_index = { + index: executor.submit( + _build_sample_region_features, + sample=sample, + motif=motifs[0], + matched_regions=matched_regions, + pileup_paths=pileup_paths, + cores=per_sample_cores, + regions_executor=None, + ) + for index, sample in enumerate(sample_list) + } + for index, future in future_by_index.items(): + ordered_results[index] = future.result() + results = [result for result in ordered_results if result is not None] + else: + shared_regions_executor = None + if len(sample_list) > 1 and (cores is None or cores > 1): + try: + shared_regions_executor = concurrent.futures.ProcessPoolExecutor( + max_workers=max(1, utils.cores_to_run(cores)), + ) + except Exception: + # Some environments (for example restricted CI sandboxes) cannot + # create process pools; fall back to the legacy per-sample path. + shared_regions_executor = None + try: + results = [] + for sample in tqdm( + sample_list, desc="Building region features", disable=quiet + ): + result = _build_sample_region_features( + sample=sample, + motif=motifs[0], + matched_regions=matched_regions, + pileup_paths=pileup_paths, + cores=per_sample_cores, + regions_executor=shared_regions_executor, + ) + results.append(result) + finally: + if shared_regions_executor is not None: + shared_regions_executor.shutdown(wait=True, cancel_futures=False) + + matrices = [matrix for matrix, _ in results] + metadata_rows = [row for _, rows in results for row in rows] + + if not matrices: + raise ValueError("No region feature rows were generated.") + return np.vstack(matrices).astype(np.float32, copy=False), metadata_rows diff --git a/dimelo/region_contrasts.py b/dimelo/region_contrasts.py new file mode 100644 index 0000000..c534b1a --- /dev/null +++ b/dimelo/region_contrasts.py @@ -0,0 +1,1859 @@ +from __future__ import annotations + +import math +from collections.abc import Iterable +from functools import partial + +import pandas as pd +from scipy import stats + +from . import cluster, load_processed, utils +from .models import ContrastSpec, RegionContrastResult + + +def validate_region_contrast_request( + *, + analysis_unit: str, + representation: str, + signal_source: str, + test: str, +) -> None: + if analysis_unit == "ensemble_region": + if signal_source != "pileup_counts": + raise ValueError( + "V1 region_contrasts inference requires signal_source='pileup_counts'." + ) + if representation not in {"modified_fraction", "modified_count"}: + raise ValueError( + "V1 region_contrasts inference requires representation to be " + "'modified_fraction' or 'modified_count'." + ) + if test not in {"effect_size_only", "beta_binomial"}: + raise ValueError( + "Current region_contrasts scoring support requires test in " + "{'effect_size_only', 'beta_binomial'}." + ) + return + + if analysis_unit == "cluster_occupancy": + if signal_source != "cluster_occupancy": + raise ValueError( + "V1 region_contrasts inference requires signal_source='cluster_occupancy'." + ) + if representation not in { + "cluster_fraction", + "dominant_cluster", + "cluster_entropy", + }: + raise ValueError( + "V1 region_contrasts inference requires representation to be " + "'cluster_fraction', 'dominant_cluster', or 'cluster_entropy'." + ) + if representation == "cluster_fraction" and test not in { + "effect_size_only", + "fraction_test", + }: + raise ValueError( + "Current cluster_occupancy cluster_fraction scoring support requires " + "test in " + "{'effect_size_only', 'fraction_test'}." + ) + if representation in {"dominant_cluster", "cluster_entropy"} and test != ( + "effect_size_only" + ): + raise ValueError( + "Current cluster_occupancy descriptive scoring requires " + "representation='cluster_fraction' for inferential tests." + ) + return + + if analysis_unit == "single_read": + if representation == "read_mod_fraction": + if signal_source != "extract_reads": + raise ValueError( + "Current single_read read_mod_fraction support requires " + "signal_source='extract_reads'." + ) + if test not in {"effect_size_only", "sample_distribution_shift"}: + raise ValueError( + "Current single_read read_mod_fraction scoring support requires " + "test in {'effect_size_only', 'sample_distribution_shift'}." + ) + return + + if representation == "read_window_features": + if signal_source != "extract_features": + raise ValueError( + "Current single_read read_window_features support requires " + "signal_source='extract_features'." + ) + if test not in {"effect_size_only", "feature_summary_shift"}: + raise ValueError( + "Current single_read read_window_features support requires " + "test in {'effect_size_only', 'feature_summary_shift'}." + ) + return + + raise ValueError( + "Current single_read support requires representation='read_mod_fraction' " + "or 'read_window_features'." + ) + + if analysis_unit not in {"ensemble_region", "cluster_occupancy", "single_read"}: + raise ValueError( + "V1 region_contrasts inference requires analysis_unit='ensemble_region' " + "or analysis_unit='cluster_occupancy' " + "or analysis_unit='single_read'." + ) + + +def build_region_evidence_table( + *, + samples, + regions, + motifs: Iterable[str], + window_size: int | None = None, + quiet: bool = True, + cores: int | None = None, +) -> pd.DataFrame: + motifs = list(motifs) + if len(motifs) != 1: + raise ValueError( + "build_region_evidence_table currently supports exactly one motif." + ) + + motif = motifs[0] + regions_dict = utils.regions_dict_from_input(regions, window_size) + ordered_regions = [ + (chromosome, start, end, strand) + for chromosome, region_list in regions_dict.items() + for start, end, strand in region_list + ] + + rows = [] + for sample in samples: + metadata = sample.metadata or {} + if "pileup_path" not in metadata: + raise ValueError( + f"Sample {sample.sample_id} is missing metadata['pileup_path']." + ) + + pileup_loader = partial( + load_processed.pileup_counts_from_bedmethyl, + bedmethyl_file=metadata["pileup_path"], + motif=motif, + ) + counts_by_region = load_processed.regions_to_list( + function_handle=pileup_loader, + regions=regions, + window_size=window_size, + quiet=quiet, + cores=cores, + ) + + if len(counts_by_region) != len(ordered_regions): + raise ValueError( + "Pileup evidence count length did not match the number of parsed regions." + ) + + for (chromosome, start, end, strand), ( + modified_count, + valid_count, + ) in zip(ordered_regions, counts_by_region, strict=False): + mod_fraction = 0.0 if valid_count == 0 else modified_count / valid_count + rows.append( + { + "region_id": f"{chromosome}:{start}-{end},{strand}", + "chromosome": chromosome, + "start": start, + "end": end, + "strand": strand, + "sample_id": sample.sample_id, + "condition": sample.condition, + "replicate": sample.replicate, + "modified_count": modified_count, + "valid_count": valid_count, + "mod_fraction": mod_fraction, + } + ) + + return pd.DataFrame(rows) + + +def build_single_read_mod_fraction_evidence_table( + *, + extract_table: pd.DataFrame, + pairing_key: str | None = None, +) -> pd.DataFrame: + required_columns = { + "region_id", + "sample_id", + "condition", + "read_id", + "modified_count", + "valid_count", + } + if pairing_key: + required_columns.add(pairing_key) + missing_columns = required_columns - set(extract_table.columns) + if missing_columns: + missing_display = ", ".join(sorted(missing_columns)) + raise ValueError( + "build_single_read_mod_fraction_evidence_table requires columns: " + f"{missing_display}." + ) + + selected_columns = [ + "region_id", + "sample_id", + "condition", + "read_id", + "modified_count", + "valid_count", + ] + if pairing_key: + selected_columns.append(pairing_key) + evidence = extract_table.loc[ + :, + selected_columns, + ].copy() + evidence["modified_count"] = pd.to_numeric( + evidence["modified_count"], errors="coerce" + ) + evidence["valid_count"] = pd.to_numeric(evidence["valid_count"], errors="coerce") + invalid_counts = ( + evidence["modified_count"].isna() + | evidence["valid_count"].isna() + | ~evidence["modified_count"].map(math.isfinite) + | ~evidence["valid_count"].map(math.isfinite) + | (evidence["modified_count"] % 1 != 0) + | (evidence["valid_count"] % 1 != 0) + | (evidence["modified_count"] < 0) + | (evidence["valid_count"] < 0) + | (evidence["modified_count"] > evidence["valid_count"]) + ) + if invalid_counts.any(): + raise ValueError( + "build_single_read_mod_fraction_evidence_table modified_count and " + "valid_count must be finite integers, >= 0, and modified_count <= valid_count." + ) + evidence["modified_count"] = evidence["modified_count"].astype(int) + evidence["valid_count"] = evidence["valid_count"].astype(int) + evidence["read_mod_fraction"] = ( + evidence["modified_count"] + .div( + evidence["valid_count"].where(evidence["valid_count"] != 0), + fill_value=0, + ) + .fillna(0.0) + ) + return evidence + + +def build_single_read_feature_evidence_table( + *, + feature_table: pd.DataFrame, + pairing_key: str | None = None, +) -> pd.DataFrame: + required_columns = {"region_id", "sample_id", "condition", "read_id"} + if pairing_key: + required_columns.add(pairing_key) + missing_columns = required_columns - set(feature_table.columns) + if missing_columns: + missing_display = ", ".join(sorted(missing_columns)) + raise ValueError( + "build_single_read_feature_evidence_table requires columns: " + f"{missing_display}." + ) + + feature_columns = [ + column for column in feature_table.columns if column not in required_columns + ] + if not feature_columns: + raise ValueError( + "build_single_read_feature_evidence_table requires at least one feature column." + ) + selected_columns = ["region_id", "sample_id", "condition", "read_id"] + if pairing_key: + selected_columns.append(pairing_key) + selected_columns.extend(feature_columns) + evidence = feature_table.loc[:, selected_columns].copy() + numeric_features = evidence.loc[:, feature_columns].apply( + pd.to_numeric, + errors="coerce", + ) + finite_feature_values = numeric_features.apply( + lambda column: column.map(math.isfinite) + ) + invalid_feature_values = numeric_features.isna() | ~finite_feature_values + if invalid_feature_values.to_numpy().any(): + raise ValueError( + "build_single_read_feature_evidence_table feature columns must be numeric " + "and finite." + ) + evidence.loc[:, feature_columns] = numeric_features + return evidence + + +def _require_single_read_pairing_key(frame: pd.DataFrame, pairing_key: str) -> None: + if pairing_key not in frame.columns: + raise ValueError( + f"single_read matched_pairwise scoring requires column {pairing_key!r}." + ) + if frame[pairing_key].isna().any(): + raise ValueError( + f"single_read matched_pairwise scoring requires non-null values in column {pairing_key!r}." + ) + + +def _require_single_read_matched_binary_sides(contrast: ContrastSpec) -> None: + if len(contrast.numerator or []) != 1 or len(contrast.denominator or []) != 1: + raise ValueError( + "single_read matched_pairwise scoring currently requires exactly one " + "numerator and one denominator condition." + ) + + +def _validate_single_read_matched_sample_summary( + sample_summary: pd.DataFrame, + *, + pairing_key: str, +) -> None: + sample_pair_counts = ( + sample_summary.loc[:, ["sample_id", pairing_key]] + .drop_duplicates() + .groupby("sample_id", dropna=False)[pairing_key] + .nunique(dropna=False) + ) + if (sample_pair_counts > 1).any(): + raise ValueError( + "single_read matched_pairwise scoring requires each sample_id to map " + "to exactly one pairing key." + ) + + side_counts = sample_summary.groupby( + ["region_id", pairing_key, "condition"], + dropna=False, + sort=False, + )["sample_id"].nunique() + if (side_counts > 1).any(): + raise ValueError( + "single_read matched_pairwise scoring requires exactly one sample " + "per region, pair, and condition." + ) + + +def _load_builtin_single_read_feature_table(*, samples, regions, motifs): + if not samples: + raise ValueError( + "Built-in single_read feature loading requires at least one sample." + ) + + selected_feature_names: list[str] | None = None + sample_frames: list[pd.DataFrame] = [] + for sample in samples: + extracted = cluster.extract_read_windows( + hdf5_file=sample.extract_h5, + motifs=motifs, + regions=regions, + ) + feature_matrix, feature_names = cluster.read_window_feature_matrix(extracted) + feature_rows = list(feature_matrix) + feature_names = list(feature_names) + if selected_feature_names is None: + selected_feature_names = feature_names + elif selected_feature_names != feature_names: + raise ValueError( + "Built-in single_read feature loading requires matching feature names " + "across samples." + ) + + if len(extracted.metadata) != len(feature_rows): + raise ValueError( + "Built-in single_read feature loading requires one metadata row per " + "feature row." + ) + + rows = [] + for metadata, feature_values in zip( + extracted.metadata, feature_rows, strict=False + ): + chromosome = metadata.get("chromosome") + start = metadata.get("region_start") + end = metadata.get("region_end") + read_id = metadata.get("read_name") + if chromosome is None or start is None or end is None: + raise ValueError( + "Built-in single_read feature loading requires metadata with " + "chromosome, region_start, and region_end." + ) + if read_id is None: + raise ValueError( + "Built-in single_read feature loading requires metadata with read_name." + ) + strand = metadata.get("region_strand") or "+" + row = { + "region_id": f"{chromosome}:{int(start)}-{int(end)},{strand}", + "sample_id": sample.sample_id, + "condition": sample.condition, + "read_id": read_id, + } + row.update( + { + feature_name: float(feature_value) + for feature_name, feature_value in zip( + feature_names, feature_values, strict=False + ) + } + ) + rows.append(row) + sample_frames.append(pd.DataFrame(rows)) + + if not sample_frames: + return pd.DataFrame( + columns=[ + "region_id", + "sample_id", + "condition", + "read_id", + *(selected_feature_names or []), + ] + ) + return pd.concat(sample_frames, ignore_index=True) + + +def build_cluster_occupancy_evidence_table( + *, + region_summaries: pd.DataFrame, +) -> pd.DataFrame: + required_columns = {"region_id", "sample_id", "condition", "cluster", "count"} + missing_columns = required_columns - set(region_summaries.columns) + if missing_columns: + missing_display = ", ".join(sorted(missing_columns)) + raise ValueError( + "build_cluster_occupancy_evidence_table requires columns: " + f"{missing_display}." + ) + + evidence = region_summaries.copy() + counts = pd.to_numeric(evidence["count"], errors="raise") + invalid_counts = counts.isna() | (counts < 0) | ~counts.map(math.isfinite) + if invalid_counts.any(): + raise ValueError( + "build_cluster_occupancy_evidence_table count values must be finite and >= 0." + ) + + grouped_keys = ["region_id", "sample_id", "condition"] + totals = evidence.groupby(grouped_keys, dropna=False)["count"].transform("sum") + evidence["_group_total_count"] = totals + evidence["fraction"] = ( + evidence["count"].div(totals.where(totals != 0), fill_value=0).fillna(0.0) + ) + + summary = ( + evidence.sort_values( + by=grouped_keys + ["fraction", "cluster"], + ascending=[True, True, True, False, True], + kind="mergesort", + ) + .drop_duplicates(subset=grouped_keys, keep="first") + .loc[:, grouped_keys + ["cluster", "_group_total_count"]] + .rename(columns={"cluster": "dominant_cluster"}) + ) + summary.loc[summary["_group_total_count"] == 0, "dominant_cluster"] = None + summary = summary.drop(columns="_group_total_count") + + def _cluster_entropy(values: pd.Series) -> float: + probabilities = values.astype(float) + if probabilities.empty: + return 0.0 + probabilities = probabilities[probabilities > 0] + if probabilities.empty: + return 0.0 + return float(-(probabilities * probabilities.map(math.log2)).sum()) + + entropy = ( + evidence.groupby(grouped_keys, dropna=False)["fraction"] + .apply(_cluster_entropy) + .reset_index(name="cluster_entropy") + ) + + return ( + evidence.merge(summary, on=grouped_keys, how="left") + .merge( + entropy, + on=grouped_keys, + how="left", + ) + .drop(columns="_group_total_count") + ) + + +def _zero_safe_divide(numerator: pd.Series, denominator: pd.Series) -> pd.Series: + return numerator.div(denominator.where(denominator != 0), fill_value=0).fillna(0.0) + + +def _estimate_beta_binomial_prior( + denominator_modified_count: int, + denominator_valid_count: int, +) -> tuple[float, float]: + if denominator_modified_count < 0: + raise ValueError("beta-binomial denominator modified_count must be >= 0.") + if denominator_valid_count < 0: + raise ValueError("beta-binomial denominator valid_count must be >= 0.") + if denominator_modified_count > denominator_valid_count: + raise ValueError( + "beta-binomial denominator modified_count cannot exceed valid_count." + ) + + denominator_unmodified_count = denominator_valid_count - denominator_modified_count + return float(denominator_modified_count + 1), float( + denominator_unmodified_count + 1 + ) + + +def _log_beta_function(alpha: float, beta: float) -> float: + return math.lgamma(alpha) + math.lgamma(beta) - math.lgamma(alpha + beta) + + +def _beta_binomial_logpmf(k: int, n: int, alpha: float, beta: float) -> float: + if k < 0 or k > n: + return float("-inf") + return ( + math.lgamma(n + 1) + - math.lgamma(k + 1) + - math.lgamma(n - k + 1) + + _log_beta_function(k + alpha, n - k + beta) + - _log_beta_function(alpha, beta) + ) + + +def _beta_binomial_two_sided_p_value( + k: int, n: int, alpha: float, beta: float +) -> float: + if k < 0: + raise ValueError("beta-binomial modified_count must be >= 0.") + if n < 0: + raise ValueError("beta-binomial valid_count must be >= 0.") + if k > n: + raise ValueError("beta-binomial modified_count cannot exceed valid_count.") + if n <= 0: + return 1.0 + + support = list(range(n + 1)) + logpmf = [_beta_binomial_logpmf(x, n, alpha, beta) for x in support] + observed_logpmf = logpmf[k] + tail_probability = sum( + math.exp(value) for value in logpmf if value <= observed_logpmf + 1e-12 + ) + return min(max(tail_probability, 0.0), 1.0) + + +def _adjust_p_values_bh(p_values: pd.Series) -> pd.Series: + if p_values.empty: + return pd.Series(dtype=float, index=p_values.index) + + ranked = sorted( + enumerate(p_values.tolist()), key=lambda item: item[1], reverse=True + ) + total = len(ranked) + adjusted = [1.0] * total + running_min = 1.0 + + for rank_from_end, (original_index, p_value) in enumerate(ranked, start=1): + rank = total - rank_from_end + 1 + candidate = min(1.0, p_value * total / rank) + running_min = min(running_min, candidate) + adjusted[original_index] = running_min + + return pd.Series(adjusted, index=p_values.index, dtype=float) + + +def _add_beta_binomial_scores( + regions_table: pd.DataFrame, + *, + multiple_testing: str, +) -> pd.DataFrame: + if multiple_testing != "fdr_bh": + raise ValueError( + "Current beta-binomial region contrast scoring requires " + "multiple_testing='fdr_bh'." + ) + + scored = regions_table.copy() + scored["p_value"] = [ + _beta_binomial_two_sided_p_value( + int(modified_count), + int(valid_count), + *_estimate_beta_binomial_prior( + int(reference_modified_count), + int(reference_valid_count), + ), + ) + for modified_count, valid_count, reference_modified_count, reference_valid_count in zip( + scored["numerator_modified_count"], + scored["numerator_valid_count"], + scored["denominator_modified_count"], + scored["denominator_valid_count"], + strict=False, + ) + ] + scored["adjusted_p_value"] = _adjust_p_values_bh(scored["p_value"]) + return scored + + +def _pool_region_groups(evidence: pd.DataFrame, contrast: ContrastSpec) -> pd.DataFrame: + if contrast.mode not in {"pairwise", "group_vs_group"}: + raise NotImplementedError( + f"Effect-size pooling is not implemented for contrast mode '{contrast.mode}'." + ) + + pooled_frames = [] + side_specs = { + "numerator": contrast.numerator or [], + "denominator": contrast.denominator or [], + } + + for side, conditions in side_specs.items(): + side_evidence = evidence.loc[evidence["condition"].isin(conditions)].copy() + available_conditions = set(side_evidence["condition"].dropna().unique()) + missing_conditions = sorted(set(conditions) - available_conditions) + if missing_conditions: + missing_display = ", ".join(missing_conditions) + raise ValueError( + f"Missing {side} evidence for requested condition(s): {missing_display}." + ) + pooled = ( + side_evidence.groupby( + ["region_id", "chromosome", "start", "end", "strand"], + dropna=False, + sort=False, + as_index=False, + ) + .agg( + modified_count=("modified_count", "sum"), + valid_count=("valid_count", "sum"), + replicate_n=("sample_id", "nunique"), + ) + .assign(contrast_side=side) + ) + pooled["fraction"] = _zero_safe_divide( + pooled["modified_count"], pooled["valid_count"] + ) + pooled_frames.append(pooled) + + return pd.concat(pooled_frames, ignore_index=True) + + +def _require_occupancy_columns( + occupancy_table: pd.DataFrame, + *, + required_columns: set[str], +) -> None: + missing_columns = required_columns - set(occupancy_table.columns) + if missing_columns: + missing_display = ", ".join(sorted(missing_columns)) + raise ValueError(f"occupancy_table requires columns: {missing_display}.") + + +def _require_supported_cluster_occupancy_mode(contrast: ContrastSpec) -> None: + if contrast.mode not in {"pairwise", "group_vs_group"}: + raise NotImplementedError( + f"Cluster occupancy scoring is not implemented for contrast mode '{contrast.mode}'." + ) + + +def _require_requested_conditions_present( + evidence: pd.DataFrame, + *, + side_specs: dict[str, list[str]], +) -> None: + for side, conditions in side_specs.items(): + side_evidence = evidence.loc[evidence["condition"].isin(conditions)].copy() + available_conditions = set(side_evidence["condition"].dropna().unique()) + missing_conditions = sorted(set(conditions) - available_conditions) + if missing_conditions: + missing_display = ", ".join(missing_conditions) + raise ValueError( + f"Missing {side} evidence for requested condition(s): {missing_display}." + ) + + +def _validate_occupancy_table_numeric_columns(occupancy_table: pd.DataFrame) -> None: + numeric_specs = [ + ( + "fraction", + "occupancy_table fraction values must be finite and between 0 and 1.", + lambda values: ( + values.isna() | ~values.map(math.isfinite) | (values < 0) | (values > 1) + ), + ), + ( + "cluster_entropy", + "occupancy_table cluster_entropy values must be finite and >= 0.", + lambda values: values.isna() | ~values.map(math.isfinite) | (values < 0), + ), + ( + "count", + "occupancy_table count values must be finite and >= 0.", + lambda values: values.isna() | ~values.map(math.isfinite) | (values < 0), + ), + ] + + for column, message, invalid_mask_builder in numeric_specs: + if column not in occupancy_table.columns: + continue + values = pd.to_numeric(occupancy_table[column], errors="coerce") + invalid_values = invalid_mask_builder(values) + if invalid_values.any(): + raise ValueError(message) + + +def _zero_fill_missing_cluster_fraction_rows( + side_evidence: pd.DataFrame, + *, + cluster_universe: pd.DataFrame, +) -> pd.DataFrame: + sample_columns = ["region_id", "sample_id", "condition"] + sample_level = side_evidence.loc[:, sample_columns].drop_duplicates() + if sample_level.empty: + return side_evidence + + complete_index = sample_level.merge(cluster_universe, on="region_id", how="inner") + merged = complete_index.merge( + side_evidence, + on=sample_columns + ["cluster"], + how="left", + sort=False, + ) + merged["fraction"] = merged["fraction"].fillna(0.0) + return merged + + +def _pool_cluster_occupancy_groups( + evidence: pd.DataFrame, + contrast: ContrastSpec, + *, + value_column: str, + group_columns: list[str], +) -> pd.DataFrame: + _require_supported_cluster_occupancy_mode(contrast) + + pooled_frames = [] + side_specs = { + "numerator": contrast.numerator or [], + "denominator": contrast.denominator or [], + } + _require_requested_conditions_present( + evidence, + side_specs=side_specs, + ) + cluster_universe = None + if value_column == "fraction": + cluster_universe = evidence.loc[:, ["region_id", "cluster"]].drop_duplicates() + + for side, conditions in side_specs.items(): + side_evidence = evidence.loc[evidence["condition"].isin(conditions)].copy() + if value_column == "fraction": + side_evidence = _zero_fill_missing_cluster_fraction_rows( + side_evidence, + cluster_universe=cluster_universe, + ) + + pooled = ( + side_evidence.groupby(group_columns, dropna=False, sort=False) + .agg( + value=(value_column, "mean"), + replicate_n=("sample_id", "nunique"), + sample_values=( + value_column, + lambda values: tuple(float(v) for v in values), + ), + ) + .reset_index() + .assign(contrast_side=side) + ) + pooled_frames.append(pooled) + + return pd.concat(pooled_frames, ignore_index=True) + + +def _summarize_single_read_mod_fraction_by_sample( + evidence: pd.DataFrame, + *, + pairing_key: str | None = None, +) -> pd.DataFrame: + group_columns = ["region_id", "sample_id", "condition"] + if pairing_key: + _require_single_read_pairing_key(evidence, pairing_key) + group_columns.append(pairing_key) + + return evidence.groupby(group_columns, as_index=False, sort=False).agg( + read_n=("read_id", "nunique"), + sample_summary_mean=("read_mod_fraction", "mean"), + ) + + +def _summarize_single_read_mod_fraction_by_pair( + sample_summary: pd.DataFrame, + *, + pairing_key: str, +) -> pd.DataFrame: + pair_columns = ["region_id", pairing_key, "condition"] + return sample_summary.groupby(pair_columns, as_index=False, sort=False).agg( + sample_summary_mean=("sample_summary_mean", "mean"), + read_n=("read_n", "sum"), + sample_n=("sample_id", "nunique"), + ) + + +def _select_complete_matched_pairs( + pair_summary: pd.DataFrame, + *, + pairing_key: str, + contrast: ContrastSpec, +) -> pd.DataFrame: + required_conditions = list( + dict.fromkeys((contrast.numerator or []) + (contrast.denominator or [])) + ) + required_condition_set = set(required_conditions) + pair_conditions = ( + pair_summary.loc[:, ["region_id", pairing_key, "condition"]] + .drop_duplicates() + .groupby(["region_id", pairing_key])["condition"] + .agg(lambda values: set(values)) + ) + complete_pair_index = [ + pair_index + for pair_index, conditions in pair_conditions.items() + if required_condition_set.issubset(conditions) + ] + if not complete_pair_index: + raise ValueError( + "single_read matched_pairwise scoring found no complete matched pairs." + ) + + complete_pairs = pd.MultiIndex.from_tuples( + complete_pair_index, + names=["region_id", pairing_key], + ) + paired_index = pd.MultiIndex.from_frame( + pair_summary.loc[:, ["region_id", pairing_key]] + ) + return pair_summary.loc[paired_index.isin(complete_pairs)].copy() + + +def _score_single_read_mod_fraction( + *, + evidence: pd.DataFrame, + contrast: ContrastSpec, +) -> tuple[pd.DataFrame, pd.DataFrame]: + if contrast.mode not in {"pairwise", "group_vs_group", "matched_pairwise"}: + raise NotImplementedError( + "Single-read mod-fraction scoring is not implemented for contrast mode " + f"'{contrast.mode}'." + ) + + pairing_key = contrast.pairing_key if contrast.mode == "matched_pairwise" else None + if contrast.mode == "matched_pairwise": + _require_single_read_matched_binary_sides(contrast) + sample_summary = _summarize_single_read_mod_fraction_by_sample( + evidence, + pairing_key=pairing_key, + ) + side_specs = { + "numerator": contrast.numerator or [], + "denominator": contrast.denominator or [], + } + _require_requested_conditions_present( + sample_summary, + side_specs=side_specs, + ) + + if contrast.mode == "matched_pairwise": + _validate_single_read_matched_sample_summary( + sample_summary, + pairing_key=pairing_key or "", + ) + pair_summary = _summarize_single_read_mod_fraction_by_pair( + sample_summary, + pairing_key=pairing_key or "", + ) + pair_summary = _select_complete_matched_pairs( + pair_summary, + pairing_key=pairing_key or "", + contrast=contrast, + ) + + numerator = ( + pair_summary.loc[pair_summary["condition"].isin(side_specs["numerator"])] + .drop(columns=["condition"]) + .rename( + columns={ + "sample_summary_mean": "sample_summary_numerator_mean", + "read_n": "numerator_read_n", + "sample_n": "numerator_sample_n", + } + ) + ) + denominator = ( + pair_summary.loc[pair_summary["condition"].isin(side_specs["denominator"])] + .drop(columns=["condition"]) + .rename( + columns={ + "sample_summary_mean": "sample_summary_denominator_mean", + "read_n": "denominator_read_n", + "sample_n": "denominator_sample_n", + } + ) + ) + regions_table = numerator.merge( + denominator, + on=["region_id", pairing_key], + how="inner", + sort=False, + ) + if regions_table.empty: + raise ValueError( + "single_read matched_pairwise scoring found no complete matched pairs." + ) + regions_table["delta_summary_mean"] = ( + regions_table["sample_summary_numerator_mean"] + - regions_table["sample_summary_denominator_mean"] + ) + regions_table["log2_fc"] = ( + (regions_table["sample_summary_numerator_mean"] + 1e-6) + / (regions_table["sample_summary_denominator_mean"] + 1e-6) + ).map(math.log2) + regions_table = regions_table.groupby( + "region_id", as_index=False, sort=False + ).agg( + sample_summary_numerator_mean=("sample_summary_numerator_mean", "mean"), + sample_summary_denominator_mean=("sample_summary_denominator_mean", "mean"), + delta_summary_mean=("delta_summary_mean", "mean"), + numerator_read_n=("numerator_read_n", "sum"), + denominator_read_n=("denominator_read_n", "sum"), + numerator_replicate_n=("numerator_sample_n", "size"), + denominator_replicate_n=("denominator_sample_n", "size"), + ) + regions_table["log2_fc"] = ( + (regions_table["sample_summary_numerator_mean"] + 1e-6) + / (regions_table["sample_summary_denominator_mean"] + 1e-6) + ).map(math.log2) + regions_table["effect_size"] = regions_table["delta_summary_mean"].abs() + regions_table = regions_table.sort_values( + by=["effect_size", "delta_summary_mean", "region_id"], + ascending=[False, False, True], + kind="mergesort", + ).reset_index(drop=True) + regions_table["rank"] = range(1, len(regions_table) + 1) + summary = regions_table.loc[ + :, + [ + "region_id", + "sample_summary_numerator_mean", + "sample_summary_denominator_mean", + "delta_summary_mean", + "log2_fc", + "rank", + "numerator_read_n", + "denominator_read_n", + "numerator_replicate_n", + "denominator_replicate_n", + ], + ].copy() + return regions_table, summary + + pooled_frames = [] + for side, conditions in side_specs.items(): + side_evidence = sample_summary.loc[ + sample_summary["condition"].isin(conditions) + ].copy() + pooled = ( + side_evidence.groupby("region_id", dropna=False, sort=False) + .agg( + sample_summary_mean=("sample_summary_mean", "mean"), + read_n=("read_n", "sum"), + replicate_n=("sample_id", "nunique"), + sample_values=( + "sample_summary_mean", + lambda values: tuple(float(v) for v in values), + ), + ) + .reset_index() + .assign(contrast_side=side) + ) + pooled_frames.append(pooled) + + pooled = pd.concat(pooled_frames, ignore_index=True) + numerator = ( + pooled.loc[pooled["contrast_side"] == "numerator"] + .drop(columns=["contrast_side", "sample_values"]) + .rename( + columns={ + "sample_summary_mean": "sample_summary_numerator_mean", + "read_n": "numerator_read_n", + "replicate_n": "numerator_replicate_n", + } + ) + ) + denominator = ( + pooled.loc[pooled["contrast_side"] == "denominator"] + .drop(columns=["contrast_side", "sample_values"]) + .rename( + columns={ + "sample_summary_mean": "sample_summary_denominator_mean", + "read_n": "denominator_read_n", + "replicate_n": "denominator_replicate_n", + } + ) + ) + + regions_table = numerator.merge( + denominator, + on="region_id", + how="outer", + sort=False, + ) + regions_table["sample_summary_numerator_mean"] = regions_table[ + "sample_summary_numerator_mean" + ].fillna(0.0) + regions_table["sample_summary_denominator_mean"] = regions_table[ + "sample_summary_denominator_mean" + ].fillna(0.0) + regions_table["numerator_read_n"] = ( + regions_table["numerator_read_n"].fillna(0).astype(int) + ) + regions_table["denominator_read_n"] = ( + regions_table["denominator_read_n"].fillna(0).astype(int) + ) + regions_table["numerator_replicate_n"] = ( + regions_table["numerator_replicate_n"].fillna(0).astype(int) + ) + regions_table["denominator_replicate_n"] = ( + regions_table["denominator_replicate_n"].fillna(0).astype(int) + ) + regions_table["delta_summary_mean"] = ( + regions_table["sample_summary_numerator_mean"] + - regions_table["sample_summary_denominator_mean"] + ) + pseudocount = 1e-6 + regions_table["log2_fc"] = ( + (regions_table["sample_summary_numerator_mean"] + pseudocount) + / (regions_table["sample_summary_denominator_mean"] + pseudocount) + ).map(math.log2) + regions_table["effect_size"] = regions_table["delta_summary_mean"].abs() + regions_table = regions_table.sort_values( + by=["effect_size", "delta_summary_mean", "region_id"], + ascending=[False, False, True], + kind="mergesort", + ).reset_index(drop=True) + regions_table["rank"] = range(1, len(regions_table) + 1) + + summary = regions_table.loc[ + :, + [ + "region_id", + "sample_summary_numerator_mean", + "sample_summary_denominator_mean", + "delta_summary_mean", + "log2_fc", + "rank", + "numerator_read_n", + "denominator_read_n", + "numerator_replicate_n", + "denominator_replicate_n", + ], + ].copy() + return regions_table, summary + + +def _summarize_single_read_features_by_sample( + evidence: pd.DataFrame, + *, + pairing_key: str | None = None, +) -> pd.DataFrame: + group_columns = ["region_id", "sample_id", "condition"] + excluded_columns = {"region_id", "sample_id", "condition", "read_id"} + if pairing_key: + _require_single_read_pairing_key(evidence, pairing_key) + group_columns.append(pairing_key) + excluded_columns.add(pairing_key) + feature_columns = [ + column for column in evidence.columns if column not in excluded_columns + ] + return evidence.groupby(group_columns, as_index=False, sort=False)[ + feature_columns + ].mean() + + +def _score_single_read_features( + *, + evidence: pd.DataFrame, + contrast: ContrastSpec, +) -> tuple[pd.DataFrame, pd.DataFrame]: + if contrast.mode not in {"pairwise", "group_vs_group", "matched_pairwise"}: + raise NotImplementedError( + "Single-read feature scoring is not implemented for contrast mode " + f"'{contrast.mode}'." + ) + + pairing_key = contrast.pairing_key if contrast.mode == "matched_pairwise" else None + if contrast.mode == "matched_pairwise": + _require_single_read_matched_binary_sides(contrast) + sample_summary = _summarize_single_read_features_by_sample( + evidence, + pairing_key=pairing_key, + ) + side_specs = { + "numerator": contrast.numerator or [], + "denominator": contrast.denominator or [], + } + _require_requested_conditions_present( + sample_summary, + side_specs=side_specs, + ) + + feature_columns = [ + column + for column in sample_summary.columns + if column not in {"region_id", "sample_id", "condition", pairing_key} + ] + if contrast.mode == "matched_pairwise": + _validate_single_read_matched_sample_summary( + sample_summary, + pairing_key=pairing_key or "", + ) + pair_columns = ["region_id", pairing_key, "condition"] + pair_summary = sample_summary.groupby(pair_columns, as_index=False, sort=False)[ + feature_columns + ].mean() + pair_summary = _select_complete_matched_pairs( + pair_summary, + pairing_key=pairing_key or "", + contrast=contrast, + ) + + numerator = ( + pair_summary.loc[pair_summary["condition"].isin(side_specs["numerator"])] + .drop(columns=["condition"]) + .rename( + columns={ + feature_name: f"{feature_name}_numerator_mean" + for feature_name in feature_columns + } + ) + ) + denominator = ( + pair_summary.loc[pair_summary["condition"].isin(side_specs["denominator"])] + .drop(columns=["condition"]) + .rename( + columns={ + feature_name: f"{feature_name}_denominator_mean" + for feature_name in feature_columns + } + ) + ) + regions_table = numerator.merge( + denominator, + on=["region_id", pairing_key], + how="inner", + sort=False, + ) + if regions_table.empty: + raise ValueError( + "single_read matched_pairwise scoring found no complete matched pairs." + ) + + summary_rows = [] + for region_id, region_table in regions_table.groupby("region_id", sort=False): + row = {"region_id": region_id} + effect_sizes = [] + for feature_name in feature_columns: + numerator_mean = float( + region_table[f"{feature_name}_numerator_mean"].mean() + ) + denominator_mean = float( + region_table[f"{feature_name}_denominator_mean"].mean() + ) + delta_mean = numerator_mean - denominator_mean + row[f"{feature_name}_numerator_mean"] = numerator_mean + row[f"{feature_name}_denominator_mean"] = denominator_mean + row[f"{feature_name}_delta_mean"] = delta_mean + effect_sizes.append(abs(delta_mean)) + row["effect_size"] = max(effect_sizes, default=0.0) + summary_rows.append(row) + + summary = pd.DataFrame(summary_rows) + if summary.empty: + summary = pd.DataFrame(columns=["region_id", "effect_size"]) + else: + summary = summary.sort_values( + by=["effect_size", "region_id"], + ascending=[False, True], + kind="mergesort", + ).reset_index(drop=True) + summary["rank"] = range(1, len(summary) + 1) + return evidence, summary.drop(columns="effect_size", errors="ignore") + + summary_rows = [] + for region_id, region_table in sample_summary.groupby("region_id", sort=False): + numerator_table = region_table[ + region_table["condition"].isin(contrast.numerator) + ] + denominator_table = region_table[ + region_table["condition"].isin(contrast.denominator) + ] + row = {"region_id": region_id} + effect_sizes = [] + for feature_name in feature_columns: + numerator_mean = float(numerator_table[feature_name].mean()) + denominator_mean = float(denominator_table[feature_name].mean()) + delta_mean = numerator_mean - denominator_mean + row[f"{feature_name}_numerator_mean"] = numerator_mean + row[f"{feature_name}_denominator_mean"] = denominator_mean + row[f"{feature_name}_delta_mean"] = delta_mean + effect_sizes.append(abs(delta_mean)) + row["effect_size"] = max(effect_sizes, default=0.0) + summary_rows.append(row) + + summary = pd.DataFrame(summary_rows) + if summary.empty: + summary = pd.DataFrame(columns=["region_id", "effect_size"]) + else: + summary = summary.sort_values( + by=["effect_size", "region_id"], + ascending=[False, True], + kind="mergesort", + ).reset_index(drop=True) + summary["rank"] = range(1, len(summary) + 1) + return evidence, summary.drop(columns="effect_size", errors="ignore") + + +def _add_fraction_test_scores( + regions_table: pd.DataFrame, + *, + multiple_testing: str, +) -> pd.DataFrame: + if multiple_testing != "fdr_bh": + raise ValueError( + "Current fraction_test region contrast scoring requires " + "multiple_testing='fdr_bh'." + ) + + def _welch_p_value(row: pd.Series) -> float: + numerator_values = list(row["numerator_sample_values"]) + denominator_values = list(row["denominator_sample_values"]) + if len(numerator_values) < 2 or len(denominator_values) < 2: + return 1.0 + + test_result = stats.ttest_ind( + numerator_values, + denominator_values, + equal_var=False, + ) + p_value = float(test_result.pvalue) + if math.isnan(p_value): + return 1.0 + return min(max(p_value, 0.0), 1.0) + + scored = regions_table.copy() + scored["p_value"] = scored.apply(_welch_p_value, axis=1) + scored["adjusted_p_value"] = _adjust_p_values_bh(scored["p_value"]) + return scored + + +def _dominant_label(values: pd.Series) -> str | None: + non_null = values.dropna() + if non_null.empty: + return None + counts = non_null.value_counts() + max_count = counts.max() + return sorted(counts[counts == max_count].index.tolist())[0] + + +def _dominant_clusters_differ( + dominant_cluster: str | None, + reference_dominant_cluster: str | None, +) -> bool: + dominant_missing = pd.isna(dominant_cluster) + reference_missing = pd.isna(reference_dominant_cluster) + if dominant_missing and reference_missing: + return False + if dominant_missing or reference_missing: + return True + return bool(dominant_cluster != reference_dominant_cluster) + + +def _score_cluster_occupancy( + *, + evidence: pd.DataFrame, + contrast: ContrastSpec, + representation: str, + test: str, + multiple_testing: str, +) -> tuple[pd.DataFrame, pd.DataFrame]: + pseudocount = 1e-6 + _require_supported_cluster_occupancy_mode(contrast) + + if representation == "cluster_fraction": + _require_occupancy_columns( + evidence, + required_columns={ + "region_id", + "sample_id", + "condition", + "cluster", + "fraction", + }, + ) + pooled = _pool_cluster_occupancy_groups( + evidence, + contrast, + value_column="fraction", + group_columns=["region_id", "cluster"], + ) + numerator = ( + pooled.loc[pooled["contrast_side"] == "numerator"] + .drop(columns=["contrast_side"]) + .rename( + columns={ + "value": "fraction", + "replicate_n": "numerator_replicate_n", + "sample_values": "numerator_sample_values", + } + ) + ) + denominator = ( + pooled.loc[pooled["contrast_side"] == "denominator"] + .drop(columns=["contrast_side"]) + .rename( + columns={ + "value": "reference_fraction", + "replicate_n": "denominator_replicate_n", + "sample_values": "denominator_sample_values", + } + ) + ) + regions_table = numerator.merge( + denominator, + on=["region_id", "cluster"], + how="outer", + sort=False, + ) + regions_table["fraction"] = regions_table["fraction"].fillna(0.0) + regions_table["reference_fraction"] = regions_table[ + "reference_fraction" + ].fillna(0.0) + regions_table["numerator_replicate_n"] = ( + regions_table["numerator_replicate_n"].fillna(0).astype(int) + ) + regions_table["denominator_replicate_n"] = ( + regions_table["denominator_replicate_n"].fillna(0).astype(int) + ) + regions_table["numerator_sample_values"] = regions_table[ + "numerator_sample_values" + ].apply(lambda values: values if isinstance(values, tuple) else tuple()) + regions_table["denominator_sample_values"] = regions_table[ + "denominator_sample_values" + ].apply(lambda values: values if isinstance(values, tuple) else tuple()) + regions_table["delta_fraction"] = ( + regions_table["fraction"] - regions_table["reference_fraction"] + ) + regions_table["log2_fc"] = ( + (regions_table["fraction"] + pseudocount) + / (regions_table["reference_fraction"] + pseudocount) + ).map(math.log2) + regions_table["effect_size"] = regions_table["delta_fraction"].abs() + + if test == "fraction_test": + regions_table = _add_fraction_test_scores( + regions_table, + multiple_testing=multiple_testing, + ) + regions_table = regions_table.sort_values( + by=[ + "adjusted_p_value", + "p_value", + "effect_size", + "region_id", + "cluster", + ], + ascending=[True, True, False, True, True], + kind="mergesort", + ).reset_index(drop=True) + else: + regions_table = regions_table.sort_values( + by=["effect_size", "delta_fraction", "region_id", "cluster"], + ascending=[False, False, True, True], + kind="mergesort", + ).reset_index(drop=True) + regions_table["rank"] = range(1, len(regions_table) + 1) + + summary_columns = [ + "region_id", + "cluster", + "fraction", + "reference_fraction", + "delta_fraction", + "log2_fc", + "rank", + "numerator_replicate_n", + "denominator_replicate_n", + ] + if test == "fraction_test": + summary_columns.extend(["p_value", "adjusted_p_value"]) + summary = regions_table.loc[:, summary_columns].copy() + return regions_table, summary + + if representation == "dominant_cluster": + _require_occupancy_columns( + evidence, + required_columns={ + "region_id", + "sample_id", + "condition", + "dominant_cluster", + }, + ) + side_specs = { + "numerator": contrast.numerator or [], + "denominator": contrast.denominator or [], + } + _require_requested_conditions_present( + evidence, + side_specs=side_specs, + ) + sample_level = evidence.loc[ + :, ["region_id", "sample_id", "condition", "dominant_cluster"] + ].drop_duplicates() + numerator = ( + sample_level.loc[sample_level["condition"].isin(side_specs["numerator"])] + .groupby("region_id", dropna=False, sort=False) + .agg( + dominant_cluster=("dominant_cluster", _dominant_label), + numerator_replicate_n=("sample_id", "nunique"), + ) + .reset_index() + ) + denominator = ( + sample_level.loc[sample_level["condition"].isin(side_specs["denominator"])] + .groupby("region_id", dropna=False, sort=False) + .agg( + reference_dominant_cluster=("dominant_cluster", _dominant_label), + denominator_replicate_n=("sample_id", "nunique"), + ) + .reset_index() + ) + regions_table = numerator.merge( + denominator, + on="region_id", + how="outer", + sort=False, + ) + missing_region_side = ( + regions_table["numerator_replicate_n"].isna() + ^ regions_table["denominator_replicate_n"].isna() + ) + if missing_region_side.any(): + raise ValueError( + "Missing dominant_cluster evidence for one or more requested contrast " + "sides at some regions." + ) + regions_table["numerator_replicate_n"] = ( + regions_table["numerator_replicate_n"].fillna(0).astype(int) + ) + regions_table["denominator_replicate_n"] = ( + regions_table["denominator_replicate_n"].fillna(0).astype(int) + ) + regions_table["dominant_cluster_changed"] = regions_table.apply( + lambda row: _dominant_clusters_differ( + row["dominant_cluster"], + row["reference_dominant_cluster"], + ), + axis=1, + ) + regions_table["effect_size"] = regions_table["dominant_cluster_changed"].astype( + int + ) + regions_table = regions_table.sort_values( + by=["effect_size", "region_id"], + ascending=[False, True], + kind="mergesort", + ).reset_index(drop=True) + regions_table["rank"] = range(1, len(regions_table) + 1) + summary = regions_table.loc[ + :, + [ + "region_id", + "dominant_cluster", + "reference_dominant_cluster", + "dominant_cluster_changed", + "rank", + "numerator_replicate_n", + "denominator_replicate_n", + ], + ].copy() + return regions_table, summary + + _require_occupancy_columns( + evidence, + required_columns={"region_id", "sample_id", "condition", "cluster_entropy"}, + ) + pooled = _pool_cluster_occupancy_groups( + evidence.loc[ + :, ["region_id", "sample_id", "condition", "cluster_entropy"] + ].drop_duplicates(), + contrast, + value_column="cluster_entropy", + group_columns=["region_id"], + ) + numerator = ( + pooled.loc[pooled["contrast_side"] == "numerator"] + .drop(columns=["contrast_side", "sample_values"]) + .rename( + columns={ + "value": "cluster_entropy", + "replicate_n": "numerator_replicate_n", + } + ) + ) + denominator = ( + pooled.loc[pooled["contrast_side"] == "denominator"] + .drop(columns=["contrast_side", "sample_values"]) + .rename( + columns={ + "value": "reference_cluster_entropy", + "replicate_n": "denominator_replicate_n", + } + ) + ) + regions_table = numerator.merge( + denominator, + on="region_id", + how="outer", + sort=False, + ) + regions_table["cluster_entropy"] = regions_table["cluster_entropy"].fillna(0.0) + regions_table["reference_cluster_entropy"] = regions_table[ + "reference_cluster_entropy" + ].fillna(0.0) + regions_table["numerator_replicate_n"] = ( + regions_table["numerator_replicate_n"].fillna(0).astype(int) + ) + regions_table["denominator_replicate_n"] = ( + regions_table["denominator_replicate_n"].fillna(0).astype(int) + ) + regions_table["delta_cluster_entropy"] = ( + regions_table["cluster_entropy"] - regions_table["reference_cluster_entropy"] + ) + regions_table["effect_size"] = regions_table["delta_cluster_entropy"].abs() + regions_table = regions_table.sort_values( + by=["effect_size", "region_id"], + ascending=[False, True], + kind="mergesort", + ).reset_index(drop=True) + regions_table["rank"] = range(1, len(regions_table) + 1) + summary = regions_table.loc[ + :, + [ + "region_id", + "cluster_entropy", + "reference_cluster_entropy", + "delta_cluster_entropy", + "rank", + "numerator_replicate_n", + "denominator_replicate_n", + ], + ].copy() + return regions_table, summary + + +def score_regions( + *, + samples, + regions, + motifs, + contrast: ContrastSpec, + analysis_unit: str = "ensemble_region", + representation: str = "modified_fraction", + signal_source: str = "pileup_counts", + test: str = "effect_size_only", + multiple_testing: str = "fdr_bh", + occupancy_table: pd.DataFrame | None = None, + read_table: pd.DataFrame | None = None, + feature_table: pd.DataFrame | None = None, +) -> RegionContrastResult: + validate_region_contrast_request( + analysis_unit=analysis_unit, + representation=representation, + signal_source=signal_source, + test=test, + ) + contrast_metadata = contrast.metadata or {} + + if analysis_unit == "single_read": + if representation == "read_mod_fraction": + if read_table is None: + raise ValueError( + "single_read read_mod_fraction scoring currently requires read_table." + ) + evidence = build_single_read_mod_fraction_evidence_table( + extract_table=read_table, + pairing_key=contrast.pairing_key + if contrast.mode == "matched_pairwise" + else None, + ) + regions_table, summary = _score_single_read_mod_fraction( + evidence=evidence, + contrast=contrast, + ) + metadata = { + "contrast_mode": contrast.mode, + "analysis_unit": analysis_unit, + "representation": representation, + "signal_source": signal_source, + "test": test, + "multiple_testing": multiple_testing, + "normalization_mode": contrast_metadata.get( + "normalization_mode", "none" + ), + "biological_interpretation": contrast_metadata.get( + "biological_interpretation", + "region-level difference in sample-level read modification fraction", + ), + "renderer": contrast_metadata.get("renderer", "region_effect_sizes"), + } + return RegionContrastResult( + regions=regions_table, + summary=summary, + contrast=contrast, + plot_data={"region_effect_sizes": summary.copy()}, + metadata=metadata, + figures={}, + ) + + if representation == "read_window_features": + if feature_table is None: + feature_table = _load_builtin_single_read_feature_table( + samples=samples, + regions=regions, + motifs=motifs, + ) + evidence = build_single_read_feature_evidence_table( + feature_table=feature_table, + pairing_key=contrast.pairing_key + if contrast.mode == "matched_pairwise" + else None, + ) + regions_table, summary = _score_single_read_features( + evidence=evidence, + contrast=contrast, + ) + metadata = { + "contrast_mode": contrast.mode, + "analysis_unit": analysis_unit, + "representation": representation, + "signal_source": signal_source, + "test": test, + "multiple_testing": multiple_testing, + "normalization_mode": contrast_metadata.get( + "normalization_mode", "none" + ), + "biological_interpretation": contrast_metadata.get( + "biological_interpretation", + "region-level difference in sample-level read feature summaries", + ), + "renderer": contrast_metadata.get("renderer", "region_effect_sizes"), + } + return RegionContrastResult( + regions=regions_table, + summary=summary, + contrast=contrast, + plot_data={"region_effect_sizes": summary.copy()}, + metadata=metadata, + figures={}, + ) + + raise NotImplementedError( + f"Single-read representation '{representation}' is not implemented yet." + ) + + if analysis_unit == "cluster_occupancy": + if occupancy_table is None: + raise ValueError( + "cluster_occupancy scoring currently requires occupancy_table." + ) + _validate_occupancy_table_numeric_columns(occupancy_table) + regions_table, summary = _score_cluster_occupancy( + evidence=occupancy_table.copy(), + contrast=contrast, + representation=representation, + test=test, + multiple_testing=multiple_testing, + ) + metadata = { + "contrast_mode": contrast.mode, + "analysis_unit": analysis_unit, + "representation": representation, + "signal_source": signal_source, + "test": test, + "multiple_testing": multiple_testing, + "normalization_mode": contrast_metadata.get("normalization_mode", "none"), + "biological_interpretation": contrast_metadata.get( + "biological_interpretation", + "region-level difference in sample-level cluster occupancy", + ), + "renderer": contrast_metadata.get("renderer", "region_effect_sizes"), + } + return RegionContrastResult( + regions=regions_table, + summary=summary, + contrast=contrast, + plot_data={"region_effect_sizes": summary.copy()}, + metadata=metadata, + figures={}, + ) + + evidence = build_region_evidence_table( + samples=samples, + regions=regions, + motifs=motifs, + ) + + pooled = _pool_region_groups(evidence=evidence, contrast=contrast) + numerator = ( + pooled.loc[pooled["contrast_side"] == "numerator"] + .drop(columns=["contrast_side"]) + .rename( + columns={ + "modified_count": "numerator_modified_count", + "valid_count": "numerator_valid_count", + "replicate_n": "numerator_replicate_n", + "fraction": "fraction", + } + ) + ) + denominator = ( + pooled.loc[pooled["contrast_side"] == "denominator"] + .drop(columns=["contrast_side"]) + .rename( + columns={ + "modified_count": "denominator_modified_count", + "valid_count": "denominator_valid_count", + "replicate_n": "denominator_replicate_n", + "fraction": "reference_fraction", + } + ) + ) + + merged = numerator.merge( + denominator, + on=["region_id", "chromosome", "start", "end", "strand"], + how="outer", + sort=False, + ) + + for column in [ + "numerator_modified_count", + "numerator_valid_count", + "numerator_replicate_n", + "denominator_modified_count", + "denominator_valid_count", + "denominator_replicate_n", + ]: + merged[column] = merged[column].fillna(0) + + merged["fraction"] = merged["fraction"].fillna(0.0) + merged["reference_fraction"] = merged["reference_fraction"].fillna(0.0) + merged["delta_fraction"] = merged["fraction"] - merged["reference_fraction"] + pseudocount = 1e-6 + merged["log2_fc"] = ( + (merged["fraction"] + pseudocount) + / (merged["reference_fraction"] + pseudocount) + ).map(math.log2) + + integer_columns = [ + "numerator_modified_count", + "numerator_valid_count", + "numerator_replicate_n", + "denominator_modified_count", + "denominator_valid_count", + "denominator_replicate_n", + ] + merged[integer_columns] = merged[integer_columns].astype(int) + + if representation == "modified_count": + merged["count"] = merged["numerator_modified_count"] + merged["reference_count"] = merged["denominator_modified_count"] + merged["delta_count"] = merged["count"] - merged["reference_count"] + merged["log2_fc_count"] = ( + (merged["count"] + pseudocount) / (merged["reference_count"] + pseudocount) + ).map(math.log2) + merged["effect_size"] = merged["delta_count"].abs() + else: + merged["effect_size"] = merged["delta_fraction"].abs() + + if test == "beta_binomial": + regions_table = _add_beta_binomial_scores( + merged, + multiple_testing=multiple_testing, + ) + regions_table = regions_table.sort_values( + by=["adjusted_p_value", "p_value"], + ascending=[True, True], + kind="mergesort", + ).reset_index(drop=True) + else: + regions_table = merged.sort_values( + by="effect_size", + ascending=False, + kind="mergesort", + ).reset_index(drop=True) + regions_table["rank"] = range(1, len(regions_table) + 1) + + summary_columns = [ + "region_id", + "fraction", + "reference_fraction", + "delta_fraction", + "log2_fc", + "rank", + "numerator_modified_count", + "numerator_valid_count", + "numerator_replicate_n", + "denominator_modified_count", + "denominator_valid_count", + "denominator_replicate_n", + ] + if test == "beta_binomial": + summary_columns.extend(["p_value", "adjusted_p_value"]) + if representation == "modified_count": + summary_columns.extend( + ["count", "reference_count", "delta_count", "log2_fc_count"] + ) + summary = regions_table.loc[:, summary_columns].copy() + + metadata = { + "contrast_mode": contrast.mode, + "analysis_unit": analysis_unit, + "representation": representation, + "signal_source": signal_source, + "test": test, + "multiple_testing": multiple_testing, + "normalization_mode": contrast_metadata.get("normalization_mode", "none"), + "biological_interpretation": contrast_metadata.get( + "biological_interpretation", + "region-level difference in pooled modified fraction", + ), + "renderer": contrast_metadata.get("renderer", "region_effect_sizes"), + } + + return RegionContrastResult( + regions=regions_table, + summary=summary, + contrast=contrast, + plot_data={"region_effect_sizes": summary.copy()}, + metadata=metadata, + figures={}, + ) diff --git a/dimelo/region_discovery.py b/dimelo/region_discovery.py new file mode 100644 index 0000000..a189a0c --- /dev/null +++ b/dimelo/region_discovery.py @@ -0,0 +1,1247 @@ +from __future__ import annotations + +from collections.abc import Iterable + +import pandas as pd + +from . import global_analysis +from .models import ContrastSpec, RegionDiscoveryResult +from .region_contrasts import ( + _adjust_p_values_bh, + _beta_binomial_two_sided_p_value, + _estimate_beta_binomial_prior, +) + +_WINDOW_KEY_COLUMNS = ["window_id", "chromosome", "start", "end", "strand"] +_MERGE_SORT_COLUMNS = ["chromosome", "strand", "start", "end", "rank"] +_OUTPUT_SORT_COLUMNS = ["rank", "chromosome", "strand", "start", "end"] +_BED_COLUMNS = ["chrom", "start", "end", "name", "score", "strand"] + + +def _validate_motifs(motifs: Iterable[str]) -> list[str]: + motif_list = list(motifs) + if len(motif_list) != 1: + raise ValueError("scan_genome currently supports exactly one motif.") + return motif_list + + +def _safe_fraction(modified_count: pd.Series, valid_count: pd.Series) -> pd.Series: + return modified_count.div(valid_count.where(valid_count != 0), fill_value=0).fillna( + 0.0 + ) + + +def _aggregate_window_counts(summary: pd.DataFrame) -> pd.DataFrame: + if summary.empty: + return pd.DataFrame( + columns=_WINDOW_KEY_COLUMNS + + ["modified_count", "valid_count", "window_fraction"] + ) + + aggregated = ( + summary.groupby(_WINDOW_KEY_COLUMNS, as_index=False, sort=False) + .agg( + modified_count=("modified_count", "sum"), + valid_count=("valid_count", "sum"), + ) + .copy() + ) + aggregated["window_fraction"] = _safe_fraction( + aggregated["modified_count"], aggregated["valid_count"] + ) + return aggregated + + +def _aggregate_condition_counts(summary: pd.DataFrame) -> pd.DataFrame: + if summary.empty: + return pd.DataFrame( + columns=_WINDOW_KEY_COLUMNS + + ["condition", "modified_count", "valid_count", "window_fraction"] + ) + + aggregated = ( + summary.groupby(_WINDOW_KEY_COLUMNS + ["condition"], as_index=False, sort=False) + .agg( + modified_count=("modified_count", "sum"), + valid_count=("valid_count", "sum"), + ) + .copy() + ) + aggregated["window_fraction"] = _safe_fraction( + aggregated["modified_count"], aggregated["valid_count"] + ) + return aggregated + + +def _condition_spread_scores(condition_counts: pd.DataFrame) -> pd.DataFrame: + if condition_counts.empty: + return pd.DataFrame(columns=_WINDOW_KEY_COLUMNS + ["score_value"]) + + spread = ( + condition_counts.groupby(_WINDOW_KEY_COLUMNS, as_index=False, sort=False) + .agg( + score_value=( + "window_fraction", + lambda values: float(values.max() - values.min()), + ), + ) + .copy() + ) + return spread + + +def _side_counts( + condition_counts: pd.DataFrame, + *, + conditions: Iterable[str], + side_name: str, +) -> pd.DataFrame: + side_conditions = list(conditions) + if not side_conditions: + return pd.DataFrame( + columns=_WINDOW_KEY_COLUMNS + + [f"{side_name}_modified_count", f"{side_name}_valid_count"] + ) + + side = condition_counts.loc[ + condition_counts["condition"].isin(side_conditions) + ].copy() + if side.empty: + return pd.DataFrame( + columns=_WINDOW_KEY_COLUMNS + + [f"{side_name}_modified_count", f"{side_name}_valid_count"] + ) + + grouped = ( + side.groupby(_WINDOW_KEY_COLUMNS, as_index=False, sort=False) + .agg( + **{ + f"{side_name}_modified_count": ("modified_count", "sum"), + f"{side_name}_valid_count": ("valid_count", "sum"), + } + ) + .copy() + ) + return grouped + + +def _validate_contrast_conditions( + *, + condition_counts: pd.DataFrame, + contrast: ContrastSpec | None, +) -> None: + if contrast is None: + return + + available_conditions = set(condition_counts["condition"].dropna().tolist()) + missing: list[str] = [] + for side_name, conditions in ( + ("numerator", contrast.numerator or []), + ("denominator", contrast.denominator or []), + ): + absent = sorted(set(conditions) - available_conditions) + if absent: + missing.append(f"{side_name}: {', '.join(absent)}") + + if missing: + raise ValueError( + "scan_genome contrast requested missing condition(s): " + "; ".join(missing) + ) + + +def _pairing_policy_value(pairing_policy: str | None) -> str: + active_pairing_policy = pairing_policy or "complete_pairs_only" + if active_pairing_policy not in {"complete_pairs_only", "error_on_missing"}: + raise ValueError( + "scan_genome paired discovery requires pairing_policy in " + "{'complete_pairs_only', 'error_on_missing'}." + ) + return active_pairing_policy + + +def _is_paired_contrast(contrast: ContrastSpec | None) -> bool: + return contrast is not None and contrast.mode in {"matched_pairwise", "time_course"} + + +def _resolve_pair_ids(samples, pairing_key: str) -> dict[str, object]: + if not pairing_key: + raise ValueError( + "scan_genome paired discovery requires an explicit pairing_key." + ) + + pair_ids: dict[str, object] = {} + for sample in samples: + metadata = sample.metadata or {} + if pairing_key not in metadata: + raise ValueError( + f"scan_genome paired discovery requires sample.metadata['{pairing_key}'] for every sample." + ) + pair_ids[sample.sample_id] = metadata[pairing_key] + return pair_ids + + +def _build_paired_window_table( + summary: pd.DataFrame, + *, + samples, + pairing_key: str | None, + required_conditions: list[str], + pairing_policy: str, +) -> tuple[pd.DataFrame, dict[str, int]]: + sample_to_pair = _resolve_pair_ids(samples, pairing_key or "") + + paired = summary.copy() + paired["pair_id"] = paired["sample_id"].map(sample_to_pair) + paired = paired.dropna(subset=["pair_id"]) + + pair_conditions = ( + paired.loc[:, ["pair_id", "condition"]] + .drop_duplicates() + .groupby("pair_id")["condition"] + .agg(lambda values: set(values)) + ) + required_condition_set = set(required_conditions) + complete_pair_ids = [ + pair_id + for pair_id, conditions in pair_conditions.items() + if required_condition_set.issubset(conditions) + ] + dropped_pair_count = int(len(pair_conditions) - len(complete_pair_ids)) + + if pairing_policy == "error_on_missing" and dropped_pair_count: + raise ValueError("scan_genome paired discovery found incomplete matched units.") + if not complete_pair_ids: + raise ValueError( + "scan_genome paired discovery found no complete matched units." + ) + + paired = paired.loc[paired["pair_id"].isin(complete_pair_ids)].copy() + paired = ( + paired.groupby( + _WINDOW_KEY_COLUMNS + ["pair_id", "condition"], as_index=False, sort=False + ) + .agg( + modified_count=("modified_count", "sum"), + valid_count=("valid_count", "sum"), + ) + .copy() + ) + paired["window_fraction"] = _safe_fraction( + paired["modified_count"], paired["valid_count"] + ) + return paired, { + "n_pairs_used": len(complete_pair_ids), + "n_pairs_dropped": dropped_pair_count, + } + + +def _score_matched_pairwise( + paired_window_table: pd.DataFrame, + *, + contrast: ContrastSpec, + rank_by: str = "mean_abs_delta", +) -> pd.DataFrame: + if rank_by != "mean_abs_delta": + raise ValueError( + "scan_genome matched_pairwise currently supports rank_by='mean_abs_delta'." + ) + + numerator = paired_window_table.loc[ + paired_window_table["condition"].isin(contrast.numerator or []) + ].copy() + denominator = paired_window_table.loc[ + paired_window_table["condition"].isin(contrast.denominator or []) + ].copy() + + if numerator.empty or denominator.empty: + scored = pd.DataFrame(columns=_WINDOW_KEY_COLUMNS) + scored["mean_delta"] = pd.Series(dtype="float64") + scored["mean_abs_delta"] = pd.Series(dtype="float64") + scored["delta_sd"] = pd.Series(dtype="float64") + scored["sign_agreement"] = pd.Series(dtype="float64") + scored["n_pairs_used"] = pd.Series(dtype="int64") + scored["score_value"] = pd.Series(dtype="float64") + scored["p_value"] = pd.Series(dtype="object") + scored["adjusted_p_value"] = pd.Series(dtype="object") + return scored + + numerator = numerator.rename( + columns={ + "modified_count": "numerator_modified_count", + "valid_count": "numerator_valid_count", + "window_fraction": "numerator_fraction", + } + ) + denominator = denominator.rename( + columns={ + "modified_count": "denominator_modified_count", + "valid_count": "denominator_valid_count", + "window_fraction": "denominator_fraction", + } + ) + + merged = numerator.merge( + denominator[ + _WINDOW_KEY_COLUMNS + + [ + "pair_id", + "denominator_modified_count", + "denominator_valid_count", + "denominator_fraction", + ] + ], + on=_WINDOW_KEY_COLUMNS + ["pair_id"], + how="inner", + ) + if merged.empty: + scored = pd.DataFrame(columns=_WINDOW_KEY_COLUMNS) + scored["mean_delta"] = pd.Series(dtype="float64") + scored["mean_abs_delta"] = pd.Series(dtype="float64") + scored["delta_sd"] = pd.Series(dtype="float64") + scored["sign_agreement"] = pd.Series(dtype="float64") + scored["n_pairs_used"] = pd.Series(dtype="int64") + scored["score_value"] = pd.Series(dtype="float64") + scored["p_value"] = pd.Series(dtype="object") + scored["adjusted_p_value"] = pd.Series(dtype="object") + return scored + + merged["delta"] = merged["numerator_fraction"] - merged["denominator_fraction"] + + scored = ( + merged.groupby(_WINDOW_KEY_COLUMNS, as_index=False, sort=False) + .agg( + mean_delta=("delta", "mean"), + mean_abs_delta=("delta", lambda values: float(values.abs().mean())), + delta_sd=("delta", lambda values: float(values.std(ddof=0))), + sign_agreement=( + "delta", + lambda values: float( + max((values.gt(0)).mean(), (values.lt(0)).mean()) + if len(values) + else 0.0 + ), + ), + n_pairs_used=("pair_id", "nunique"), + ) + .copy() + ) + scored["score_value"] = scored["mean_abs_delta"] + scored["p_value"] = pd.NA + scored["adjusted_p_value"] = pd.NA + return scored + + +def _validate_time_order( + paired_window_table: pd.DataFrame, time_order: list[str] +) -> None: + available_conditions = set(paired_window_table["condition"].dropna().tolist()) + missing = [ + condition for condition in time_order if condition not in available_conditions + ] + if missing: + raise ValueError( + "scan_genome paired time_course requested missing time_order condition(s): " + + ", ".join(missing) + ) + + +def _score_paired_time_course( + paired_window_table: pd.DataFrame, + *, + time_order: list[str], +) -> pd.DataFrame: + if paired_window_table.empty: + scored = pd.DataFrame(columns=_WINDOW_KEY_COLUMNS) + scored["trajectory_amplitude_mean"] = pd.Series(dtype="float64") + scored["trajectory_amplitude_sd"] = pd.Series(dtype="float64") + scored["n_pairs_used"] = pd.Series(dtype="int64") + scored["score_value"] = pd.Series(dtype="float64") + scored["p_value"] = pd.Series(dtype="object") + scored["adjusted_p_value"] = pd.Series(dtype="object") + return scored + + ordered = paired_window_table.loc[ + paired_window_table["condition"].isin(time_order) + ].copy() + _validate_time_order(ordered, time_order) + ordered["condition"] = pd.Categorical( + ordered["condition"], + categories=time_order, + ordered=True, + ) + ordered = ordered.sort_values( + by=_WINDOW_KEY_COLUMNS + ["pair_id", "condition"], + ascending=[True] * (len(_WINDOW_KEY_COLUMNS) + 2), + kind="mergesort", + na_position="last", + ).reset_index(drop=True) + + complete_trajectories = ( + ordered.drop_duplicates(subset=_WINDOW_KEY_COLUMNS + ["pair_id", "condition"]) + .groupby(_WINDOW_KEY_COLUMNS + ["pair_id"], as_index=False, sort=False) + .agg(trajectory_condition_count=("condition", "nunique")) + ) + complete_trajectory_ids = complete_trajectories.loc[ + complete_trajectories["trajectory_condition_count"] == len(time_order), + _WINDOW_KEY_COLUMNS + ["pair_id"], + ] + if complete_trajectory_ids.empty: + scored = pd.DataFrame(columns=_WINDOW_KEY_COLUMNS) + scored["trajectory_amplitude_mean"] = pd.Series(dtype="float64") + scored["trajectory_amplitude_sd"] = pd.Series(dtype="float64") + scored["n_pairs_used"] = pd.Series(dtype="int64") + scored["score_value"] = pd.Series(dtype="float64") + scored["p_value"] = pd.Series(dtype="object") + scored["adjusted_p_value"] = pd.Series(dtype="object") + return scored + + ordered = ordered.merge( + complete_trajectory_ids, + on=_WINDOW_KEY_COLUMNS + ["pair_id"], + how="inner", + sort=False, + ) + per_pair = ( + ordered.groupby(_WINDOW_KEY_COLUMNS + ["pair_id"], as_index=False, sort=False) + .agg( + trajectory_amplitude=( + "window_fraction", + lambda values: float(values.max() - values.min()), + ), + ) + .copy() + ) + + scored = ( + per_pair.groupby(_WINDOW_KEY_COLUMNS, as_index=False, sort=False) + .agg( + trajectory_amplitude_mean=("trajectory_amplitude", "mean"), + trajectory_amplitude_sd=( + "trajectory_amplitude", + lambda values: float(values.std(ddof=0)), + ), + n_pairs_used=("pair_id", "nunique"), + ) + .copy() + ) + scored["score_value"] = scored["trajectory_amplitude_mean"] + scored["p_value"] = pd.NA + scored["adjusted_p_value"] = pd.NA + return scored + + +def _score_with_contrast( + window_totals: pd.DataFrame, + condition_counts: pd.DataFrame, + *, + contrast: ContrastSpec | None, + score: str, +) -> pd.DataFrame: + if score == "beta_binomial": + available_conditions = sorted( + condition_counts["condition"].dropna().unique().tolist() + ) + if contrast is not None and contrast.numerator and contrast.denominator: + numerator_conditions = list(contrast.numerator) + denominator_conditions = list(contrast.denominator) + elif len(available_conditions) >= 2: + numerator_conditions = [available_conditions[0]] + denominator_conditions = [available_conditions[1]] + else: + numerator_conditions = [] + denominator_conditions = [] + + if not numerator_conditions and not denominator_conditions: + scored = window_totals.copy() + scored["score_value"] = 0.0 + scored["p_value"] = 1.0 + scored["adjusted_p_value"] = 1.0 + return scored + + numerator_counts = _side_counts( + condition_counts, conditions=numerator_conditions, side_name="numerator" + ) + denominator_counts = _side_counts( + condition_counts, conditions=denominator_conditions, side_name="denominator" + ) + + scored = window_totals.merge( + numerator_counts, on=_WINDOW_KEY_COLUMNS, how="left" + ) + scored = scored.merge(denominator_counts, on=_WINDOW_KEY_COLUMNS, how="left") + + for column in [ + "numerator_modified_count", + "numerator_valid_count", + "denominator_modified_count", + "denominator_valid_count", + ]: + scored[column] = scored[column].fillna(0).astype(int) + + scored["score_value"] = ( + _safe_fraction( + scored["numerator_modified_count"], scored["numerator_valid_count"] + ) + .sub( + _safe_fraction( + scored["denominator_modified_count"], + scored["denominator_valid_count"], + ) + ) + .abs() + ) + + scored["p_value"] = [ + _beta_binomial_two_sided_p_value( + int(numerator_modified_count), + int(numerator_valid_count), + *_estimate_beta_binomial_prior( + int(denominator_modified_count), + int(denominator_valid_count), + ), + ) + for numerator_modified_count, numerator_valid_count, denominator_modified_count, denominator_valid_count in zip( + scored["numerator_modified_count"], + scored["numerator_valid_count"], + scored["denominator_modified_count"], + scored["denominator_valid_count"], + strict=False, + ) + ] + scored["adjusted_p_value"] = _adjust_p_values_bh(scored["p_value"]) + return scored + + if contrast is None or not contrast.numerator or not contrast.denominator: + spread = _condition_spread_scores(condition_counts) + scored = window_totals.merge(spread, on=_WINDOW_KEY_COLUMNS, how="left") + scored["score_value"] = scored["score_value"].fillna(0.0) + scored["p_value"] = pd.NA + scored["adjusted_p_value"] = pd.NA + return scored + + numerator_counts = _side_counts( + condition_counts, conditions=contrast.numerator, side_name="numerator" + ) + denominator_counts = _side_counts( + condition_counts, conditions=contrast.denominator, side_name="denominator" + ) + + scored = window_totals.merge(numerator_counts, on=_WINDOW_KEY_COLUMNS, how="left") + scored = scored.merge(denominator_counts, on=_WINDOW_KEY_COLUMNS, how="left") + + for column in [ + "numerator_modified_count", + "numerator_valid_count", + "denominator_modified_count", + "denominator_valid_count", + ]: + scored[column] = scored[column].fillna(0).astype(int) + + scored["score_value"] = ( + _safe_fraction( + scored["numerator_modified_count"], scored["numerator_valid_count"] + ) + .sub( + _safe_fraction( + scored["denominator_modified_count"], scored["denominator_valid_count"] + ) + ) + .abs() + ) + + if score == "beta_binomial": + scored["p_value"] = [ + _beta_binomial_two_sided_p_value( + int(numerator_modified_count), + int(numerator_valid_count), + *_estimate_beta_binomial_prior( + int(denominator_modified_count), + int(denominator_valid_count), + ), + ) + for numerator_modified_count, numerator_valid_count, denominator_modified_count, denominator_valid_count in zip( + scored["numerator_modified_count"], + scored["numerator_valid_count"], + scored["denominator_modified_count"], + scored["denominator_valid_count"], + strict=False, + ) + ] + else: + scored["p_value"] = pd.NA + scored["adjusted_p_value"] = pd.NA + + return scored + + +def _rank_windows(scored: pd.DataFrame, *, score: str) -> pd.DataFrame: + if scored.empty: + return scored.assign(rank=pd.Series(dtype="float64")) + + if score == "beta_binomial": + ranked = scored.sort_values( + by=[ + "adjusted_p_value", + "p_value", + "score_value", + "chromosome", + "start", + "end", + ], + ascending=[True, True, False, True, True, True], + kind="mergesort", + na_position="last", + ).reset_index(drop=True) + else: + ranked = scored.sort_values( + by=["score_value", "chromosome", "start", "end"], + ascending=[False, True, True, True], + kind="mergesort", + na_position="last", + ).reset_index(drop=True) + + ranked["rank"] = range(1, len(ranked) + 1) + return ranked + + +def _apply_min_coverage( + scored: pd.DataFrame, + *, + min_coverage: int, +) -> tuple[pd.DataFrame, pd.DataFrame]: + if scored.empty: + return scored.copy(), scored.copy() + + filtered = scored.copy() + covered = filtered["valid_count"] >= min_coverage + filtered.loc[~covered, ["score_value", "p_value", "adjusted_p_value", "rank"]] = ( + pd.NA + ) + hits = filtered.loc[covered].copy() + return filtered, hits + + +def _sort_hits_for_merge(hits: pd.DataFrame) -> pd.DataFrame: + sort_columns = [column for column in _MERGE_SORT_COLUMNS if column in hits.columns] + if not sort_columns: + return hits.copy() + + ordered = hits.sort_values( + by=sort_columns, + ascending=[True] * len(sort_columns), + kind="mergesort", + na_position="last", + ).reset_index(drop=True) + return ordered + + +def _sort_hits_for_output(hits: pd.DataFrame) -> pd.DataFrame: + sort_columns = [column for column in _OUTPUT_SORT_COLUMNS if column in hits.columns] + if not sort_columns: + return hits.copy() + + ascending = [True] * len(sort_columns) + ordered = hits.sort_values( + by=sort_columns, + ascending=ascending, + kind="mergesort", + na_position="last", + ).reset_index(drop=True) + return ordered + + +def _merge_value(values: pd.Series, *, prefer: str) -> object: + non_null = values.dropna() + if non_null.empty: + return pd.NA + if prefer == "max": + return non_null.max() + if prefer == "min": + return non_null.min() + raise ValueError(f"Unsupported merge preference: {prefer}") + + +def _build_merged_hit(group: pd.DataFrame) -> dict[str, object]: + merged = group.iloc[0].to_dict() + + if {"chromosome", "start", "end"}.issubset(group.columns): + merged["chromosome"] = group.iloc[0]["chromosome"] + merged["start"] = int(group["start"].min()) + merged["end"] = int(group["end"].max()) + + if "modified_count" in group.columns: + merged["modified_count"] = int(group["modified_count"].fillna(0).sum()) + if "valid_count" in group.columns: + merged["valid_count"] = int(group["valid_count"].fillna(0).sum()) + if {"modified_count", "valid_count"}.issubset(merged): + valid_count = int(merged.get("valid_count", 0)) + modified_count = int(merged.get("modified_count", 0)) + merged["window_fraction"] = ( + 0.0 if valid_count == 0 else modified_count / valid_count + ) + + if "score_value" in group.columns: + merged["score_value"] = _merge_value(group["score_value"], prefer="max") + if "p_value" in group.columns: + merged["p_value"] = _merge_value(group["p_value"], prefer="min") + if "adjusted_p_value" in group.columns: + merged["adjusted_p_value"] = _merge_value( + group["adjusted_p_value"], prefer="min" + ) + if "rank" in group.columns: + rank_values = group["rank"].dropna() + if not rank_values.empty: + merged["rank"] = int(rank_values.min()) + + contrast_count_fields = [ + "numerator_modified_count", + "numerator_valid_count", + "denominator_modified_count", + "denominator_valid_count", + ] + contrast_counts_present = any( + field in group.columns and group[field].notna().any() + for field in contrast_count_fields + ) + if contrast_counts_present: + for field in contrast_count_fields: + if field in group.columns and group[field].notna().any(): + merged[field] = int(group[field].fillna(0).sum()) + + merged_window_count = 0 + if "merged_window_count" in group.columns: + merged_window_count = int(group["merged_window_count"].fillna(1).sum()) + else: + merged_window_count = len(group) + merged["merged_window_count"] = merged_window_count + + if contrast_counts_present and all( + field in merged and not pd.isna(merged[field]) + for field in contrast_count_fields + ): + numerator_fraction = _safe_fraction( + pd.Series([merged["numerator_modified_count"]], dtype="float64"), + pd.Series([merged["numerator_valid_count"]], dtype="float64"), + ).iloc[0] + denominator_fraction = _safe_fraction( + pd.Series([merged["denominator_modified_count"]], dtype="float64"), + pd.Series([merged["denominator_valid_count"]], dtype="float64"), + ).iloc[0] + merged["score_value"] = abs( + float(numerator_fraction) - float(denominator_fraction) + ) + + if {"chromosome", "start", "end"}.issubset(merged): + merged["window_id"] = ( + f"{merged['chromosome']}:{int(merged['start'])}-{int(merged['end'])}" + ) + + return merged + + +def _build_window_id_series(frame: pd.DataFrame) -> pd.Series: + return ( + frame["chromosome"].astype(str) + + ":" + + frame["start"].astype("int64").astype(str) + + "-" + + frame["end"].astype("int64").astype(str) + ) + + +def merge_adjacent_hits(hits: pd.DataFrame, merge_distance: int) -> pd.DataFrame: + if hits.empty: + merged = hits.copy() + if "merged_window_count" not in merged.columns: + merged["merged_window_count"] = pd.Series(dtype="int64") + return merged + + ordered = _sort_hits_for_merge(hits) + if len(ordered) == 1: + merged = ordered.copy() + merged["merged_window_count"] = ( + merged["merged_window_count"] + if "merged_window_count" in merged.columns + else 1 + ) + if {"chromosome", "start", "end"}.issubset(merged.columns): + merged["window_id"] = _build_window_id_series(merged) + if {"modified_count", "valid_count"}.issubset(merged.columns): + merged["window_fraction"] = _safe_fraction( + merged["modified_count"], merged["valid_count"] + ) + return merged + + same_chromosome = pd.Series(True, index=ordered.index) + if "chromosome" in ordered.columns: + same_chromosome = ordered["chromosome"].eq(ordered["chromosome"].shift()) + + same_strand = pd.Series(True, index=ordered.index) + if "strand" in ordered.columns: + same_strand = ordered["strand"].eq(ordered["strand"].shift()) + + within_distance = ordered["start"].le(ordered["end"].shift().add(merge_distance)) + merge_with_previous = (same_chromosome & same_strand & within_distance).fillna( + False + ) + merge_group = (~merge_with_previous).cumsum() + + merged_rows = [ + _build_merged_hit(group) + for _, group in ordered.groupby(merge_group, sort=False, group_keys=False) + ] + merged = pd.DataFrame.from_records(merged_rows) + merged = _sort_hits_for_output(merged) + + if "window_id" not in merged.columns and {"chromosome", "start", "end"}.issubset( + merged.columns + ): + merged["window_id"] = _build_window_id_series(merged) + + if "merged_window_count" not in merged.columns: + merged["merged_window_count"] = 1 + + if {"modified_count", "valid_count"}.issubset( + merged.columns + ) and "window_fraction" not in merged.columns: + merged["window_fraction"] = _safe_fraction( + merged["modified_count"], merged["valid_count"] + ) + + ordered_columns = list(hits.columns) + if "rank" in merged.columns and "rank" not in ordered_columns: + ordered_columns.append("rank") + for column in ["window_id", "window_fraction", "merged_window_count"]: + if column in merged.columns and column not in ordered_columns: + ordered_columns.append(column) + merged = merged.loc[ + :, [column for column in ordered_columns if column in merged.columns] + ] + return merged.reset_index(drop=True) + + +def hits_to_bed(hits: pd.DataFrame) -> pd.DataFrame: + if hits.empty: + return pd.DataFrame(columns=_BED_COLUMNS) + + ordered = _sort_hits_for_output(hits) + + chrom_column = "chromosome" if "chromosome" in ordered.columns else "chrom" + chrom = ordered[chrom_column] + if "window_id" in ordered.columns: + name = ordered["window_id"] + else: + name = ordered.apply( + lambda row: ( + f"{row.get('chromosome', row.get('chrom'))}:{int(row['start'])}-{int(row['end'])}" + ), + axis=1, + ) + if "score_value" in ordered.columns: + score = ( + ordered["score_value"] + .fillna(0.0) + .mul(1000) + .round() + .clip(lower=0, upper=1000) + .astype(int) + ) + else: + score = pd.Series([0] * len(ordered), index=ordered.index, dtype="int64") + strand = ordered["strand"] if "strand" in ordered.columns else "." + strand = ( + strand.where(strand.isin({"+", "-"}), ".") + if isinstance(strand, pd.Series) + else strand + ) + + bed = pd.DataFrame( + { + "chrom": chrom, + "start": ordered["start"], + "end": ordered["end"], + "name": name, + "score": score, + "strand": strand, + } + ) + return bed.loc[:, _BED_COLUMNS].reset_index(drop=True) + + +def _merge_adjacent_hits(hits: pd.DataFrame, *, merge_distance: int) -> pd.DataFrame: + return merge_adjacent_hits(hits, merge_distance=merge_distance) + + +def scan_genome( + *, + samples, + motifs: Iterable[str], + genome_sizes: dict[str, int], + window_size: int, + step_size: int, + include_contigs: Iterable[str] | None = None, + exclude_contigs: Iterable[str] | None = None, + min_coverage: int = 0, + merge_hits: bool = False, + merge_distance: int = 0, + score: str = "effect_size_only", + contrast: ContrastSpec | None = None, + pairing_policy: str | None = None, + rank_by: str | None = None, + quiet: bool = True, + cores: int | None = None, +) -> RegionDiscoveryResult: + motif_list = _validate_motifs(motifs) + if score not in {"effect_size_only", "beta_binomial"}: + raise ValueError( + "scan_genome requires score in {'effect_size_only', 'beta_binomial'}." + ) + if score == "beta_binomial" and contrast is None: + raise ValueError( + "scan_genome score='beta_binomial' requires an explicit contrast." + ) + if contrast is not None and contrast.mode not in { + "pairwise", + "group_vs_group", + "matched_pairwise", + "time_course", + }: + raise ValueError( + "scan_genome currently supports only pairwise/group_vs_group and paired contrast modes." + ) + + window_summary = global_analysis.build_window_summary( + samples=samples, + motifs=motif_list, + genome_sizes=genome_sizes, + window_size=window_size, + step_size=step_size, + include_contigs=include_contigs, + exclude_contigs=exclude_contigs, + quiet=quiet, + cores=cores, + ) + + pairing_metadata: dict[str, object] = {} + active_pairing_policy = _pairing_policy_value(pairing_policy) + active_rank_by = rank_by + if _is_paired_contrast(contrast): + if contrast.mode == "time_course": + if not contrast.pairing_key: + raise ValueError( + "scan_genome paired discovery requires an explicit pairing_key." + ) + active_rank_by = active_rank_by or "trajectory_amplitude_mean" + if active_rank_by != "trajectory_amplitude_mean": + raise ValueError( + "scan_genome time_course currently supports rank_by='trajectory_amplitude_mean'." + ) + time_order = list(dict.fromkeys(contrast.time_order or [])) + available_conditions = set(window_summary["condition"].dropna().tolist()) + missing_time_order = [ + condition + for condition in time_order + if condition not in available_conditions + ] + if missing_time_order: + raise ValueError( + "scan_genome paired time_course requested missing time_order condition(s): " + + ", ".join(missing_time_order) + ) + if contrast.mode == "matched_pairwise": + numerator_conditions = list(contrast.numerator or []) + denominator_conditions = list(contrast.denominator or []) + if len(numerator_conditions) != 1 or len(denominator_conditions) != 1: + raise ValueError( + "scan_genome matched_pairwise requires exactly one numerator and one denominator condition." + ) + required_conditions = ( + list(contrast.time_order or []) + if contrast.mode == "time_course" + else list( + dict.fromkeys((contrast.numerator or []) + (contrast.denominator or [])) + ) + ) + window_summary, pairing_metadata = _build_paired_window_table( + window_summary, + samples=samples, + pairing_key=contrast.pairing_key, + required_conditions=required_conditions, + pairing_policy=active_pairing_policy, + ) + if contrast.mode == "matched_pairwise": + if score != "effect_size_only": + raise ValueError( + "scan_genome matched_pairwise currently supports score='effect_size_only'." + ) + if merge_hits: + raise ValueError( + "scan_genome matched_pairwise does not support merge_hits=True." + ) + active_rank_by = active_rank_by or "mean_abs_delta" + window_totals = _aggregate_window_counts(window_summary) + scored = _score_matched_pairwise( + window_summary, + contrast=contrast, + rank_by=active_rank_by, + ) + ranked = _rank_windows(scored, score=score) + scored_columns = [ + column + for column in ranked.columns + if column not in _WINDOW_KEY_COLUMNS + and column not in {"modified_count", "valid_count", "window_fraction"} + ] + window_table = window_totals.merge( + ranked.loc[:, _WINDOW_KEY_COLUMNS + scored_columns], + on=_WINDOW_KEY_COLUMNS, + how="left", + sort=False, + ) + covered_mask = window_table["valid_count"] >= min_coverage + paired_score_columns = [ + column + for column in [ + "mean_delta", + "mean_abs_delta", + "delta_sd", + "sign_agreement", + "n_pairs_used", + "score_value", + "p_value", + "adjusted_p_value", + "rank", + ] + if column in window_table.columns + ] + window_table.loc[~covered_mask, paired_score_columns] = pd.NA + covered_hits = ranked.merge( + window_totals.loc[ + :, + _WINDOW_KEY_COLUMNS + + ["modified_count", "valid_count", "window_fraction"], + ], + on=_WINDOW_KEY_COLUMNS, + how="left", + sort=False, + ) + hits = covered_hits.loc[covered_hits["valid_count"] >= min_coverage].copy() + if not hits.empty: + hits["rank"] = range(1, len(hits) + 1) + rank_lookup = hits.set_index("window_id")["rank"] + window_table["rank"] = window_table["window_id"].map(rank_lookup) + + plot_data = { + "window_score_table": window_table.copy(), + "top_hits_table": hits.copy(), + } + metadata = { + "analysis_unit": "ensemble_region", + "representation": "modified_fraction", + "signal_source": "pileup_counts", + "score": score, + "window_size": window_size, + "step_size": step_size, + "min_coverage": min_coverage, + "merge_hits": merge_hits, + "merge_distance": merge_distance, + "motifs": motif_list, + "include_contigs": list(include_contigs) + if include_contigs is not None + else None, + "exclude_contigs": list(exclude_contigs) + if exclude_contigs is not None + else None, + "contrast_mode": contrast.mode, + "contrast_numerator": list(contrast.numerator or []), + "contrast_denominator": list(contrast.denominator or []), + "pairing_key": contrast.pairing_key, + "pairing_policy": active_pairing_policy, + "n_pairs_used": pairing_metadata["n_pairs_used"], + "n_pairs_dropped": pairing_metadata["n_pairs_dropped"], + "paired_mode": contrast.mode, + "rank_by": active_rank_by, + } + return RegionDiscoveryResult( + hits=hits.reset_index(drop=True), + windows=window_table.reset_index(drop=True), + contrast=contrast, + plot_data=plot_data, + metadata=metadata, + figures={}, + ) + if contrast.mode == "time_course": + if score != "effect_size_only": + raise ValueError( + "scan_genome time_course currently supports score='effect_size_only'." + ) + if merge_hits: + raise ValueError( + "scan_genome time_course does not support merge_hits=True." + ) + time_order = list(dict.fromkeys(contrast.time_order or [])) + window_totals = _aggregate_window_counts(window_summary) + scored = _score_paired_time_course(window_summary, time_order=time_order) + ranked = _rank_windows(scored, score=score) + scored_columns = [ + column + for column in ranked.columns + if column not in _WINDOW_KEY_COLUMNS + and column not in {"modified_count", "valid_count", "window_fraction"} + ] + window_table = window_totals.merge( + ranked.loc[:, _WINDOW_KEY_COLUMNS + scored_columns], + on=_WINDOW_KEY_COLUMNS, + how="left", + sort=False, + ) + covered_mask = window_table["valid_count"] >= min_coverage + paired_score_columns = [ + column + for column in [ + "trajectory_amplitude_mean", + "trajectory_amplitude_sd", + "n_pairs_used", + "score_value", + "p_value", + "adjusted_p_value", + "rank", + ] + if column in window_table.columns + ] + window_table.loc[~covered_mask, paired_score_columns] = pd.NA + covered_hits = ranked.merge( + window_totals.loc[ + :, + _WINDOW_KEY_COLUMNS + + ["modified_count", "valid_count", "window_fraction"], + ], + on=_WINDOW_KEY_COLUMNS, + how="left", + sort=False, + ) + hits = covered_hits.loc[covered_hits["valid_count"] >= min_coverage].copy() + if not hits.empty: + hits["rank"] = range(1, len(hits) + 1) + rank_lookup = hits.set_index("window_id")["rank"] + window_table["rank"] = window_table["window_id"].map(rank_lookup) + + plot_data = { + "window_score_table": window_table.copy(), + "top_hits_table": hits.copy(), + } + metadata = { + "analysis_unit": "ensemble_region", + "representation": "modified_fraction", + "signal_source": "pileup_counts", + "score": score, + "window_size": window_size, + "step_size": step_size, + "min_coverage": min_coverage, + "merge_hits": merge_hits, + "merge_distance": merge_distance, + "motifs": motif_list, + "include_contigs": list(include_contigs) + if include_contigs is not None + else None, + "exclude_contigs": list(exclude_contigs) + if exclude_contigs is not None + else None, + "contrast_mode": contrast.mode, + "contrast_numerator": list(contrast.numerator or []), + "contrast_denominator": list(contrast.denominator or []), + "pairing_key": contrast.pairing_key, + "pairing_policy": active_pairing_policy, + "n_pairs_used": pairing_metadata["n_pairs_used"], + "n_pairs_dropped": pairing_metadata["n_pairs_dropped"], + "paired_mode": contrast.mode, + "time_order": time_order, + "rank_by": active_rank_by, + } + return RegionDiscoveryResult( + hits=hits.reset_index(drop=True), + windows=window_table.reset_index(drop=True), + contrast=contrast, + plot_data=plot_data, + metadata=metadata, + figures={}, + ) + + window_totals = _aggregate_window_counts(window_summary) + condition_counts = _aggregate_condition_counts(window_summary) + _validate_contrast_conditions(condition_counts=condition_counts, contrast=contrast) + + covered_mask = window_totals["valid_count"] >= min_coverage + covered_window_totals = window_totals.loc[covered_mask].copy() + covered_keys = covered_window_totals.loc[:, _WINDOW_KEY_COLUMNS] + covered_condition_counts = condition_counts.merge( + covered_keys, + on=_WINDOW_KEY_COLUMNS, + how="inner", + ) + + scored = _score_with_contrast( + window_totals=covered_window_totals, + condition_counts=covered_condition_counts, + contrast=contrast, + score=score, + ) + ranked = _rank_windows(scored, score=score) + scored_columns = [ + column + for column in ranked.columns + if column not in _WINDOW_KEY_COLUMNS + and column not in {"modified_count", "valid_count", "window_fraction"} + ] + window_table = window_totals.merge( + ranked.loc[:, _WINDOW_KEY_COLUMNS + scored_columns], + on=_WINDOW_KEY_COLUMNS, + how="left", + sort=False, + ) + hits = ranked.copy() + + if merge_hits: + hits = merge_adjacent_hits(hits, merge_distance=merge_distance) + + plot_data = { + "window_score_table": window_table.copy(), + "top_hits_table": hits.copy(), + } + metadata = { + "analysis_unit": "ensemble_region", + "representation": "modified_fraction", + "signal_source": "pileup_counts", + "score": score, + "window_size": window_size, + "step_size": step_size, + "min_coverage": min_coverage, + "merge_hits": merge_hits, + "merge_distance": merge_distance, + "motifs": motif_list, + "include_contigs": list(include_contigs) + if include_contigs is not None + else None, + "exclude_contigs": list(exclude_contigs) + if exclude_contigs is not None + else None, + } + if contrast is not None: + metadata["contrast_mode"] = contrast.mode + metadata["contrast_numerator"] = list(contrast.numerator or []) + metadata["contrast_denominator"] = list(contrast.denominator or []) + if pairing_metadata: + metadata["pairing_policy"] = active_pairing_policy + metadata["n_pairs_used"] = pairing_metadata["n_pairs_used"] + metadata["n_pairs_dropped"] = pairing_metadata["n_pairs_dropped"] + metadata["paired_mode"] = contrast.mode if contrast is not None else None + metadata["rank_by"] = active_rank_by + + return RegionDiscoveryResult( + hits=hits.reset_index(drop=True), + windows=window_table.reset_index(drop=True), + contrast=contrast, + plot_data=plot_data, + metadata=metadata, + figures={}, + ) diff --git a/dimelo/regulatory_enrichment.py b/dimelo/regulatory_enrichment.py new file mode 100644 index 0000000..3a3e1df --- /dev/null +++ b/dimelo/regulatory_enrichment.py @@ -0,0 +1,1195 @@ +from __future__ import annotations + +import html +import http.cookiejar +import re +import shutil +import subprocess +import time +from collections.abc import Iterable +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any +from urllib import error, parse, request + +from .models import UniBindJobResult + +_UNIBIND_SUPPORTED_SPECIES = ( + "arabidopsis_thaliana", + "caenorhabditis_elegans", + "danio_rerio", + "drosophila_melanogaster", + "homo_sapiens", + "mus_musculus", + "rattus_norvegicus", + "saccharomyces_cerevisiae", + "schizosaccharomyces_pombe", +) + +_SCREEN_SUPPORTED_SPECIES = { + "homo_sapiens", + "mus_musculus", +} + +_DEFAULT_SPECIES = "homo_sapiens" +_DEFAULT_TARGET_GENOME_BY_SPECIES = { + "homo_sapiens": "hg38", + "mus_musculus": "mm10", +} + +_PROVIDER_ALIASES = { + "screen": "screen", + "screen2": "screen", + "screen_2": "screen", + "screen2_0": "screen", + "screen_2_0": "screen", + "unibind": "unibind", + "unibind_2021": "unibind", + "unibind2021": "unibind", +} + +_SPECIES_ALIASES = { + "arabidopsis_thaliana": "arabidopsis_thaliana", + "arabidopsis": "arabidopsis_thaliana", + "3702": "arabidopsis_thaliana", + "caenorhabditis_elegans": "caenorhabditis_elegans", + "c_elegans": "caenorhabditis_elegans", + "celegans": "caenorhabditis_elegans", + "worm": "caenorhabditis_elegans", + "6239": "caenorhabditis_elegans", + "danio_rerio": "danio_rerio", + "zebrafish": "danio_rerio", + "7955": "danio_rerio", + "drosophila_melanogaster": "drosophila_melanogaster", + "d_melanogaster": "drosophila_melanogaster", + "fly": "drosophila_melanogaster", + "fruit_fly": "drosophila_melanogaster", + "7227": "drosophila_melanogaster", + "homo_sapiens": "homo_sapiens", + "human": "homo_sapiens", + "9606": "homo_sapiens", + "mus_musculus": "mus_musculus", + "mouse": "mus_musculus", + "10090": "mus_musculus", + "rattus_norvegicus": "rattus_norvegicus", + "rat": "rattus_norvegicus", + "10116": "rattus_norvegicus", + "saccharomyces_cerevisiae": "saccharomyces_cerevisiae", + "s_cerevisiae": "saccharomyces_cerevisiae", + "yeast": "saccharomyces_cerevisiae", + "4932": "saccharomyces_cerevisiae", + "schizosaccharomyces_pombe": "schizosaccharomyces_pombe", + "s_pombe": "schizosaccharomyces_pombe", + "4896": "schizosaccharomyces_pombe", +} + +_GENOME_SPECIES_HINTS = { + "hg38": "homo_sapiens", + "grch38": "homo_sapiens", + "hg19": "homo_sapiens", + "grch37": "homo_sapiens", + "hs1": "homo_sapiens", + "chm13": "homo_sapiens", + "t2t_chm13v2_0": "homo_sapiens", + "t2t_chm13v2": "homo_sapiens", + "mm10": "mus_musculus", + "grcm38": "mus_musculus", + "mm39": "mus_musculus", + "grcm39": "mus_musculus", +} + +_UNIBIND_TRACKHUB_URLS = { + "robust": "https://unibind.uio.no/static/data/latest/UniBind_hubs_Robust/UCSC/hub.txt", + "permissive": "https://unibind.uio.no/static/data/latest/UniBind_hubs_Permissive/UCSC/hub.txt", +} + +DEFAULT_UNIBIND_TFBS_EXTRACTION_URL = "https://unibind.uio.no/TFBS_extraction/" +DEFAULT_UNIBIND_ENRICHMENT_URL = "https://unibind.uio.no/enrichment/" + +_UNIBIND_FORM_SPECIES_CODES = { + "arabidopsis_thaliana": "3702", + "caenorhabditis_elegans": "6239", + "danio_rerio": "7955", + "drosophila_melanogaster": "7227", + "homo_sapiens": "9606", + "mus_musculus": "10090", + "rattus_norvegicus": "10116", + "saccharomyces_cerevisiae": "4932", + "schizosaccharomyces_pombe": "4896", +} +_UNIBIND_FORM_SPECIES_BY_CODE = {v: k for k, v in _UNIBIND_FORM_SPECIES_CODES.items()} +_UNIBIND_COLLECTION_ALIASES = { + "robust": "Robust", + "permissive": "Permissive", +} +_UNIBIND_ENRICHMENT_ANALYSIS_ALIASES = { + "onesetbg": "oneSetBg", + "one_set_bg": "oneSetBg", + "with_background": "oneSetBg", + "twosets": "twoSets", + "two_sets": "twoSets", + "differential": "twoSets", +} +_UNIBIND_STATUS_ALIASES = { + "queued": "queued", + "pending": "queued", + "running": "running", + "submitted": "queued", + "processing": "running", + "completed": "completed", + "finished": "completed", + "done": "completed", + "success": "completed", + "failed": "error", + "failure": "error", + "error": "error", + "cancelled": "cancelled", + "canceled": "cancelled", +} +_UNIBIND_TERMINAL_STATUSES = {"completed", "error", "cancelled"} +_UNIBIND_SUCCESS_STATUSES = {"completed"} + + +class RegulatoryEnrichmentSpecError(ValueError): + """Invalid regulatory enrichment provider configuration.""" + + +def _normalize_token(value: str) -> str: + return re.sub(r"[^a-z0-9]+", "_", value.strip().lower()).strip("_") + + +def _normalize_assembly_token(value: str) -> str: + return re.sub(r"[^a-z0-9]+", "", value.strip().lower()) + + +def normalize_species_name(species: str) -> str: + token = _normalize_token(species) + if not token: + raise RegulatoryEnrichmentSpecError("Species cannot be empty.") + canonical = _SPECIES_ALIASES.get(token, token) + if canonical not in _UNIBIND_SUPPORTED_SPECIES: + supported = ", ".join(_UNIBIND_SUPPORTED_SPECIES) + raise RegulatoryEnrichmentSpecError( + "Unsupported species for UniBind-backed workflows: " + f"{species!r}. Supported species: {supported}." + ) + return canonical + + +def _infer_species_from_genomes( + *, reference_genome: str | None, target_genome: str | None +) -> str | None: + for genome in (target_genome, reference_genome): + if genome is None: + continue + token = _normalize_token(str(genome)) + if token in _GENOME_SPECIES_HINTS: + return _GENOME_SPECIES_HINTS[token] + return None + + +def _normalize_provider(provider: str) -> str: + token = _normalize_token(provider) + if token not in _PROVIDER_ALIASES: + supported = ", ".join(sorted(set(_PROVIDER_ALIASES.values()))) + raise RegulatoryEnrichmentSpecError( + f"Unsupported provider {provider!r}. Supported providers: {supported}." + ) + return _PROVIDER_ALIASES[token] + + +def _unique_preserve_order(values: Iterable[str]) -> list[str]: + seen: set[str] = set() + out: list[str] = [] + for value in values: + if value in seen: + continue + seen.add(value) + out.append(value) + return out + + +def _normalize_unibind_species_code(species: str) -> str: + raw = str(species).strip() + if not raw: + raise ValueError("UniBind species cannot be empty.") + if raw in _UNIBIND_FORM_SPECIES_BY_CODE: + return raw + token = _normalize_token(raw) + if token in _UNIBIND_FORM_SPECIES_BY_CODE: + return token + canonical = normalize_species_name(raw) + return _UNIBIND_FORM_SPECIES_CODES[canonical] + + +def _normalize_unibind_collection(collection: str) -> str: + token = _normalize_token(collection) + if token not in _UNIBIND_COLLECTION_ALIASES: + allowed = ", ".join(sorted(_UNIBIND_COLLECTION_ALIASES.values())) + raise ValueError( + f"Unsupported UniBind collection {collection!r}. Supported: {allowed}." + ) + return _UNIBIND_COLLECTION_ALIASES[token] + + +def _normalize_unibind_enrichment_analysis_type(analysis_type: str) -> str: + token = _normalize_token(analysis_type) + if token not in _UNIBIND_ENRICHMENT_ANALYSIS_ALIASES: + allowed = ", ".join(sorted(set(_UNIBIND_ENRICHMENT_ANALYSIS_ALIASES.values()))) + raise ValueError( + "Unsupported UniBind enrichment analysis_type " + f"{analysis_type!r}. Supported: {allowed}." + ) + return _UNIBIND_ENRICHMENT_ANALYSIS_ALIASES[token] + + +def _normalize_unibind_status(status: str | None) -> str: + if status is None: + return "unknown" + token = _normalize_token(str(status)) + if not token: + return "unknown" + return _UNIBIND_STATUS_ALIASES.get(token, token) + + +def _regions_to_bed_bytes(regions: Any) -> bytes: + from . import chip_atlas + + text = chip_atlas._regions_to_bed_text(regions) + return text.encode("utf-8") + + +def _coerce_optional_text_file_payload( + value: str | Path | Iterable[str] | None, + *, + default_filename: str, +) -> tuple[str, bytes, str] | None: + if value is None: + return None + + filename = default_filename + if isinstance(value, Path): + payload = value.read_bytes() + filename = value.name + elif isinstance(value, str): + candidate = Path(value).expanduser() + if "\n" not in value and "\t" not in value and candidate.exists(): + payload = candidate.read_bytes() + filename = candidate.name + else: + payload = value.encode("utf-8") + else: + lines = [str(item).strip() for item in value if str(item).strip()] + payload = ("\n".join(lines) + ("\n" if lines else "")).encode("utf-8") + + if not payload: + return None + return filename, payload, "text/plain" + + +def _build_multipart_form( + *, + fields: dict[str, str], + files: dict[str, tuple[str, bytes, str]], +) -> tuple[bytes, str]: + boundary = f"----dimelo-{int(time.time() * 1000)}" + chunks: list[bytes] = [] + for key, value in fields.items(): + chunks.append(f"--{boundary}\r\n".encode()) + chunks.append(f'Content-Disposition: form-data; name="{key}"\r\n\r\n'.encode()) + chunks.append(str(value).encode("utf-8")) + chunks.append(b"\r\n") + for key, (filename, payload, content_type) in files.items(): + chunks.append(f"--{boundary}\r\n".encode()) + chunks.append( + ( + f'Content-Disposition: form-data; name="{key}"; ' + f'filename="{filename}"\r\n' + ).encode() + ) + chunks.append(f"Content-Type: {content_type}\r\n\r\n".encode()) + chunks.append(payload) + chunks.append(b"\r\n") + chunks.append(f"--{boundary}--\r\n".encode()) + return b"".join(chunks), boundary + + +def _extract_csrf_token(page_html: str) -> str: + match = re.search( + r'name="csrfmiddlewaretoken"\s+value="([^"]+)"', + page_html, + flags=re.IGNORECASE, + ) + if match is None: + raise RuntimeError("UniBind form did not include a CSRF token.") + return html.unescape(match.group(1)) + + +def _extract_unibind_results_url(job_html: str, *, base_url: str) -> str | None: + match = re.search( + r"\s*Results URL:\s*\s*\s* str: + match = re.search( + r"\s*Job status:\s*\s*\s*([^<]+)\s*", + job_html, + flags=re.IGNORECASE, + ) + if match is None: + return "unknown" + raw = html.unescape(match.group(1)).strip() + return _normalize_unibind_status(raw) + + +def _looks_like_unibind_output_url(url: str) -> bool: + parsed = parse.urlparse(url) + path = parsed.path.lower() + if "/temp/" in path: + return True + return path.endswith( + ( + ".bed", + ".bed.gz", + ".tsv", + ".csv", + ".txt", + ".zip", + ".pdf", + ) + ) + + +def _extract_unibind_output_urls(job_html: str, *, base_url: str) -> list[str]: + links: list[str] = [] + for href in re.findall(r'href="([^"]+)"', job_html, flags=re.IGNORECASE): + resolved = parse.urljoin(base_url, html.unescape(href).strip()) + if not _looks_like_unibind_output_url(resolved): + continue + links.append(resolved) + return _unique_preserve_order(links) + + +def _extract_unibind_job_id(job_url: str) -> str: + parts = [segment for segment in parse.urlparse(job_url).path.split("/") if segment] + if not parts: + raise ValueError(f"Could not determine UniBind job id from URL: {job_url}") + return parts[-1] + + +def _submit_unibind_form( + *, + endpoint_url: str, + fields: dict[str, str], + files: dict[str, tuple[str, bytes, str]], + timeout_seconds: float, + query: dict[str, Any], +) -> UniBindJobResult: + cookie_jar = http.cookiejar.CookieJar() + opener = request.build_opener(request.HTTPCookieProcessor(cookie_jar)) + + get_req = request.Request( + endpoint_url, + method="GET", + headers={"User-Agent": "dimelo-toolkit/1.0"}, + ) + with opener.open(get_req, timeout=timeout_seconds) as response: + landing_html = response.read().decode("utf-8", errors="replace") + + csrf_token = _extract_csrf_token(landing_html) + fields_with_csrf = dict(fields) + fields_with_csrf["csrfmiddlewaretoken"] = csrf_token + body, boundary = _build_multipart_form(fields=fields_with_csrf, files=files) + + post_req = request.Request( + endpoint_url, + data=body, + method="POST", + headers={ + "User-Agent": "dimelo-toolkit/1.0", + "Content-Type": f"multipart/form-data; boundary={boundary}", + "Referer": endpoint_url, + }, + ) + with opener.open(post_req, timeout=timeout_seconds) as response: + final_url = str(response.geturl()) + job_html = response.read().decode("utf-8", errors="replace") + + results_url = _extract_unibind_results_url(job_html, base_url=final_url) + job_url = results_url or final_url + status = _extract_unibind_job_status(job_html) + output_urls = _extract_unibind_output_urls(job_html, base_url=job_url) + job_id = _extract_unibind_job_id(job_url) + + return UniBindJobResult( + job_id=job_id, + status=status, + job_url=job_url, + endpoint_url=endpoint_url, + results_url=results_url, + download_urls=output_urls, + query=query, + status_history=[ + { + "status": status, + "timestamp": time.time(), + "stage": "submitted", + "url": job_url, + } + ], + metadata={}, + ) + + +def _fetch_bytes(url: str, *, timeout_seconds: float = 60.0) -> bytes: + req = request.Request(url, headers={"User-Agent": "dimelo-toolkit/1.0"}) + try: + with request.urlopen(req, timeout=timeout_seconds) as response: + return response.read() + except error.HTTPError as exc: + raise RuntimeError( + f"Track hub request failed with HTTP {exc.code}: {url}" + ) from exc + except error.URLError as exc: + raise RuntimeError(f"Track hub request failed for {url}: {exc.reason}") from exc + + +def _fetch_text(url: str, *, timeout_seconds: float = 60.0) -> str: + return _fetch_bytes(url, timeout_seconds=timeout_seconds).decode( + "utf-8", errors="replace" + ) + + +def _parse_kv_blocks(text: str) -> list[dict[str, str]]: + blocks: list[dict[str, str]] = [] + current: dict[str, str] = {} + for raw in text.splitlines(): + line = raw.strip() + if not line or line.startswith("#"): + if current: + blocks.append(current) + current = {} + continue + parts = line.split(None, 1) + key = parts[0] + value = parts[1].strip() if len(parts) > 1 else "" + current[key] = value + if current: + blocks.append(current) + return blocks + + +def _load_trackdb_stanzas( + url: str, + *, + timeout_seconds: float = 60.0, + _visited: set[str] | None = None, +) -> list[dict[str, str]]: + visited = _visited if _visited is not None else set() + if url in visited: + return [] + visited.add(url) + + stanzas: list[dict[str, str]] = [] + current: dict[str, str] = {} + text = _fetch_text(url, timeout_seconds=timeout_seconds) + + def flush_current() -> None: + nonlocal current + if current: + stanzas.append(current) + current = {} + + for raw in text.splitlines(): + line = raw.strip() + if not line or line.startswith("#"): + flush_current() + continue + + if line.lower().startswith("include "): + flush_current() + include_ref = line.split(None, 1)[1].strip().split()[0] + include_url = parse.urljoin(url, include_ref) + stanzas.extend( + _load_trackdb_stanzas( + include_url, + timeout_seconds=timeout_seconds, + _visited=visited, + ) + ) + continue + + parts = line.split(None, 1) + key = parts[0] + value = parts[1].strip() if len(parts) > 1 else "" + current[key] = value + + flush_current() + return stanzas + + +def _select_trackdb_url( + *, + hub_url: str, + assembly: str | None, + timeout_seconds: float, +) -> tuple[str, str]: + hub_text = _fetch_text(hub_url, timeout_seconds=timeout_seconds) + hub_meta = _parse_kv_blocks(hub_text) + if not hub_meta: + raise RuntimeError(f"Track hub descriptor is empty: {hub_url}") + + genomes_file = hub_meta[0].get("genomesFile") + if not genomes_file: + raise RuntimeError( + "Track hub descriptor did not include 'genomesFile'. " + "Only multi-file hubs are currently supported." + ) + + genomes_url = parse.urljoin(hub_url, genomes_file) + genome_blocks = _parse_kv_blocks( + _fetch_text(genomes_url, timeout_seconds=timeout_seconds) + ) + candidates: list[tuple[str, str]] = [] + for block in genome_blocks: + genome = block.get("genome") + trackdb = block.get("trackDb") + if genome and trackdb: + candidates.append((genome, parse.urljoin(genomes_url, trackdb))) + if not candidates: + raise RuntimeError(f"No genome/trackDb entries found in {genomes_url}") + + if assembly is None: + genome, trackdb_url = candidates[0] + return genome, trackdb_url + + requested = _normalize_assembly_token(assembly) + for genome, trackdb_url in candidates: + if _normalize_assembly_token(genome) == requested: + return genome, trackdb_url + + available = ", ".join(genome for genome, _ in candidates) + raise ValueError( + f"Assembly {assembly!r} was not found in track hub. Available assemblies: {available}" + ) + + +def _is_bigbed_path(path: Path) -> bool: + lower_name = path.name.lower() + return lower_name.endswith(".bb") or lower_name.endswith(".bigbed") + + +def _download_track_file( + *, + url: str, + cache_dir: Path, + allow_cached: bool, + timeout_seconds: float, +) -> Path: + filename = Path(parse.urlparse(url).path).name + if not filename: + filename = "track_file" + output = cache_dir / filename + + if allow_cached and output.exists() and output.stat().st_size > 0: + return output + + payload = _fetch_bytes(url, timeout_seconds=timeout_seconds) + output.parent.mkdir(parents=True, exist_ok=True) + tmp_output = output.with_suffix(output.suffix + ".tmp") + tmp_output.write_bytes(payload) + tmp_output.replace(output) + return output + + +def _convert_bigbed_to_bed(path: Path) -> Path: + converter = shutil.which("bigBedToBed") + if converter is None: + raise RuntimeError( + "Requested bigBed conversion but 'bigBedToBed' was not found on PATH." + ) + + converted = path.with_suffix(".bed") + proc = subprocess.run( + [converter, str(path), str(converted)], + capture_output=True, + text=True, + check=False, + ) + if proc.returncode != 0: + raise RuntimeError( + f"bigBedToBed failed for {path}: {proc.stderr.strip()[:400]}" + ) + return converted + + +def unibind_trackhub_url(collection: str = "robust") -> str: + key = _normalize_token(collection) + if key not in _UNIBIND_TRACKHUB_URLS: + available = ", ".join(sorted(_UNIBIND_TRACKHUB_URLS)) + raise ValueError( + f"Unsupported UniBind track hub collection {collection!r}. Available: {available}." + ) + return _UNIBIND_TRACKHUB_URLS[key] + + +def search_unibind_trackhub_tracks( + *, + trackhub_url: str | None = None, + collection: str = "robust", + assembly: str | None = None, + search_terms: Iterable[str] | None = None, + timeout_seconds: float = 60.0, +) -> list[dict[str, str]]: + resolved_hub_url = ( + trackhub_url if trackhub_url is not None else unibind_trackhub_url(collection) + ) + resolved_assembly, trackdb_url = _select_trackdb_url( + hub_url=resolved_hub_url, + assembly=assembly, + timeout_seconds=timeout_seconds, + ) + stanzas = _load_trackdb_stanzas(trackdb_url, timeout_seconds=timeout_seconds) + + terms = [ + str(term).strip().lower() for term in (search_terms or []) if str(term).strip() + ] + + rows: list[dict[str, str]] = [] + for stanza in stanzas: + big_data = stanza.get("bigDataUrl") + if not big_data: + continue + + track_name = stanza.get("track", "") + short_label = stanza.get("shortLabel", "") + long_label = stanza.get("longLabel", "") + track_type = stanza.get("type", "") + big_data_url = parse.urljoin(trackdb_url, big_data) + + if terms: + haystack = " ".join( + [track_name, short_label, long_label, big_data_url] + ).lower() + if not any(term in haystack for term in terms): + continue + + rows.append( + { + "assembly": resolved_assembly, + "track": track_name, + "short_label": short_label, + "long_label": long_label, + "type": track_type, + "url": big_data_url, + } + ) + + return rows + + +def resolve_unibind_track_paths( + *, + track_paths: Iterable[str | Path] | None = None, + trackhub_url: str | None = None, + collection: str = "robust", + assembly: str | None = None, + search_terms: Iterable[str] | None = None, + cache_dir: str | Path = "cache/unibind_tracks", + max_tracks: int | None = None, + allow_cached: bool = True, + timeout_seconds: float = 60.0, + convert_bigbed_to_bed: bool = False, +) -> list[Path]: + if track_paths is not None: + resolved_paths: list[Path] = [] + for raw in track_paths: + candidate = Path(raw).expanduser().resolve() + if not candidate.exists(): + raise FileNotFoundError( + f"UniBind track path does not exist: {candidate}" + ) + resolved_paths.append(candidate) + if not resolved_paths: + raise ValueError("track_paths was provided but empty.") + return resolved_paths + + discovered = search_unibind_trackhub_tracks( + trackhub_url=trackhub_url, + collection=collection, + assembly=assembly, + search_terms=search_terms, + timeout_seconds=timeout_seconds, + ) + if not discovered: + raise ValueError( + "No UniBind tracks matched the requested track hub search criteria." + ) + + if max_tracks is not None: + discovered = discovered[: int(max_tracks)] + + cache_root = Path(cache_dir) + cache_root.mkdir(parents=True, exist_ok=True) + + outputs: list[Path] = [] + for row in discovered: + downloaded = _download_track_file( + url=row["url"], + cache_dir=cache_root, + allow_cached=allow_cached, + timeout_seconds=timeout_seconds, + ) + if convert_bigbed_to_bed and _is_bigbed_path(downloaded): + downloaded = _convert_bigbed_to_bed(downloaded) + outputs.append(downloaded) + + return outputs + + +def submit_unibind_tfbs_extraction( + *, + regions: Any, + species: str = _DEFAULT_SPECIES, + collection: str = "robust", + tf_list: str | Path | Iterable[str] | None = None, + experiment_ids: str | Path | Iterable[str] | None = None, + name: str | None = None, + email: str | None = None, + endpoint_url: str = DEFAULT_UNIBIND_TFBS_EXTRACTION_URL, + timeout_seconds: float = 120.0, +) -> UniBindJobResult: + species_code = _normalize_unibind_species_code(species) + resolved_collection = _normalize_unibind_collection(collection) + bed_payload = _regions_to_bed_bytes(regions) + + files: dict[str, tuple[str, bytes, str]] = { + "bed_file": ("regions.bed", bed_payload, "text/plain"), + } + tf_payload = _coerce_optional_text_file_payload( + tf_list, + default_filename="tf_list.txt", + ) + if tf_payload is not None: + files["TFs_file"] = tf_payload + experiment_payload = _coerce_optional_text_file_payload( + experiment_ids, + default_filename="experiment_ids.txt", + ) + if experiment_payload is not None: + files["experiments_file"] = experiment_payload + + fields = { + "name": (name or "").strip(), + "email": (email or "").strip(), + "species": species_code, + "collection": resolved_collection, + "performTFBSextraction": "1", + } + return _submit_unibind_form( + endpoint_url=endpoint_url, + fields=fields, + files=files, + timeout_seconds=timeout_seconds, + query={ + "species": _UNIBIND_FORM_SPECIES_BY_CODE.get(species_code, species_code), + "species_code": species_code, + "collection": resolved_collection, + "name": fields["name"], + "email": fields["email"], + "has_tf_filter": tf_payload is not None, + "has_experiment_filter": experiment_payload is not None, + }, + ) + + +def submit_unibind_enrichment( + *, + regions: Any, + analysis_type: str = "oneSetBg", + background_regions: Any | None = None, + comparison_regions: Any | None = None, + species: str = _DEFAULT_SPECIES, + collection: str = "robust", + name: str | None = None, + email: str | None = None, + endpoint_url: str = DEFAULT_UNIBIND_ENRICHMENT_URL, + timeout_seconds: float = 120.0, +) -> UniBindJobResult: + resolved_analysis_type = _normalize_unibind_enrichment_analysis_type(analysis_type) + species_code = _normalize_unibind_species_code(species) + resolved_collection = _normalize_unibind_collection(collection) + bed_payload = _regions_to_bed_bytes(regions) + + files: dict[str, tuple[str, bytes, str]] = { + "bed_file_1": ("regions_1.bed", bed_payload, "text/plain"), + } + if resolved_analysis_type == "oneSetBg": + if background_regions is None: + raise ValueError( + "UniBind enrichment with analysis_type='oneSetBg' requires " + "background_regions." + ) + files["bed_file_background"] = ( + "background_regions.bed", + _regions_to_bed_bytes(background_regions), + "text/plain", + ) + elif resolved_analysis_type == "twoSets": + if comparison_regions is None: + raise ValueError( + "UniBind enrichment with analysis_type='twoSets' requires " + "comparison_regions." + ) + files["bed_file_2"] = ( + "regions_2.bed", + _regions_to_bed_bytes(comparison_regions), + "text/plain", + ) + + fields = { + "analysis_type": resolved_analysis_type, + "name": (name or "").strip(), + "email": (email or "").strip(), + "species": species_code, + "collection": resolved_collection, + "performEnrichment": "1", + } + return _submit_unibind_form( + endpoint_url=endpoint_url, + fields=fields, + files=files, + timeout_seconds=timeout_seconds, + query={ + "analysis_type": resolved_analysis_type, + "species": _UNIBIND_FORM_SPECIES_BY_CODE.get(species_code, species_code), + "species_code": species_code, + "collection": resolved_collection, + "name": fields["name"], + "email": fields["email"], + "has_background": background_regions is not None, + "has_comparison": comparison_regions is not None, + }, + ) + + +def poll_unibind_job( + job: UniBindJobResult | str, + *, + poll_interval_seconds: float = 5.0, + timeout_seconds: float = 1200.0, + request_timeout_seconds: float = 60.0, +) -> UniBindJobResult: + if isinstance(job, UniBindJobResult): + current = job + else: + parsed = str(job).strip() + if not parsed: + raise ValueError("job URL cannot be empty.") + current = UniBindJobResult( + job_id=_extract_unibind_job_id(parsed), + status="unknown", + job_url=parsed, + endpoint_url=parse.urljoin(parsed, "."), + query={}, + status_history=[], + metadata={}, + ) + + history = list(current.status_history) + started = time.monotonic() + status = current.status + results_url = current.results_url + output_urls = list(current.download_urls) + + while True: + page_html = _fetch_text( + current.job_url, timeout_seconds=request_timeout_seconds + ) + status = _extract_unibind_job_status(page_html) + extracted_results_url = _extract_unibind_results_url( + page_html, base_url=current.job_url + ) + results_url = extracted_results_url or results_url or current.job_url + output_urls = _extract_unibind_output_urls(page_html, base_url=current.job_url) + + history.append( + { + "status": status, + "timestamp": time.time(), + "stage": "poll", + "url": current.job_url, + } + ) + if status in _UNIBIND_TERMINAL_STATUSES: + break + + elapsed = time.monotonic() - started + if elapsed > timeout_seconds: + raise TimeoutError( + "Timed out waiting for UniBind job completion: " + f"{current.job_url} (last status={status!r})." + ) + time.sleep(max(0.1, float(poll_interval_seconds))) + + return UniBindJobResult( + job_id=current.job_id, + status=status, + job_url=current.job_url, + endpoint_url=current.endpoint_url, + results_url=results_url, + download_urls=output_urls, + query=dict(current.query), + status_history=history, + metadata=dict(current.metadata), + ) + + +def download_unibind_job_outputs( + *, + job: UniBindJobResult, + output_dir: str | Path = "cache/unibind_jobs", + allow_cached: bool = True, + timeout_seconds: float = 120.0, +) -> list[Path]: + if not job.download_urls: + return [] + + root = Path(output_dir).expanduser().resolve() / job.job_id + root.mkdir(parents=True, exist_ok=True) + downloaded: list[Path] = [] + + for index, url in enumerate(job.download_urls): + name = Path(parse.urlparse(url).path).name or f"output_{index}" + destination = root / name + if destination.exists() and destination.stat().st_size > 0 and allow_cached: + downloaded.append(destination) + continue + + payload = _fetch_bytes(url, timeout_seconds=timeout_seconds) + tmp_path = destination.with_suffix(destination.suffix + ".tmp") + tmp_path.write_bytes(payload) + tmp_path.replace(destination) + downloaded.append(destination) + return downloaded + + +def run_unibind_tfbs_extraction( + *, + regions: Any, + species: str = _DEFAULT_SPECIES, + collection: str = "robust", + tf_list: str | Path | Iterable[str] | None = None, + experiment_ids: str | Path | Iterable[str] | None = None, + name: str | None = None, + email: str | None = None, + endpoint_url: str = DEFAULT_UNIBIND_TFBS_EXTRACTION_URL, + submit_timeout_seconds: float = 120.0, + wait: bool = True, + poll_interval_seconds: float = 5.0, + timeout_seconds: float = 1200.0, + download_outputs: bool = False, + output_dir: str | Path = "cache/unibind_jobs", + allow_cached_downloads: bool = True, + download_timeout_seconds: float = 120.0, +) -> UniBindJobResult: + result = submit_unibind_tfbs_extraction( + regions=regions, + species=species, + collection=collection, + tf_list=tf_list, + experiment_ids=experiment_ids, + name=name, + email=email, + endpoint_url=endpoint_url, + timeout_seconds=submit_timeout_seconds, + ) + if wait: + result = poll_unibind_job( + result, + poll_interval_seconds=poll_interval_seconds, + timeout_seconds=timeout_seconds, + ) + if download_outputs: + if not wait: + raise ValueError("download_outputs=True requires wait=True.") + downloaded = download_unibind_job_outputs( + job=result, + output_dir=output_dir, + allow_cached=allow_cached_downloads, + timeout_seconds=download_timeout_seconds, + ) + result.metadata["downloaded_outputs"] = [str(path) for path in downloaded] + return result + + +def run_unibind_enrichment( + *, + regions: Any, + analysis_type: str = "oneSetBg", + background_regions: Any | None = None, + comparison_regions: Any | None = None, + species: str = _DEFAULT_SPECIES, + collection: str = "robust", + name: str | None = None, + email: str | None = None, + endpoint_url: str = DEFAULT_UNIBIND_ENRICHMENT_URL, + submit_timeout_seconds: float = 120.0, + wait: bool = True, + poll_interval_seconds: float = 5.0, + timeout_seconds: float = 1200.0, + download_outputs: bool = False, + output_dir: str | Path = "cache/unibind_jobs", + allow_cached_downloads: bool = True, + download_timeout_seconds: float = 120.0, +) -> UniBindJobResult: + result = submit_unibind_enrichment( + regions=regions, + analysis_type=analysis_type, + background_regions=background_regions, + comparison_regions=comparison_regions, + species=species, + collection=collection, + name=name, + email=email, + endpoint_url=endpoint_url, + timeout_seconds=submit_timeout_seconds, + ) + if wait: + result = poll_unibind_job( + result, + poll_interval_seconds=poll_interval_seconds, + timeout_seconds=timeout_seconds, + ) + if download_outputs: + if not wait: + raise ValueError("download_outputs=True requires wait=True.") + downloaded = download_unibind_job_outputs( + job=result, + output_dir=output_dir, + allow_cached=allow_cached_downloads, + timeout_seconds=download_timeout_seconds, + ) + result.metadata["downloaded_outputs"] = [str(path) for path in downloaded] + return result + + +def unibind_supported_species() -> tuple[str, ...]: + return _UNIBIND_SUPPORTED_SPECIES + + +def screen_supported_species() -> tuple[str, ...]: + return tuple(sorted(_SCREEN_SUPPORTED_SPECIES)) + + +@dataclass +class RegulatoryEnrichmentSpec: + providers: tuple[str, ...] = ("screen", "unibind") + species: str | None = None + reference_genome: str | None = None + target_genome: str | None = None + crossmap_chain_file: str | Path | None = None + crossmap_chain_url: str | None = None + crossmap_chain_cache_dir: str | Path | None = None + crossmap_executable: str | None = "CrossMap.py" + strict_provider_support: bool = False + enabled_providers: tuple[str, ...] = field(init=False) + provider_notes: dict[str, str] = field(init=False, default_factory=dict) + + def __post_init__(self) -> None: + requested = self.providers if self.providers else ("screen", "unibind") + normalized_providers = _unique_preserve_order( + _normalize_provider(p) for p in requested + ) + + inferred = _infer_species_from_genomes( + reference_genome=self.reference_genome, + target_genome=self.target_genome, + ) + resolved_species = ( + self.species if self.species is not None else (inferred or _DEFAULT_SPECIES) + ) + canonical_species = normalize_species_name(str(resolved_species)) + + enabled: list[str] = [] + notes: dict[str, str] = {} + for provider in normalized_providers: + if ( + provider == "screen" + and canonical_species not in _SCREEN_SUPPORTED_SPECIES + ): + message = ( + "SCREEN disabled for species " + f"{canonical_species!r}; SCREEN supports only " + "homo_sapiens and mus_musculus." + ) + if self.strict_provider_support: + raise RegulatoryEnrichmentSpecError(message) + notes[provider] = message + continue + enabled.append(provider) + + if not enabled: + raise RegulatoryEnrichmentSpecError( + "No providers remain after species-based filtering. " + f"Requested providers={normalized_providers}, species={canonical_species!r}." + ) + + if ( + self.target_genome is None + and canonical_species in _DEFAULT_TARGET_GENOME_BY_SPECIES + ): + self.target_genome = _DEFAULT_TARGET_GENOME_BY_SPECIES[canonical_species] + + self.providers = tuple(normalized_providers) + self.species = canonical_species + self.enabled_providers = tuple(enabled) + self.provider_notes = notes + + def as_dict(self) -> dict[str, Any]: + return { + "providers": list(self.providers), + "enabled_providers": list(self.enabled_providers), + "species": self.species, + "reference_genome": self.reference_genome, + "target_genome": self.target_genome, + "crossmap_chain_file": ( + str(self.crossmap_chain_file) + if isinstance(self.crossmap_chain_file, Path) + else self.crossmap_chain_file + ), + "crossmap_chain_url": self.crossmap_chain_url, + "crossmap_chain_cache_dir": ( + str(self.crossmap_chain_cache_dir) + if isinstance(self.crossmap_chain_cache_dir, Path) + else self.crossmap_chain_cache_dir + ), + "crossmap_executable": self.crossmap_executable, + "provider_notes": dict(self.provider_notes), + } + + +__all__ = [ + "DEFAULT_UNIBIND_ENRICHMENT_URL", + "DEFAULT_UNIBIND_TFBS_EXTRACTION_URL", + "RegulatoryEnrichmentSpec", + "RegulatoryEnrichmentSpecError", + "download_unibind_job_outputs", + "normalize_species_name", + "poll_unibind_job", + "resolve_unibind_track_paths", + "run_unibind_enrichment", + "run_unibind_tfbs_extraction", + "screen_supported_species", + "search_unibind_trackhub_tracks", + "submit_unibind_enrichment", + "submit_unibind_tfbs_extraction", + "unibind_supported_species", + "unibind_trackhub_url", +] diff --git a/dimelo/run_modkit.py b/dimelo/run_modkit.py index c49106c..5344f26 100644 --- a/dimelo/run_modkit.py +++ b/dimelo/run_modkit.py @@ -6,48 +6,282 @@ import pty import re import select +import shutil import subprocess import sys +from dataclasses import dataclass +from functools import lru_cache from pathlib import Path -from typing import Optional, cast +from typing import TypeAlias, cast from tqdm.auto import tqdm -# This should be updated in tandem with the environment.yml nanoporetech::modkit version -EXPECTED_MODKIT_VERSION = "0.2.4" - -""" -Import checks -""" -# Add conda env bin folder to path if it is not already present -# On some systems, the directory containing executables for the active environment isn't automatically on the path -# If this is the case, add that directory to the path so modkit can run -current_interpreter = sys.executable -env_bin_path = os.path.dirname(current_interpreter) -if env_bin_path not in os.environ["PATH"]: - print( - f"PATH does not include the conda environment /bin folder. Adding {env_bin_path}." +SUPPORTED_MODKIT_SERIES = ("0.2.x", "0.6.x") +SUPPORTED_MODKIT_MINOR_VERSIONS = {(0, 2), (0, 6)} +MODKIT_EXECUTABLE_ENV = "DIMELO_MODKIT_EXECUTABLE" +FindingProgressDict: TypeAlias = dict[str, tuple[int, int]] +_NOISY_RUNTIME_LINES: tuple[str, ...] = ( + "MallocStackLogging: can't turn off malloc stack logging because it was not enabled.", +) + + +@dataclass(frozen=True) +class ModkitCapabilities: + executable: str + version_raw: str + version: str | None + version_tuple: tuple[int, ...] | None + supports_mod_threshold: bool + supports_mod_thresholds: bool + supports_modified_bases: bool + supports_force_allow_implicit: bool + supports_extract_subcommands: bool + extract_supports_reference_long: bool + extract_supports_reference_short: bool + + +def _strip_runtime_noise(text: str) -> str: + cleaned = text + for line in _NOISY_RUNTIME_LINES: + cleaned = cleaned.replace(line, "") + return cleaned + + +def _should_render_live_progress() -> bool: + """ + Decide whether tqdm live bars should be rendered for modkit subprocess output. + + The default "auto" mode disables live bars in notebook/non-TTY contexts where + carriage-return updates tend to produce mangled output. + """ + mode = os.environ.get("DIMELO_PROGRESS_MODE", "auto").strip().lower() + if mode in {"off", "none", "false", "0"}: + return False + if mode in {"on", "force", "true", "1"}: + return True + in_notebook = "JPY_PARENT_PID" in os.environ or "IPYKERNEL_PARENT_PID" in os.environ + return sys.stderr.isatty() and not in_notebook + + +def _prepare_modkit_path(quiet: bool = False) -> None: + # Add conda env bin folder to path if it is not already present + current_interpreter = sys.executable + env_bin_path = os.path.dirname(current_interpreter) + if env_bin_path not in os.environ["PATH"]: + if not quiet: + print( + f"PATH does not include the conda environment /bin folder. Adding {env_bin_path}." + ) + os.environ["PATH"] = f"{env_bin_path}:{os.environ['PATH']}" + + +def _parse_modkit_semver(version_text: str) -> tuple[int, ...] | None: + match = re.search(r"(\d+)\.(\d+)\.(\d+)", version_text) + if match is None: + return None + return (int(match.group(1)), int(match.group(2)), int(match.group(3))) + + +def _help_supports_flag(help_text: str, flag: str) -> bool: + return re.search(rf"(?m)^\s+{re.escape(flag)}(?:\s|$)", help_text) is not None + + +def _resolve_modkit_executable( + executable: str | None, +) -> str: + requested = executable or os.environ.get(MODKIT_EXECUTABLE_ENV) or "modkit" + looks_like_path = any(sep in requested for sep in ("/", "\\")) + + if looks_like_path: + expanded = str(Path(requested).expanduser()) + if not Path(expanded).exists(): + raise FileNotFoundError( + f"Requested modkit executable does not exist: {expanded}. " + f"Set {MODKIT_EXECUTABLE_ENV} or pass a valid executable path/name." + ) + return expanded + + discovered = shutil.which(requested) + if discovered is None: + raise FileNotFoundError( + f"Executable not found for modkit candidate '{requested}'. " + 'Install dimelo using "conda env create -f environment.yml" ' + 'or install modkit manually using "conda install nanoporetech::modkit==0.6.1". ' + "Without modkit you cannot run parse_bam functions." + ) + return discovered + + +def _modkit_cache_fingerprint(executable_path: str) -> str: + """ + Build a cache fingerprint for a resolved executable path. + Includes file metadata so replacing/upgrading modkit in-place invalidates + cached capabilities. + """ + path = Path(executable_path) + try: + stat = path.stat() + return f"{path.resolve()}:{stat.st_mtime_ns}:{stat.st_size}" + except OSError: + # Fall back to the resolved path string when stat is unavailable. + return str(path.resolve()) + + +@lru_cache(maxsize=16) +def _get_modkit_capabilities_cached( + executable_path: str, + executable_fingerprint: str, + quiet: bool = False, +) -> ModkitCapabilities: + # executable_fingerprint is included to invalidate cache entries when the + # binary file is replaced in-place (e.g., conda install modkit==new_version). + # It is intentionally unused beyond participating in the cache key. + _ = executable_fingerprint + executable = executable_path + + try: + version_result = subprocess.run( + [executable, "--version"], + capture_output=True, + text=True, + check=True, + ) + except ( + Exception + ) as exc: # pragma: no cover - direct subprocess failures are environment-specific + raise FileNotFoundError( + 'Executable not found for modkit. Install dimelo using "conda env create -f environment.yml" ' + 'or install modkit manually using "conda install nanoporetech::modkit==0.6.1". ' + "Without modkit you cannot run parse_bam functions." + ) from exc + + version_raw = (version_result.stdout or version_result.stderr).strip() + version_tuple = _parse_modkit_semver(version_raw) + version = ".".join(str(value) for value in version_tuple) if version_tuple else None + + pileup_help = subprocess.run( + [executable, "pileup", "--help"], + capture_output=True, + text=True, + check=True, ) - os.environ["PATH"] = f"{env_bin_path}:{os.environ['PATH']}" - print(f"PATH is now {os.environ['PATH']}") - -# Check modkit on first import: does it run; does it have the right version -try: - result = subprocess.run(["modkit", "--version"], stdout=subprocess.PIPE, text=True) - modkit_version = result.stdout - if modkit_version.split()[1] == EXPECTED_MODKIT_VERSION: - pass - # print(f"modkit found with expected version {EXPECTED_MODKIT_VERSION}") - else: + pileup_help_text = (pileup_help.stdout or "") + "\n" + (pileup_help.stderr or "") + + extract_help = subprocess.run( + [executable, "extract", "--help"], + capture_output=True, + text=True, + check=True, + ) + extract_help_text = (extract_help.stdout or "") + "\n" + (extract_help.stderr or "") + supports_extract_subcommands = ( + re.search(r"(?m)^Usage:\s+modkit\s+extract\s+", extract_help_text) + is not None + ) + if ( + not supports_extract_subcommands + and version_tuple is not None + and len(version_tuple) >= 2 + and (version_tuple[0], version_tuple[1]) >= (0, 6) + ): + supports_extract_subcommands = True + extract_command_help_text = extract_help_text + if supports_extract_subcommands: + try: + extract_full_help = subprocess.run( + [executable, "extract", "full", "--help"], + capture_output=True, + text=True, + check=True, + ) + extract_command_help_text = ( + (extract_full_help.stdout or "") + + "\n" + + (extract_full_help.stderr or "") + ) + except subprocess.CalledProcessError: + # Fall back to top-level extract help text if subcommand help probing fails. + extract_command_help_text = extract_help_text + + minor_version = ( + (version_tuple[0], version_tuple[1]) + if version_tuple and len(version_tuple) >= 2 + else None + ) + if ( + minor_version is not None + and minor_version not in SUPPORTED_MODKIT_MINOR_VERSIONS + and not quiet + ): print( - f"modkit found with unexpected version {modkit_version.split()[1]}. Versions other than {EXPECTED_MODKIT_VERSION} may exhibit unexpected behavior. It is recommended that you use v{EXPECTED_MODKIT_VERSION}" + "modkit found with version " + f"{version or version_raw}. Officially tested series are {', '.join(SUPPORTED_MODKIT_SERIES)}." ) -except subprocess.CalledProcessError: - print( - 'Executable not found for modkit. Install dimelo using "conda env create -f environment.yml" or install modkit manually to your conda environment using "conda install nanoporetech::modkit==0.2.4". Without modkit you cannot run parse_bam functions.' + + return ModkitCapabilities( + executable=executable, + version_raw=version_raw, + version=version, + version_tuple=version_tuple, + supports_mod_threshold=_help_supports_flag(pileup_help_text, "--mod-threshold"), + supports_mod_thresholds=_help_supports_flag( + pileup_help_text, "--mod-thresholds" + ), + supports_modified_bases=_help_supports_flag( + pileup_help_text, "--modified-bases" + ), + supports_force_allow_implicit=_help_supports_flag( + pileup_help_text, "--force-allow-implicit" + ), + supports_extract_subcommands=supports_extract_subcommands, + extract_supports_reference_long=_help_supports_flag( + extract_command_help_text, "--reference" + ), + extract_supports_reference_short=_help_supports_flag( + extract_command_help_text, "--ref" + ), ) +def get_modkit_capabilities( + quiet: bool = False, + executable: str | None = None, +) -> ModkitCapabilities: + _prepare_modkit_path(quiet=quiet) + executable_key = executable or os.environ.get(MODKIT_EXECUTABLE_ENV) or "modkit" + resolved_executable = _resolve_modkit_executable(executable_key) + executable_fingerprint = _modkit_cache_fingerprint(resolved_executable) + return _get_modkit_capabilities_cached( + executable_path=resolved_executable, + executable_fingerprint=executable_fingerprint, + quiet=quiet, + ) + + +def configure_modkit_executable(executable: str | Path | None) -> None: + """ + Configure which modkit binary should be used by parse operations. + Pass None to clear explicit override and fall back to PATH resolution. + """ + if executable is None: + os.environ.pop(MODKIT_EXECUTABLE_ENV, None) + else: + os.environ[MODKIT_EXECUTABLE_ENV] = str(executable) + _get_modkit_capabilities_cached.cache_clear() + + +def _ensure_modkit_available( + quiet: bool = False, + executable: str | Path | None = None, +) -> ModkitCapabilities: + """ + Lazily check that modkit is on PATH and return parsed capabilities. + Called by parse functions to avoid import-time failures during analysis-only workflows. + """ + executable_override = None if executable is None else str(executable) + return get_modkit_capabilities(quiet=quiet, executable=executable_override) + + def run_with_progress_bars( command_list: list[str], input_file: Path, @@ -114,16 +348,17 @@ def run_with_progress_bars( f"{ref_genome.name} is gzipped, which will cause modkit to fail.\ngunzip {ref_genome.name} and try again." ) - # Set up progress bar variables to display progress updates when not in quiet mode + render_progress = (not quiet) and _should_render_live_progress() + + # Set up progress bar variables to display progress updates when enabled format_pre = "{bar}| {desc} {percentage:3.0f}% | {elapsed}" format_contigs = "{bar}| {desc} {percentage:3.0f}% | {elapsed}<{remaining}" format_chr = "{bar}| {desc} {percentage:3.0f}%" - pbar_pre: Optional[tqdm] = None - pbar_contigs: Optional[tqdm] = None - pbar_chr: Optional[tqdm] = None + pbar_pre: tqdm | None = None + pbar_contigs: tqdm | None = None + pbar_chr: tqdm | None = None - # TODO: Is this the correct type annotation? I think it is, based on approx. line 280 - finding_progress_dict: dict[str, tuple[int, int]] = {} + finding_progress_dict: FindingProgressDict = {} in_contig_progress = (0, 1) total_contigs = 0 @@ -163,82 +398,80 @@ def run_with_progress_bars( if not data: break # No more data - if quiet: - # If we are in quiet mode, nothing gets grabbed - continue - else: - # Create the progress bars when first entering this code block - if not progress_bars_initialized: - pbar_pre = tqdm( - total=100, - desc=f"Step 1: Identify motif locations in {ref_genome.name}", - bar_format=format_pre, - ) - pbar_contigs = tqdm( - total=100, - desc=f"Step 2: Parse regions in {input_file.name}", - bar_format=format_contigs, - ) - pbar_chr = tqdm( - total=100, - desc="", - bar_format=format_chr, + if render_progress and not progress_bars_initialized: + pbar_pre = tqdm( + total=100, + desc=f"Step 1: Identify motif locations in {ref_genome.name}", + bar_format=format_pre, + ) + pbar_contigs = tqdm( + total=100, + desc=f"Step 2: Parse regions in {input_file.name}", + bar_format=format_contigs, + ) + pbar_chr = tqdm( + total=100, + desc="", + bar_format=format_chr, + ) + progress_bars_initialized = True + + buffer_bytes += data # Accumulate bytes in the buffer + + try: + # Try to decode the accumulated bytes + # This will throw a UnicodeDecodeError if not complete, which is ok! Then we just continue on + text = buffer_bytes.decode("utf-8") + readout_count += 1 + buffer_bytes.clear() # Clear the buffer after successful decoding + # If we have hit an error or modkit is done, just accumulate the rest of the output and then deal with it: + # no need to check the progress tracking stuff in that case + if err_flag or done_flag: + tail_buffer = _strip_runtime_noise(tail_buffer + text) + # If we haven't hit an error or a done state, first check for that + else: + tail_buffer = _strip_runtime_noise( + (tail_buffer + text)[-buffer_size:] ) - progress_bars_initialized = True - - buffer_bytes += data # Accumulate bytes in the buffer - - try: - # Try to decode the accumulated bytes - # This will throw a UnicodeDecodeError if not complete, which is ok! Then we just continue on - text = buffer_bytes.decode("utf-8") - readout_count += 1 - buffer_bytes.clear() # Clear the buffer after successful decoding - # If we have hit an error or modkit is done, just accumulate the rest of the output and then deal with it: - # no need to check the progress tracking stuff in that case - if err_flag or done_flag: - tail_buffer += text - # If we haven't hit an error or a done state, first check for that - else: - tail_buffer = (tail_buffer + text)[-buffer_size:] - if err_str in tail_buffer: - index = tail_buffer.find(err_str) - tail_buffer = tail_buffer[index:] - err_flag = True - elif done_str in tail_buffer: - index = tail_buffer.find(done_str) - tail_buffer = tail_buffer[index - 2 :] - done_flag = True - # If the process is ongoing, then go through the possible cases and create/adjust pbars accordingly - # We only sometimes want to update progress because otherwise the constant updates slow us down - elif ( - readout_count % progress_granularity == 0 - and progress_bars_initialized - ): - region_parsing_started, in_contig_progress = ( - update_progress_bars( - pbar_pre=pbar_pre, - pbar_contigs=pbar_contigs, - pbar_chr=pbar_chr, - tail_buffer=tail_buffer, - contigs_progress_regex=contigs_progress_regex, - single_contig_regex=single_contig_regex, - find_motifs_regex=find_motifs_regex, - load_fasta_regex=load_fasta_regex, - region_parsing_started=region_parsing_started, - in_contig_progress=in_contig_progress, - finding_progress_dict=finding_progress_dict, - ref_genome=ref_genome, - input_file=input_file, - motifs=motifs, - ) + if err_str in tail_buffer: + index = tail_buffer.find(err_str) + tail_buffer = tail_buffer[index:] + err_flag = True + elif done_str in tail_buffer: + index = tail_buffer.find(done_str) + tail_buffer = tail_buffer[index - 2 :] + done_flag = True + # If the process is ongoing, then go through the possible cases and create/adjust pbars accordingly + # We only sometimes want to update progress because otherwise the constant updates slow us down + elif ( + render_progress + and readout_count % progress_granularity == 0 + and progress_bars_initialized + ): + region_parsing_started, in_contig_progress = ( + update_progress_bars( + pbar_pre=pbar_pre, + pbar_contigs=pbar_contigs, + pbar_chr=pbar_chr, + tail_buffer=tail_buffer, + contigs_progress_regex=contigs_progress_regex, + single_contig_regex=single_contig_regex, + find_motifs_regex=find_motifs_regex, + load_fasta_regex=load_fasta_regex, + region_parsing_started=region_parsing_started, + in_contig_progress=in_contig_progress, + finding_progress_dict=finding_progress_dict, + ref_genome=ref_genome, + input_file=input_file, + motifs=motifs, ) + ) - except UnicodeDecodeError: - # If decoding fails, continue accumulating bytes - continue - except Exception as e: - raise e + except UnicodeDecodeError: + # If decoding fails, continue accumulating bytes + continue + except Exception as e: + raise e except OSError: break @@ -319,21 +552,21 @@ def run_with_progress_bars( def update_progress_bars( - pbar_pre, - pbar_contigs, - pbar_chr, - tail_buffer, - contigs_progress_regex, - single_contig_regex, - find_motifs_regex, - load_fasta_regex, - region_parsing_started, - in_contig_progress, - finding_progress_dict, - ref_genome, - input_file, - motifs, -): + pbar_pre: tqdm | None, + pbar_contigs: tqdm | None, + pbar_chr: tqdm | None, + tail_buffer: str, + contigs_progress_regex: str, + single_contig_regex: str, + find_motifs_regex: str, + load_fasta_regex: str, + region_parsing_started: bool, + in_contig_progress: tuple[int, int], + finding_progress_dict: FindingProgressDict, + ref_genome: Path, + input_file: Path, + motifs: list[str], +) -> tuple[bool, tuple[int, int]]: # We check these in the reverse order from that in which they occur, which I guess will save a tiny # amount of processing time because we don't check for previous steps when on later steps # Once we are in the contig progress stage, step 1 is done by definition diff --git a/dimelo/shared_cluster_tests.py b/dimelo/shared_cluster_tests.py new file mode 100644 index 0000000..d6ba032 --- /dev/null +++ b/dimelo/shared_cluster_tests.py @@ -0,0 +1,892 @@ +from __future__ import annotations + +from typing import Any + +import numpy as np +import pandas as pd +from scipy import stats + +from dimelo.models import ContrastSpec, SharedClusterContrastResult, SharedClusterResult + +_SUPPORTED_SHARED_CLUSTER_MODES = { + "pairwise", + "matched_pairwise", + "group_vs_group", + "time_course", +} + +_SUPPORTED_POOLED_SCREEN_TESTS = {"chi_squared", "g_test"} + + +def _require_supported_shared_cluster_mode(contrast: ContrastSpec) -> None: + if contrast.mode not in _SUPPORTED_SHARED_CLUSTER_MODES: + raise NotImplementedError( + f"Shared cluster tests are not implemented for contrast mode '{contrast.mode}'." + ) + + +def _require_requested_conditions_present( + sample_table: pd.DataFrame, + *, + side_specs: dict[str, list[str]], +) -> None: + available_conditions = set(sample_table["condition"].dropna().unique()) + for side, conditions in side_specs.items(): + missing = sorted(set(conditions) - available_conditions) + if missing: + raise ValueError( + f"Missing {side} evidence for requested condition(s): {', '.join(missing)}." + ) + + +def _require_time_order_present( + sample_table: pd.DataFrame, time_order: list[str] +) -> None: + available_conditions = set(sample_table["condition"].dropna().unique()) + missing = [ + condition for condition in time_order if condition not in available_conditions + ] + if missing: + raise ValueError( + "Shared cluster time_course requested missing time_order condition(s): " + + ", ".join(missing) + ) + + +def _sample_fraction_table(result: SharedClusterResult) -> pd.DataFrame: + required = {"sample_id", "condition", "cluster", "fraction"} + missing = required - set(result.cluster_distribution.columns) + if missing: + raise ValueError( + "Shared cluster tests require cluster_distribution columns: " + + ", ".join(sorted(missing)) + + "." + ) + return result.cluster_distribution.loc[ + :, ["sample_id", "condition", "cluster", "fraction"] + ].copy() + + +def _condition_count_table(result: SharedClusterResult) -> pd.DataFrame: + required = {"condition", "cluster", "count", "fraction"} + missing = required - set(result.condition_distribution.columns) + if missing: + raise ValueError( + "Shared cluster tests require condition_distribution columns: " + + ", ".join(sorted(missing)) + + "." + ) + return result.condition_distribution.loc[ + :, ["condition", "cluster", "count", "fraction"] + ].copy() + + +def _cluster_order(result: SharedClusterResult) -> list[str]: + cluster_labels = list(result.model.cluster_labels) + if cluster_labels: + return cluster_labels + return ( + result.cluster_distribution["cluster"] + .drop_duplicates() + .astype(str) + .sort_values() + .tolist() + ) + + +def _composition_effect_size(summary_table: pd.DataFrame) -> float: + return float(summary_table["delta_fraction"].abs().sum() / 2.0) + + +def _composition_effect_size_from_vectors( + observed: np.ndarray, reference: np.ndarray +) -> float: + return float(np.abs(observed - reference).sum() / 2.0) + + +def _trend_statistic(timepoint_fractions: np.ndarray) -> float: + if timepoint_fractions.shape[0] < 2: + return 0.0 + + positions = np.arange(timepoint_fractions.shape[0], dtype=float) + slopes = [ + np.polyfit(positions, timepoint_fractions[:, cluster_index], 1)[0] + for cluster_index in range(timepoint_fractions.shape[1]) + ] + return float(np.abs(slopes).sum() / 2.0) + + +def _adjust_p_values_bh(p_values: pd.Series) -> pd.Series: + if p_values.empty: + return pd.Series(dtype=float, index=p_values.index) + + numeric = pd.to_numeric(p_values, errors="coerce") + valid_mask = numeric.notna() + if not valid_mask.any(): + return pd.Series(np.nan, index=p_values.index, dtype=float) + + valid_values = numeric[valid_mask].to_numpy(dtype=float) + order = np.argsort(valid_values, kind="mergesort") + ranked = valid_values[order] + adjusted = np.empty_like(ranked) + running_min = 1.0 + total = len(ranked) + + for index in range(total - 1, -1, -1): + rank = index + 1 + candidate = min(1.0, ranked[index] * total / rank) + running_min = min(running_min, candidate) + adjusted[index] = running_min + + restored = np.full(len(p_values), np.nan, dtype=float) + restored_indices = np.flatnonzero(valid_mask.to_numpy()) + restored[restored_indices[order]] = adjusted + return pd.Series(restored, index=p_values.index, dtype=float) + + +def _contrast_id(contrast: ContrastSpec) -> str: + numerator = "+".join(contrast.numerator or []) + denominator = "+".join(contrast.denominator or []) + if contrast.mode == "time_course": + time_order = "+".join(contrast.time_order or []) + return f"time_course:{time_order}" + return f"{numerator}_vs_{denominator}" + + +def _sample_cluster_matrix(result: SharedClusterResult) -> pd.DataFrame: + sample_table = _sample_fraction_table(result) + cluster_order = _cluster_order(result) + sample_info = sample_table.loc[:, ["sample_id", "condition"]].drop_duplicates() + matrix = ( + sample_table.pivot_table( + index="sample_id", + columns="cluster", + values="fraction", + aggfunc="first", + fill_value=0.0, + ) + .reindex(columns=cluster_order, fill_value=0.0) + .reset_index() + ) + return sample_info.merge(matrix, on="sample_id", how="left") + + +def _add_pairing_metadata( + sample_matrix: pd.DataFrame, + *, + assignments: pd.DataFrame, + pairing_key: str, +) -> pd.DataFrame: + if pairing_key not in assignments.columns: + raise ValueError( + f"Shared cluster matched_pairwise requires assignments column {pairing_key!r}." + ) + + pairing = assignments.loc[:, ["sample_id", pairing_key]].drop_duplicates() + pair_counts = pairing.groupby("sample_id", dropna=False)[pairing_key].nunique( + dropna=False + ) + if (pair_counts > 1).any(): + raise ValueError( + "Shared cluster matched_pairwise requires each sample_id to map to exactly one pairing key." + ) + if pairing[pairing_key].isna().any(): + raise ValueError( + f"Shared cluster matched_pairwise requires non-null values in column {pairing_key!r}." + ) + return sample_matrix.merge(pairing, on="sample_id", how="left") + + +def _prepare_unpaired_group_table( + result: SharedClusterResult, + *, + contrast: ContrastSpec, +) -> tuple[pd.DataFrame, list[str]]: + sample_matrix = _sample_cluster_matrix(result) + cluster_order = _cluster_order(result) + side_specs = { + "numerator": contrast.numerator or [], + "denominator": contrast.denominator or [], + } + _require_requested_conditions_present(sample_matrix, side_specs=side_specs) + requested_conditions = set(side_specs["numerator"]) | set(side_specs["denominator"]) + filtered = sample_matrix.loc[ + sample_matrix["condition"].isin(requested_conditions) + ].copy() + filtered["contrast_side"] = np.where( + filtered["condition"].isin(side_specs["numerator"]), + "numerator", + "denominator", + ) + return filtered, cluster_order + + +def _prepare_paired_group_table( + result: SharedClusterResult, + *, + contrast: ContrastSpec, +) -> tuple[pd.DataFrame, list[str]]: + if len(contrast.numerator or []) != 1 or len(contrast.denominator or []) != 1: + raise ValueError( + "Shared cluster matched_pairwise currently requires exactly one numerator " + "and one denominator condition." + ) + + sample_matrix = _sample_cluster_matrix(result) + sample_matrix = _add_pairing_metadata( + sample_matrix, + assignments=result.assignments, + pairing_key=contrast.pairing_key or "", + ) + cluster_order = _cluster_order(result) + side_specs = { + "numerator": contrast.numerator or [], + "denominator": contrast.denominator or [], + } + _require_requested_conditions_present(sample_matrix, side_specs=side_specs) + requested_conditions = set(side_specs["numerator"]) | set(side_specs["denominator"]) + filtered = sample_matrix.loc[ + sample_matrix["condition"].isin(requested_conditions) + ].copy() + filtered["contrast_side"] = np.where( + filtered["condition"].isin(side_specs["numerator"]), + "numerator", + "denominator", + ) + + grouped = filtered.groupby( + [contrast.pairing_key, "contrast_side"], as_index=False, sort=False + )[cluster_order].mean() + side_sets = ( + grouped.loc[:, [contrast.pairing_key, "contrast_side"]] + .drop_duplicates() + .groupby(contrast.pairing_key)["contrast_side"] + .agg(lambda values: set(values)) + ) + complete_pairs = [ + pair_id + for pair_id, sides in side_sets.items() + if {"numerator", "denominator"} <= sides + ] + if not complete_pairs: + raise ValueError( + "Shared cluster matched_pairwise found no complete matched units." + ) + + return grouped.loc[ + grouped[contrast.pairing_key].isin(complete_pairs) + ].copy(), cluster_order + + +def _permutation_p_value(observed: float, permuted: np.ndarray) -> float: + return float( + (1 + np.count_nonzero(permuted >= observed - 1e-12)) / (len(permuted) + 1) + ) + + +def _run_unpaired_permutations( + values: np.ndarray, + *, + n_numerator: int, + n_permutations: int, + random_state: int | None, +) -> tuple[np.ndarray, np.ndarray]: + rng = np.random.default_rng(random_state) + cluster_stats = np.zeros((n_permutations, values.shape[1]), dtype=float) + omnibus_stats = np.zeros(n_permutations, dtype=float) + + for permutation_index in range(n_permutations): + permuted_indices = rng.permutation(values.shape[0]) + numerator = values[permuted_indices[:n_numerator]] + denominator = values[permuted_indices[n_numerator:]] + delta = numerator.mean(axis=0) - denominator.mean(axis=0) + cluster_stats[permutation_index] = np.abs(delta) + omnibus_stats[permutation_index] = np.abs(delta).sum() / 2.0 + + return cluster_stats, omnibus_stats + + +def _run_paired_permutations( + pair_deltas: np.ndarray, + *, + n_permutations: int, + random_state: int | None, +) -> tuple[np.ndarray, np.ndarray]: + rng = np.random.default_rng(random_state) + cluster_stats = np.zeros((n_permutations, pair_deltas.shape[1]), dtype=float) + omnibus_stats = np.zeros(n_permutations, dtype=float) + + for permutation_index in range(n_permutations): + signs = rng.choice(np.array([-1.0, 1.0]), size=pair_deltas.shape[0])[:, None] + delta = (pair_deltas * signs).mean(axis=0) + cluster_stats[permutation_index] = np.abs(delta) + omnibus_stats[permutation_index] = np.abs(delta).sum() / 2.0 + + return cluster_stats, omnibus_stats + + +def _score_unpaired_group( + group_table: pd.DataFrame, + *, + cluster_order: list[str], + n_permutations: int, + random_state: int | None, +) -> tuple[pd.DataFrame, dict[str, Any]]: + numerator = group_table.loc[ + group_table["contrast_side"] == "numerator", cluster_order + ] + denominator = group_table.loc[ + group_table["contrast_side"] == "denominator", cluster_order + ] + if numerator.empty or denominator.empty: + raise ValueError( + "Shared cluster tests require evidence for both contrast sides." + ) + + observed_fraction = numerator.mean(axis=0) + reference_fraction = denominator.mean(axis=0) + delta = observed_fraction - reference_fraction + details = pd.DataFrame( + { + "cluster": cluster_order, + "fraction": observed_fraction.to_numpy(dtype=float), + "reference_fraction": reference_fraction.to_numpy(dtype=float), + "delta_fraction": delta.to_numpy(dtype=float), + } + ) + + perm_cluster, perm_omnibus = _run_unpaired_permutations( + group_table.loc[:, cluster_order].to_numpy(dtype=float), + n_numerator=len(numerator), + n_permutations=n_permutations, + random_state=random_state, + ) + return details, { + "permuted_cluster_stats": perm_cluster, + "permuted_omnibus_stats": perm_omnibus, + "paired": False, + "n_numerator": int(len(numerator)), + "n_denominator": int(len(denominator)), + } + + +def _score_paired_group( + pair_table: pd.DataFrame, + *, + cluster_order: list[str], + contrast: ContrastSpec, + n_permutations: int, + random_state: int | None, +) -> tuple[pd.DataFrame, dict[str, Any]]: + pair_key = contrast.pairing_key or "" + numerator = ( + pair_table.loc[pair_table["contrast_side"] == "numerator"] + .set_index(pair_key) + .reindex(columns=cluster_order) + .sort_index() + ) + denominator = ( + pair_table.loc[pair_table["contrast_side"] == "denominator"] + .set_index(pair_key) + .reindex(columns=cluster_order) + .sort_index() + ) + common_pairs = numerator.index.intersection(denominator.index) + if common_pairs.empty: + raise ValueError( + "Shared cluster matched_pairwise found no complete matched units." + ) + + numerator = numerator.loc[common_pairs] + denominator = denominator.loc[common_pairs] + observed_fraction = numerator.mean(axis=0) + reference_fraction = denominator.mean(axis=0) + delta = observed_fraction - reference_fraction + details = pd.DataFrame( + { + "cluster": cluster_order, + "fraction": observed_fraction.to_numpy(dtype=float), + "reference_fraction": reference_fraction.to_numpy(dtype=float), + "delta_fraction": delta.to_numpy(dtype=float), + } + ) + + pair_deltas = numerator.to_numpy(dtype=float) - denominator.to_numpy(dtype=float) + perm_cluster, perm_omnibus = _run_paired_permutations( + pair_deltas, + n_permutations=n_permutations, + random_state=random_state, + ) + return details, { + "permuted_cluster_stats": perm_cluster, + "permuted_omnibus_stats": perm_omnibus, + "paired": True, + "n_pairs_used": int(len(common_pairs)), + } + + +def _finalize_details( + details: pd.DataFrame, + *, + permuted_cluster_stats: np.ndarray, +) -> pd.DataFrame: + finalized = details.copy() + finalized["log2_fc"] = np.log2( + (finalized["fraction"] + 1e-9) / (finalized["reference_fraction"] + 1e-9) + ) + finalized["effect_size"] = finalized["delta_fraction"].abs() + finalized["p_value"] = [ + _permutation_p_value( + float(observed), + permuted_cluster_stats[:, cluster_index], + ) + for cluster_index, observed in enumerate(finalized["effect_size"]) + ] + finalized["adjusted_p_value"] = _adjust_p_values_bh(finalized["p_value"]) + finalized = finalized.sort_values( + ["adjusted_p_value", "p_value", "effect_size", "cluster"], + ascending=[True, True, False, True], + kind="stable", + ).reset_index(drop=True) + finalized["rank"] = np.arange(1, len(finalized) + 1, dtype=int) + return finalized + + +def _build_summary( + details: pd.DataFrame, + *, + contrast: ContrastSpec, + omnibus_p_value: float, + stats_metadata: dict[str, Any], + test: str, + trend_p_value: float | None = None, +) -> pd.DataFrame: + top_cluster_row = details.sort_values( + ["effect_size", "p_value", "cluster"], + ascending=[False, True, True], + kind="stable", + ).iloc[0] + summary_row: dict[str, Any] = { + "contrast_id": _contrast_id(contrast), + "contrast_mode": contrast.mode, + "test": test, + "numerator_conditions": "+".join(contrast.numerator or []), + "denominator_conditions": "+".join(contrast.denominator or []), + "composition_effect_size": _composition_effect_size(details), + "omnibus_p_value": float(omnibus_p_value), + "top_cluster": top_cluster_row["cluster"], + "top_cluster_delta_fraction": float(top_cluster_row["delta_fraction"]), + "effect_size_metric": "total_variation_distance", + "paired": bool(stats_metadata["paired"]), + } + if trend_p_value is not None: + summary_row["trend_p_value"] = float(trend_p_value) + + for key, value in stats_metadata.items(): + if key.startswith("n_"): + summary_row[key] = value + return pd.DataFrame([summary_row]) + + +def _build_pooled_omnibus_p_value( + *, + result: SharedClusterResult, + contrast: ContrastSpec, + test: str, +) -> float: + if test not in _SUPPORTED_POOLED_SCREEN_TESTS: + raise ValueError( + "shared_cluster_tests currently requires test='permutation', " + "'chi_squared', or 'g_test'." + ) + + condition_table = _condition_count_table(result) + side_specs = { + "numerator": contrast.numerator or [], + "denominator": contrast.denominator or [], + } + _require_requested_conditions_present(condition_table, side_specs=side_specs) + requested_conditions = side_specs["numerator"] + side_specs["denominator"] + filtered = condition_table.loc[ + condition_table["condition"].isin(requested_conditions) + ].copy() + contingency = ( + filtered.pivot_table( + index="condition", + columns="cluster", + values="count", + aggfunc="sum", + fill_value=0, + ) + .reindex( + index=requested_conditions, columns=_cluster_order(result), fill_value=0 + ) + .astype(float) + ) + if contingency.shape[0] < 2 or contingency.shape[1] < 2: + raise ValueError( + "shared_cluster_tests pooled screening requires at least 2x2 counts." + ) + + if test == "chi_squared": + _, omnibus_p_value, _, _ = stats.chi2_contingency( + contingency.to_numpy(dtype=float), + correction=False, + ) + else: + _, omnibus_p_value, _, _ = stats.chi2_contingency( + contingency.to_numpy(dtype=float), + correction=False, + lambda_="log-likelihood", + ) + return float(omnibus_p_value) + + +def _mean_by_group_codes( + values: np.ndarray, + group_codes: np.ndarray, + *, + n_groups: int, +) -> np.ndarray: + sums = np.zeros((n_groups, values.shape[1]), dtype=float) + np.add.at(sums, group_codes, values) + counts = np.bincount(group_codes, minlength=n_groups).astype(float, copy=False) + means = np.zeros_like(sums) + np.divide(sums, counts[:, None], out=means, where=counts[:, None] > 0) + return means + + +def _score_time_course( + *, + result: SharedClusterResult, + contrast: ContrastSpec, + n_permutations: int, + random_state: int | None, + include_pairwise: bool, +) -> SharedClusterContrastResult: + time_order = list(dict.fromkeys(contrast.time_order or [])) + if not time_order: + raise ValueError("Shared cluster time_course requires contrast.time_order.") + + sample_matrix = _sample_cluster_matrix(result) + _require_time_order_present(sample_matrix, time_order) + cluster_order = _cluster_order(result) + + filtered = sample_matrix.loc[sample_matrix["condition"].isin(time_order)].copy() + filtered["condition"] = pd.Categorical( + filtered["condition"], categories=time_order, ordered=True + ) + filtered = filtered.sort_values( + ["condition", "sample_id"], kind="stable" + ).reset_index(drop=True) + + n_timepoints = len(time_order) + condition_codes = filtered["condition"].cat.codes.to_numpy( + dtype=np.int64, copy=False + ) + if (condition_codes < 0).any(): + raise ValueError( + "Shared cluster time_course found rows with conditions outside time_order." + ) + value_matrix = filtered.loc[:, cluster_order].to_numpy(dtype=float) + observed_matrix = _mean_by_group_codes( + value_matrix, + condition_codes, + n_groups=n_timepoints, + ) + counts = np.bincount(condition_codes, minlength=n_timepoints).astype( + int, copy=False + ) + time_course_table = pd.DataFrame(observed_matrix, columns=cluster_order) + time_course_table.insert(0, "timepoint", time_order) + time_course_table["n_samples"] = counts + + observed_first = observed_matrix[0] + observed_last = observed_matrix[-1] + observed_delta = observed_last - observed_first + observed_omnibus = _composition_effect_size_from_vectors( + observed_last, observed_first + ) + observed_trend = _trend_statistic(observed_matrix) + + rng = np.random.default_rng(random_state) + permuted_cluster_stats = np.zeros((n_permutations, len(cluster_order)), dtype=float) + permuted_omnibus_stats = np.zeros(n_permutations, dtype=float) + permuted_trend_stats = np.zeros(n_permutations, dtype=float) + + for permutation_index in range(n_permutations): + permuted_codes = rng.permutation(condition_codes) + permuted_matrix = _mean_by_group_codes( + value_matrix, + permuted_codes, + n_groups=n_timepoints, + ) + permuted_delta = permuted_matrix[-1] - permuted_matrix[0] + permuted_cluster_stats[permutation_index] = np.abs(permuted_delta) + permuted_omnibus_stats[permutation_index] = ( + _composition_effect_size_from_vectors( + permuted_matrix[-1], + permuted_matrix[0], + ) + ) + permuted_trend_stats[permutation_index] = _trend_statistic(permuted_matrix) + + details = pd.DataFrame( + { + "cluster": cluster_order, + "fraction": observed_last, + "reference_fraction": observed_first, + "delta_fraction": observed_delta, + } + ) + details = _finalize_details(details, permuted_cluster_stats=permuted_cluster_stats) + + omnibus_p_value = _permutation_p_value(observed_omnibus, permuted_omnibus_stats) + trend_p_value = _permutation_p_value(observed_trend, permuted_trend_stats) + summary = _build_summary( + details, + contrast=contrast, + omnibus_p_value=omnibus_p_value, + stats_metadata={ + "paired": False, + "n_timepoints": int(len(time_order)), + "n_samples": int(filtered["sample_id"].nunique()), + }, + test="permutation", + trend_p_value=trend_p_value, + ) + summary["test"] = "permutation" + summary["trend_p_value"] = float(trend_p_value) + + pairwise = None + plot_data: dict[str, pd.DataFrame | dict[str, Any]] = { + "summary_table": summary.copy(), + "cluster_effect_table": details.copy(), + "time_course_table": time_course_table.copy(), + } + + if include_pairwise: + pairwise_rows = [] + for left_timepoint, right_timepoint in zip( + time_order[:-1], time_order[1:], strict=False + ): + pair_table = filtered.loc[ + filtered["condition"].isin([left_timepoint, right_timepoint]) + ].copy() + pair_table["contrast_side"] = np.where( + pair_table["condition"] == left_timepoint, + "numerator", + "denominator", + ) + pair_details, stats_metadata = _score_unpaired_group( + pair_table, + cluster_order=cluster_order, + n_permutations=n_permutations, + random_state=random_state, + ) + pair_details = _finalize_details( + pair_details, + permuted_cluster_stats=stats_metadata["permuted_cluster_stats"], + ) + pair_omnibus = _permutation_p_value( + _composition_effect_size(pair_details), + stats_metadata["permuted_omnibus_stats"], + ) + pair_summary = _build_summary( + pair_details, + contrast=ContrastSpec( + mode="pairwise", + numerator=[left_timepoint], + denominator=[right_timepoint], + ), + omnibus_p_value=pair_omnibus, + stats_metadata=stats_metadata, + test="permutation", + ) + row = pair_summary.iloc[0].to_dict() + row["left_timepoint"] = left_timepoint + row["right_timepoint"] = right_timepoint + pairwise_rows.append(row) + + pairwise = pd.DataFrame(pairwise_rows) + plot_data["pairwise_table"] = pairwise.copy() + + metadata: dict[str, Any] = { + "contrast_mode": contrast.mode, + "contrast_id": _contrast_id(contrast), + "paired": False, + "pairing_key": None, + "test": "permutation", + "multiple_testing": "fdr_bh", + "n_permutations": int(n_permutations), + "random_state": random_state, + "inference_level": "replicate_aware", + "n_timepoints": int(len(time_order)), + "n_samples": int(filtered["sample_id"].nunique()), + } + return SharedClusterContrastResult( + summary=summary, + details=details, + pairwise=pairwise, + plot_data=plot_data, + metadata=metadata, + ) + + +def shared_cluster_tests( + *, + result: SharedClusterResult, + contrast: ContrastSpec, + test: str = "permutation", + multiple_testing: str = "fdr_bh", + n_permutations: int = 1000, + random_state: int | None = 42, + include_pairwise: bool = False, +) -> SharedClusterContrastResult: + _require_supported_shared_cluster_mode(contrast) + if multiple_testing != "fdr_bh": + raise ValueError( + "shared_cluster_tests currently requires multiple_testing='fdr_bh'." + ) + if n_permutations <= 0: + raise ValueError("shared_cluster_tests requires n_permutations > 0.") + + if contrast.mode == "time_course": + if test != "permutation": + raise ValueError( + "shared_cluster_tests time_course currently requires test='permutation'." + ) + return _score_time_course( + result=result, + contrast=contrast, + n_permutations=n_permutations, + random_state=random_state, + include_pairwise=include_pairwise, + ) + + if test == "permutation": + if contrast.mode == "matched_pairwise": + pair_table, cluster_order = _prepare_paired_group_table( + result, contrast=contrast + ) + details, stats_metadata = _score_paired_group( + pair_table, + cluster_order=cluster_order, + contrast=contrast, + n_permutations=n_permutations, + random_state=random_state, + ) + sample_plot_table = pair_table.copy() + else: + group_table, cluster_order = _prepare_unpaired_group_table( + result, contrast=contrast + ) + details, stats_metadata = _score_unpaired_group( + group_table, + cluster_order=cluster_order, + n_permutations=n_permutations, + random_state=random_state, + ) + sample_plot_table = group_table.copy() + + details = _finalize_details( + details, + permuted_cluster_stats=stats_metadata["permuted_cluster_stats"], + ) + omnibus_p_value = _permutation_p_value( + _composition_effect_size(details), + stats_metadata["permuted_omnibus_stats"], + ) + summary = _build_summary( + details, + contrast=contrast, + omnibus_p_value=omnibus_p_value, + stats_metadata=stats_metadata, + test=test, + ) + metadata: dict[str, Any] = { + "contrast_mode": contrast.mode, + "contrast_id": _contrast_id(contrast), + "paired": bool(stats_metadata["paired"]), + "pairing_key": contrast.pairing_key + if contrast.mode == "matched_pairwise" + else None, + "test": test, + "multiple_testing": multiple_testing, + "n_permutations": int(n_permutations), + "random_state": random_state, + "inference_level": "replicate_aware", + } + metadata.update( + { + key: value + for key, value in stats_metadata.items() + if key.startswith("n_") + } + ) + plot_data = { + "summary_table": summary.copy(), + "cluster_effect_table": details.copy(), + "sample_fraction_table": sample_plot_table, + } + return SharedClusterContrastResult( + summary=summary, + details=details, + plot_data=plot_data, + metadata=metadata, + ) + + if test not in _SUPPORTED_POOLED_SCREEN_TESTS: + raise ValueError( + "shared_cluster_tests currently requires test='permutation', " + "'chi_squared', or 'g_test'." + ) + + group_table, cluster_order = _prepare_unpaired_group_table( + result, contrast=contrast + ) + details, stats_metadata = _score_unpaired_group( + group_table, + cluster_order=cluster_order, + n_permutations=n_permutations, + random_state=random_state, + ) + details = _finalize_details( + details, + permuted_cluster_stats=stats_metadata["permuted_cluster_stats"], + ) + omnibus_p_value = _build_pooled_omnibus_p_value( + result=result, contrast=contrast, test=test + ) + summary = _build_summary( + details, + contrast=contrast, + omnibus_p_value=omnibus_p_value, + stats_metadata=stats_metadata, + test=test, + ) + metadata: dict[str, Any] = { + "contrast_mode": contrast.mode, + "contrast_id": _contrast_id(contrast), + "paired": bool(stats_metadata["paired"]), + "pairing_key": None, + "test": test, + "multiple_testing": multiple_testing, + "n_permutations": int(n_permutations), + "random_state": random_state, + "inference_level": "pooled_screen", + } + metadata.update( + {key: value for key, value in stats_metadata.items() if key.startswith("n_")} + ) + plot_data = { + "summary_table": summary.copy(), + "cluster_effect_table": details.copy(), + "sample_fraction_table": group_table.copy(), + } + return SharedClusterContrastResult( + summary=summary, + details=details, + plot_data=plot_data, + metadata=metadata, + ) diff --git a/dimelo/test/data/ctcf_demo_peak_and_not_peak.sorted.merged.bed b/dimelo/test/data/ctcf_demo_peak_and_not_peak.sorted.merged.bed new file mode 100644 index 0000000..c9a58c3 --- /dev/null +++ b/dimelo/test/data/ctcf_demo_peak_and_not_peak.sorted.merged.bed @@ -0,0 +1,114 @@ +chr1 9168068 9168218 +chr1 9169918 9170303 +chr1 9172003 9172153 +chr1 114356586 114356736 +chr1 114358436 114358754 +chr1 114360454 114360604 +chr1 150101499 150101649 +chr1 150103349 150103702 +chr1 150105402 150105552 +chr1 224661531 224661681 +chr1 224663381 224663751 +chr1 224665451 224665601 +chr12 57871837 57871987 +chr12 57873687 57874053 +chr12 57875753 57875903 +chr14 17377273 17377423 +chr14 17379123 17379509 +chr14 17381209 17381359 +chr14 44123159 44123308 +chr14 44125008 44125326 +chr14 44127026 44127176 +chr15 38664850 38665000 +chr15 38666700 38667060 +chr15 38668760 38668910 +chr15 54632158 54632308 +chr15 54634008 54634360 +chr15 54636060 54636210 +chr16 4279474 4279624 +chr16 4281324 4281704 +chr16 4283404 4283554 +chr16 61261359 61261509 +chr16 61263209 61263606 +chr16 61265306 61265456 +chr16 63442391 63442541 +chr16 63444241 63444602 +chr16 63446302 63446452 +chr17 42517551 42517701 +chr17 42519401 42519781 +chr17 42521481 42521631 +chr17 44334563 44334713 +chr17 44336413 44336731 +chr17 44338431 44338581 +chr17 45987586 45987736 +chr17 45989436 45989794 +chr17 45991494 45991644 +chr17 82008550 82008700 +chr17 82010400 82010849 +chr17 82012549 82012699 +chr19 41732348 41732498 +chr19 41734198 41734516 +chr19 41736216 41736366 +chr19 43453207 43453357 +chr19 43455057 43455420 +chr19 43457120 43457270 +chr19 44926036 44926186 +chr19 44927886 44928204 +chr19 44929904 44930054 +chr2 44482827 44482977 +chr2 44484677 44484995 +chr2 44486695 44486845 +chr20 33335198 33335348 +chr20 33337048 33337454 +chr20 33339154 33339304 +chr20 36033923 36034073 +chr20 36035773 36036091 +chr20 36037791 36037941 +chr20 40161013 40161163 +chr20 40162863 40163181 +chr20 40164881 40165031 +chr20 59988985 59989135 +chr20 59990835 59991205 +chr20 59992905 59993055 +chr21 24894312 24894462 +chr21 24896162 24896480 +chr21 24898180 24898330 +chr3 48462628 48462778 +chr3 48464478 48464856 +chr3 48466556 48466706 +chr5 111666908 111667058 +chr5 111668758 111669076 +chr5 111670776 111670926 +chr5 138876711 138876861 +chr5 138878561 138878879 +chr5 138880579 138880729 +chr6 15561907 15562057 +chr6 15563757 15564153 +chr6 15565853 15566003 +chr6 41196591 41196741 +chr6 41198441 41198825 +chr6 41200525 41200675 +chr6 53009844 53009994 +chr6 53011694 53012012 +chr6 53013712 53013862 +chr6 73588139 73588289 +chr6 73589989 73590368 +chr6 73592068 73592218 +chr6 155875926 155876076 +chr6 155877776 155878192 +chr6 155879892 155880042 +chr7 152343785 152343935 +chr7 152345635 152346015 +chr7 152347715 152347865 +chr8 22846344 22846494 +chr8 22848194 22848596 +chr8 22850296 22850446 +chr9 145234660 145234810 +chr9 145236510 145236865 +chr9 145238565 145238715 +chrX 9700334 9700484 +chrX 9702184 9702502 +chrX 9704202 9704352 +chrX 99209453 99209603 +chrX 99211303 99211621 +chrX 99213321 99213471 diff --git a/dimelo/test/data/subset_ctcf_demo_bams.sh b/dimelo/test/data/subset_ctcf_demo_bams.sh new file mode 100755 index 0000000..06d6790 --- /dev/null +++ b/dimelo/test/data/subset_ctcf_demo_bams.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ---- inputs ---- +SRC_DIR="/Users/ngamarra/sherlock_oak/data/20250110_NG_one_pot" +OUT_DIR="/Users/ngamarra/Documents/GitHub/dimelo-toolkit/dimelo/test/data" + +PEAK_BED="${OUT_DIR}/ctcf_demo_peak.bed" +NOT_PEAK_BED="${OUT_DIR}/ctcf_demo_not_peak.bed" + +BAMS=( + "barcode17.merged.sorted.bam" + "barcode18.merged.sorted.bam" +) + +# ---- checks ---- +command -v samtools >/dev/null || { echo "ERROR: samtools not found"; exit 1; } +command -v bedtools >/dev/null || { echo "ERROR: bedtools not found"; exit 1; } + +mkdir -p "${OUT_DIR}" + +for f in "${PEAK_BED}" "${NOT_PEAK_BED}"; do + [[ -s "$f" ]] || { echo "ERROR: missing BED: $f"; exit 1; } +done + +# ---- make combined target BED ---- +TARGET_BED="${OUT_DIR}/ctcf_demo_peak_and_not_peak.sorted.merged.bed" + +cat "${PEAK_BED}" "${NOT_PEAK_BED}" \ + | awk 'BEGIN{OFS="\t"} $0 !~ /^#/ && NF >= 3 {print $1,$2,$3}' \ + | sort -k1,1 -k2,2n \ + | bedtools merge -i - \ + > "${TARGET_BED}" + +echo "Wrote target regions:" +echo " ${TARGET_BED}" +wc -l "${TARGET_BED}" + +# ---- subset each BAM ---- +for bam_name in "${BAMS[@]}"; do + in_bam="${SRC_DIR}/${bam_name}" + + [[ -s "$in_bam" ]] || { echo "ERROR: missing BAM: $in_bam"; exit 1; } + + # Ensure source BAM is indexed. This may write to the SSHFS-mounted OAK dir. + if [[ ! -s "${in_bam}.bai" && ! -s "${in_bam%.bam}.bai" ]]; then + echo "Indexing source BAM: ${in_bam}" + samtools index "${in_bam}" + fi + + stem="${bam_name%.bam}" + tmp_bam="${OUT_DIR}/${stem}.ctcf_demo.tmp.bam" + out_bam="${OUT_DIR}/${stem}.ctcf_demo.sorted.bam" + + echo + echo "Subsetting:" + echo " input: ${in_bam}" + echo " output: ${out_bam}" + + # -L keeps all alignments overlapping the BED intervals. + # -b outputs BAM. + samtools view -b -L "${TARGET_BED}" "${in_bam}" \ + | samtools sort -o "${tmp_bam}" - + + mv "${tmp_bam}" "${out_bam}" + samtools index "${out_bam}" + + echo "Done:" + samtools idxstats "${out_bam}" | awk '$3 > 0 {print}' +done + +echo +echo "Finished. Outputs in:" +echo " ${OUT_DIR}" +ls -lh "${OUT_DIR}"/*.ctcf_demo.sorted.bam* diff --git a/dimelo/test/dimelo_test.py b/dimelo/test/dimelo_test.py index 170397a..f20bc6d 100644 --- a/dimelo/test/dimelo_test.py +++ b/dimelo/test/dimelo_test.py @@ -62,12 +62,12 @@ def test_unit__pileup( # Read and compare file contents file1_contents = f1.read() file2_contents = f2.read() - assert ( - file1_contents == file2_contents - ), f"{test_case}: {pileup_bed} does not match {pileup_target}." - assert filecmp.cmp( - regions_processed, regions_target, shallow=False - ), f"{test_case}: {regions_processed} does not match {regions_target}." + assert file1_contents == file2_contents, ( + f"{test_case}: {pileup_bed} does not match {pileup_target}." + ) + assert filecmp.cmp(regions_processed, regions_target, shallow=False), ( + f"{test_case}: {regions_processed} does not match {regions_target}." + ) else: print(f"{test_case} skipped for pileup.") @@ -119,13 +119,13 @@ def test_unit__extract( for target_item in target_dataset ], f"{test_case}: {dataset} does not match." else: - assert ( - test_dataset == target_dataset - ), f"{test_case}: {dataset} does not match." + assert test_dataset == target_dataset, ( + f"{test_case}: {dataset} does not match." + ) # assert os.path.getsize(extract_h5) == os.path.getsize(extract_target), f"{test_case}: {extract_h5} does not match {extract_target}." - assert filecmp.cmp( - regions_processed, regions_target, shallow=False - ), f"{test_case}: {regions_processed} does not match {regions_target}." + assert filecmp.cmp(regions_processed, regions_target, shallow=False), ( + f"{test_case}: {regions_processed} does not match {regions_target}." + ) else: print(f"{test_case} skipped for extract.") @@ -159,9 +159,9 @@ def test_integration__pileup_load_plot( motif=motif, **kwargs_counts_from_bedmethyl, ) - assert ( - actual == expected - ), f"{test_case}: Counts for motif {motif} are not equal" + assert actual == expected, ( + f"{test_case}: Counts for motif {motif} are not equal" + ) kwargs_vectors_from_bedmethyl = filter_kwargs_for_func( dm.load_processed.pileup_vectors_from_bedmethyl, kwargs @@ -173,16 +173,16 @@ def test_integration__pileup_load_plot( motif=motif, **kwargs_vectors_from_bedmethyl, ) - assert len(expected_tuple) == len( - actual_tuple - ), f"{test_case}: Unexpected number of arrays returned for {motif}" + assert len(expected_tuple) == len(actual_tuple), ( + f"{test_case}: Unexpected number of arrays returned for {motif}" + ) - for expected, actual in zip(expected_tuple, actual_tuple): + for expected, actual in zip(expected_tuple, actual_tuple, strict=False): # TODO: The following was the original assertion error message, but it was not written in a functional way. Find a way to make it work as intended. # assert np.array_equal(expected, actual), f"{test_case}: Arrays for motif {motif} are not equal: expected {value} but got {actual[key]}" - assert np.array_equal( - expected, actual - ), f"{test_case}: Arrays for motif {motif} are not equal." + assert np.array_equal(expected, actual), ( + f"{test_case}: Arrays for motif {motif} are not equal." + ) else: print( f"{test_case} loading skipped for pileup_load_plot, continuing to plotting." @@ -277,13 +277,13 @@ def test_integration__extract_load_plot( {value[np.where(value != actual[key])[0]]} vs {actual[key][np.where(value != actual[key])[0]]}. """ elif isinstance(value, (str, int, bool)): - assert ( - actual[key] == expected[key] - ), f"{test_case}: Values for {key} are not equal: expected {value} but got {actual[key]}." + assert actual[key] == expected[key], ( + f"{test_case}: Values for {key} are not equal: expected {value} but got {actual[key]}." + ) else: - assert np.isclose( - actual[key], value, atol=1e-4 - ), f"{test_case}: Values for {key} are not equal: expected {value} but got {actual[key]}." + assert np.isclose(actual[key], value, atol=1e-4), ( + f"{test_case}: Values for {key} are not equal: expected {value} but got {actual[key]}." + ) else: print("{test_case} skipped for read_vectors_from_hdf5.") kwargs_plot_reads_plot_reads = filter_kwargs_for_func( @@ -301,9 +301,9 @@ def test_integration__extract_load_plot( mod_file_name=extract_h5, **kwargs_plot_reads_plot_reads, ) - assert "No threshold has been applied" in str( - excinfo.value - ), f"{test_case}: unexpected exception {excinfo.value}" + assert "No threshold has been applied" in str(excinfo.value), ( + f"{test_case}: unexpected exception {excinfo.value}" + ) # providing a threshold should be enough to run plot_reads.plot_reads without an error kwargs_plot_reads_plot_reads["thresh"] = 0.75 ax = dm.plot_reads.plot_reads( @@ -384,9 +384,9 @@ def test_unit__pileup_counts_from_bedmethyl( motif=motif, **kwargs_counts_from_bedmethyl, ) - assert ( - actual == expected - ), f"{test_case}: Counts for motif {motif} are not equal" + assert actual == expected, ( + f"{test_case}: Counts for motif {motif} are not equal" + ) else: print(f"{test_case} skipped for pileup_counts_from_bedmethyl.") @@ -407,14 +407,14 @@ def test_unit__pileup_vectors_from_bedmethyl( motif=motif, **kwargs_vectors_from_bedmethyl, ) - assert len(expected_tuple) == len( - actual_tuple - ), f"{test_case}: Unexpected number of arrays returned for {motif}" - - for expected, actual in zip(expected_tuple, actual_tuple): - assert np.array_equal( - expected, actual - ), f"{test_case}: Arrays for motif {motif} are not equal" + assert len(expected_tuple) == len(actual_tuple), ( + f"{test_case}: Unexpected number of arrays returned for {motif}" + ) + + for expected, actual in zip(expected_tuple, actual_tuple, strict=False): + assert np.array_equal(expected, actual), ( + f"{test_case}: Arrays for motif {motif} are not equal" + ) else: print(f"{test_case} skipped for pileup_vectors_from_bedmethyl.") @@ -450,13 +450,13 @@ def test_unit__read_vectors_from_hdf5( {value[np.where(value != actual[key])[0]]} vs {actual[key][np.where(value != actual[key])[0]]}. """ elif isinstance(value, (str, int, bool)): - assert ( - actual[key] == expected[key] - ), f"{test_case}: Values for {key} are not equal: expected {value} but got {actual[key]}." + assert actual[key] == expected[key], ( + f"{test_case}: Values for {key} are not equal: expected {value} but got {actual[key]}." + ) else: - assert np.isclose( - actual[key], value, atol=1e-4 - ), f"{test_case}: Values for {key} are not equal: expected {value} but got {actual[key]}." + assert np.isclose(actual[key], value, atol=1e-4), ( + f"{test_case}: Values for {key} are not equal: expected {value} but got {actual[key]}." + ) else: print("{test_case} skipped for read_vectors_from_hdf5.") @@ -560,9 +560,9 @@ def test_unit__plot_enrichment_plot_enrichment( sample_names=["label" for _ in regions_list], **kwargs_plot_enrichment_plot_enrichment, ) - assert isinstance( - ax, Axes - ), f"{test_case}: plotting failed for {motif}." + assert isinstance(ax, Axes), ( + f"{test_case}: plotting failed for {motif}." + ) else: print(f"{test_case} skipped for plot_enrichment.plot_enrichment.") @@ -589,9 +589,9 @@ def test_unit__plot_enrichment_by_regions( sample_names=["label" for _ in regions_list], **kwargs_plot_enrichment_by_regions, ) - assert isinstance( - ax, Axes - ), f"{test_case}: plotting failed for {motif}." + assert isinstance(ax, Axes), ( + f"{test_case}: plotting failed for {motif}." + ) else: print(f"{test_case} skipped for plot_enrichment.by_regions.") @@ -720,9 +720,9 @@ def test_unit__plot_enrichment_profile_plot_enrichment_profile( sample_names=["label" for _ in regions_list], **kwargs_plot_enrichment_profile_plot_enrichment_profile, ) - assert isinstance( - ax, Axes - ), f"{test_case}: plotting failed for {motif}." + assert isinstance(ax, Axes), ( + f"{test_case}: plotting failed for {motif}." + ) else: print( f"{test_case} skipped for plot_enrichment_profile.plot_enrichment_profile." @@ -753,9 +753,9 @@ def test_unit__plot_enrichment_profile_by_regions( sample_names=["label" for _ in regions_list], **kwargs_plot_enrichment_profile_by_regions, ) - assert isinstance( - ax, Axes - ), f"{test_case}: plotting failed for {motif}." + assert isinstance(ax, Axes), ( + f"{test_case}: plotting failed for {motif}." + ) else: print(f"{test_case} skipped for plot_enrichment_profile.by_regions.") @@ -886,9 +886,9 @@ def test_unit__plot_depth_profile_plot_depth_profile( sample_names=["label" for _ in regions_list], **kwargs_plot_depth_profile_plot_depth_profile, ) - assert isinstance( - ax, Axes - ), f"{test_case}: plotting failed for {motif}." + assert isinstance(ax, Axes), ( + f"{test_case}: plotting failed for {motif}." + ) else: print(f"{test_case} skipped for plot_depth_profile.plot_depth_profile.") @@ -917,9 +917,9 @@ def test_unit__plot_depth_profile_by_regions( sample_names=["label" for _ in regions_list], **kwargs_plot_depth_profile_by_regions, ) - assert isinstance( - ax, Axes - ), f"{test_case}: plotting failed for {motif}." + assert isinstance(ax, Axes), ( + f"{test_case}: plotting failed for {motif}." + ) else: print(f"{test_case} skipped for plot_depth_profile.by_regions.") @@ -1046,9 +1046,9 @@ def test_unit__plot_depth_histogram_plot_depth_histogram( sample_names=["label" for _ in regions_list], **kwargs_plot_depth_histogram_plot_depth_histogram, ) - assert isinstance( - ax, Axes - ), f"{test_case}: plotting failed for {motif}." + assert isinstance(ax, Axes), ( + f"{test_case}: plotting failed for {motif}." + ) else: print(f"{test_case} skipped for plot_depth_histogram.plot_depth_histogram.") @@ -1077,9 +1077,9 @@ def test_unit__plot_depth_histogram_by_regions( sample_names=["label" for _ in regions_list], **kwargs_plot_depth_histogram_by_regions, ) - assert isinstance( - ax, Axes - ), f"{test_case}: plotting failed for {motif}." + assert isinstance(ax, Axes), ( + f"{test_case}: plotting failed for {motif}." + ) else: print(f"{test_case} skipped for plot_depth_histogram.by_regions.") @@ -1168,9 +1168,9 @@ def test_unit__plot_reads_plot_reads( mod_file_name=results["extract"][0], **kwargs_plot_reads_plot_reads, ) - assert "No threshold has been applied" in str( - excinfo.value - ), f"{test_case}: unexpected exception {excinfo.value}" + assert "No threshold has been applied" in str(excinfo.value), ( + f"{test_case}: unexpected exception {excinfo.value}" + ) # providing a threshold should be enough to run plot_reads.plot_reads without an error kwargs_plot_reads_plot_reads["thresh"] = 0.75 ax = dm.plot_reads.plot_reads( @@ -1208,9 +1208,9 @@ def test_unit__plot_read_browser( region=kwargs["regions"], **kwargs_plot_read_browser, ) - assert isinstance( - fig, plotly.graph_objs.Figure - ), f"{test_case}: plotting failed." + assert isinstance(fig, plotly.graph_objs.Figure), ( + f"{test_case}: plotting failed." + ) else: with pytest.raises(ValueError) as excinfo: fig = dm.plot_read_browser.plot_read_browser( @@ -1222,22 +1222,23 @@ def test_unit__plot_read_browser( isinstance(kwargs["regions"], list) or Path(kwargs["regions"]).suffix == ".bed" ) and kwargs["thresh"] is None: - assert ( - "Invalid region" in str(excinfo.value) - ), f"{test_case}: unexpected exception for no-threshold bad-region case {excinfo.value}" + assert "Invalid region" in str(excinfo.value), ( + f"{test_case}: unexpected exception for no-threshold bad-region case {excinfo.value}" + ) elif ( kwargs["thresh"] is not None and not isinstance(kwargs["regions"], list) and Path(kwargs["regions"]).suffix != ".bed" ): - assert ( - "A threshold has been applied" in str(excinfo.value) - ), f"{test_case}: unexpected exception thresholded valid-region case {excinfo.value}" + assert "A threshold has been applied" in str(excinfo.value), ( + f"{test_case}: unexpected exception thresholded valid-region case {excinfo.value}" + ) else: - assert ( - "A threshold has been applied" in str(excinfo.value) - or "Invalid region" in str(excinfo.value) - ), f"{test_case}: unexpected exception thresholded bad-region case {excinfo.value}" + assert "A threshold has been applied" in str( + excinfo.value + ) or "Invalid region" in str(excinfo.value), ( + f"{test_case}: unexpected exception thresholded bad-region case {excinfo.value}" + ) else: print(f"{test_case} skipped for test_unit__plot_read_browser") diff --git a/dimelo/utils.py b/dimelo/utils.py index 91a2661..38daa6b 100644 --- a/dimelo/utils.py +++ b/dimelo/utils.py @@ -1,5 +1,8 @@ +import contextlib import multiprocessing +import os from collections import defaultdict +from collections.abc import Sequence from pathlib import Path import numpy as np @@ -42,12 +45,51 @@ rng = np.random.default_rng() +def _parse_positive_int(value: str | None) -> int | None: + if value is None: + return None + text = str(value).strip() + if not text: + return None + digits = [] + for char in text: + if char.isdigit(): + digits.append(char) + else: + break + if not digits: + return None + parsed = int("".join(digits)) + return parsed if parsed > 0 else None + + +def _effective_cpu_count() -> int: + candidates: list[int] = [] + + with contextlib.suppress(Exception): + candidates.append(int(multiprocessing.cpu_count())) + + try: + affinity = os.sched_getaffinity(0) + if affinity: + candidates.append(len(affinity)) + except Exception: + pass + + slurm_cpus_per_task = _parse_positive_int(os.environ.get("SLURM_CPUS_PER_TASK")) + if slurm_cpus_per_task is not None: + candidates.append(slurm_cpus_per_task) + + if not candidates: + return 1 + return max(1, min(candidates)) + + def cores_to_run(cores): - cores_avail = multiprocessing.cpu_count() - if cores is None or cores > cores_avail: + cores_avail = _effective_cpu_count() + if cores is None: return cores_avail - else: - return cores + return max(1, min(int(cores), cores_avail)) class ParsedMotif: @@ -133,7 +175,7 @@ def process_chunks_from_regions_dict( def regions_dict_from_input( - regions: str | Path | list[str | Path] | None = None, + regions: str | Path | Sequence[str | Path] | None = None, window_size: int | None = None, ) -> dict: """ @@ -152,7 +194,7 @@ def regions_dict_from_input( "Invalid window_size. To disable windowing, set window_size to None or do not pass a value (the default is None)." ) - if isinstance(regions, list): + if isinstance(regions, Sequence) and not isinstance(regions, (str, Path)): for region in regions: add_region_to_dict(region, window_size, regions_dict) else: @@ -325,6 +367,7 @@ def line_plot( dep_vectors: list[np.ndarray], dep_names: list[str], y_label: str, + legend_title: str = "variable", **kwargs, ) -> Axes: """ @@ -333,14 +376,13 @@ def line_plot( Takes in one independent vector and arbitrarily many dependent vectors. Plots all dependent vectors on the same axes against the same dependent vector. All vectors must be of equal length. - TODO: Right now, this always generates a legend with the title "variable". I could add a parameter to specify this (by passing the var_name argument to pd.DataFrame.melt), but then that percolates upwards to other methods. How to do this cleanly? - Args: indep_vector: parallel with each entry in dep_vectors; independent variable values shared across each overlayed line indep_name: name of independent variable; set as x axis label dep_vectors: outer list parallel with dep_names; each inner vector parallel with indep_vector; dependent variable values for each overlayed line dep_names: parallel with dep_vectors; names of each overlayed line; set as legend entries y_label: y-axis label + legend_title: legend heading shown for the overlaid traces kwargs: other keyword parameters passed through to seaborn.lineplot Returns: @@ -350,11 +392,16 @@ def line_plot( ValueError: raised if any vectors are of unequal length """ # construct dict of {vector_name: vector}, including the x vector using dict union operations - data_dict = {indep_name: indep_vector} | dict(zip(dep_names, dep_vectors)) + data_dict = {indep_name: indep_vector} | dict( + zip(dep_names, dep_vectors, strict=False) + ) + hue_column = legend_title if legend_title else "variable" # construct long-form data table for plotting try: data_table = pd.DataFrame(data_dict).melt( - id_vars=indep_name, value_name=y_label + id_vars=indep_name, + value_name=y_label, + var_name=hue_column, ) except ValueError as e: raise ValueError( @@ -362,7 +409,7 @@ def line_plot( ) from e # plot lines return sns.lineplot( - data=data_table, x=indep_name, y=y_label, hue="variable", **kwargs + data=data_table, x=indep_name, y=y_label, hue=hue_column, **kwargs ) diff --git a/dimelo/workflows.py b/dimelo/workflows.py new file mode 100644 index 0000000..209ffc7 --- /dev/null +++ b/dimelo/workflows.py @@ -0,0 +1,1816 @@ +from __future__ import annotations + +from collections.abc import Iterable +from pathlib import Path +from typing import Any + +import numpy as np +import pandas as pd +from tqdm.auto import tqdm + +from . import ( + chip_atlas, + cluster, + distribution, + dmr, + plotting, + region_analysis, + region_contrasts, + region_discovery, + regulatory_enrichment, +) +from .artifacts import resolve_artifact +from .models import ( + ChipAtlasEnrichmentResult, + DatasetArtifact, + ModkitDMRMultiResult, + ModkitDMRPairResult, + RegionDiscoveryClusterContrastResult, + RegionDiscoveryClusterResult, + SampleSpec, + SharedClusterModel, + SharedClusterResult, + UniBindJobResult, +) + +_SUPPORTED_SIGNAL_NORMALIZATION = {"none", "per_sample_global", "control_regions"} +_SUPPORTED_FEATURE_SCALING = {"none", "robust_zscore"} +_SUPPORTED_CLUSTER_BASIS = {"shape_only", "shape_plus_level", "level_only"} +_PACKAGE_VERSION = "1.0.0" +_PREDICTION_CHUNK_SIZE = 100_000 +_DISCOVERY_SELECTION_DEFAULT_TOP_N = 250 +_LEVEL_FEATURES = { + "global_mean", + "global_var", + "global_median", + "q25", + "q75", + "iqr", + "global_mod_fraction", +} +RegionSpec = str | Path | list[str | Path] | None + + +def _source_fingerprint(path: Path) -> dict[str, Any]: + if path.exists(): + stat = path.stat() + return { + "path": str(path), + "size": stat.st_size, + "mtime_ns": stat.st_mtime_ns, + } + return {"path": str(path), "missing": True} + + +def _serialize_region_spec(region_spec: Any) -> Any: + if region_spec is None: + return None + if isinstance(region_spec, (str, Path)): + return str(region_spec) + if isinstance(region_spec, list): + return [str(item) if isinstance(item, Path) else item for item in region_spec] + return region_spec + + +def _coerce_artifacts(sample: SampleSpec) -> list[DatasetArtifact]: + if not sample.metadata: + return [] + artifacts = sample.metadata.get("artifacts", []) + coerced: list[DatasetArtifact] = [] + for artifact in artifacts: + if isinstance(artifact, DatasetArtifact): + coerced.append(artifact) + elif isinstance(artifact, dict): + coerced.append(DatasetArtifact(**artifact)) + return coerced + + +def _merge_sample_metadata( + row: dict[str, Any], + sample_metadata: dict[str, Any] | None, +) -> dict[str, Any]: + if not sample_metadata: + return row + for key, value in sample_metadata.items(): + if key not in row: + row[key] = value + return row + + +def _requested_extract_artifact( + sample: SampleSpec, + *, + motifs: list[str], + matched_regions: Any, + signal_normalization: str, + feature_scaling: str, + cluster_basis: str, + window_size: int | None = None, +) -> DatasetArtifact: + extract_path = Path(sample.extract_h5) + return DatasetArtifact( + sample_id=sample.sample_id, + artifact_type="extract", + path=extract_path, + format=extract_path.suffix.lstrip(".") or "h5", + params={ + "motifs": motifs, + "matched_regions": _serialize_region_spec(matched_regions), + "window_size": window_size, + "signal_normalization": signal_normalization, + "feature_scaling": feature_scaling, + "cluster_basis": cluster_basis, + }, + provenance={ + "pipeline": "parse_bam", + "source_files": [str(extract_path)], + "source_fingerprints": [_source_fingerprint(extract_path)], + "upstream_lineage": [], + }, + metadata={ + "schema_version": "artifact-v1", + "package_version": _PACKAGE_VERSION, + }, + ) + + +def _requested_pileup_artifact( + sample: SampleSpec, + *, + motifs: list[str], + matched_regions: Any, + signal_normalization: str, + feature_scaling: str, + cluster_basis: str, +) -> DatasetArtifact: + if not sample.metadata or "pileup_path" not in sample.metadata: + raise ValueError( + f"Sample {sample.sample_id!r} is missing metadata['pileup_path'] for region_anchored mode." + ) + pileup_path = Path(sample.metadata["pileup_path"]) + return DatasetArtifact( + sample_id=sample.sample_id, + artifact_type="pileup", + path=pileup_path, + format=pileup_path.suffix.lstrip(".") or "bed.gz", + params={ + "motifs": motifs, + "matched_regions": _serialize_region_spec(matched_regions), + "signal_normalization": signal_normalization, + "feature_scaling": feature_scaling, + "cluster_basis": cluster_basis, + }, + provenance={ + "pipeline": "parse_bam", + "source_files": [str(pileup_path)], + "source_fingerprints": [_source_fingerprint(pileup_path)], + "upstream_lineage": [], + }, + metadata={ + "schema_version": "artifact-v1", + "package_version": _PACKAGE_VERSION, + }, + ) + + +def _require_pileup_path(sample: SampleSpec) -> str | Path: + if sample.metadata and "pileup_path" in sample.metadata: + return sample.metadata["pileup_path"] + raise ValueError( + f"Sample {sample.sample_id!r} is missing metadata['pileup_path'] for region_anchored mode." + ) + + +def _normalize_read_windows( + result: cluster.ReadWindowExtractionResult, + *, + signal_normalization: str, + control_result: cluster.ReadWindowExtractionResult | None = None, +) -> tuple[cluster.ReadWindowExtractionResult, dict[str, float | None]]: + if signal_normalization == "none": + return result, {"global_offset": None} + if signal_normalization not in {"per_sample_global", "control_regions"}: + raise ValueError(f"Unsupported signal_normalization: {signal_normalization}") + + data_matrix = np.asarray(result.data_matrix, dtype=float) + val_matrix = ( + None + if result.val_matrix is None + else np.asarray(result.val_matrix, dtype=float) + ) + offset_source = ( + result if signal_normalization == "per_sample_global" else control_result + ) + if offset_source is None: + raise ValueError( + "control_regions normalization requires control-region read windows." + ) + offset_matrix = np.asarray(offset_source.data_matrix, dtype=float) + offset_val_matrix = ( + None + if offset_source.val_matrix is None + else np.asarray(offset_source.val_matrix, dtype=float) + ) + if offset_val_matrix is not None and offset_val_matrix.sum() > 0: + global_offset = float(offset_matrix.sum() / offset_val_matrix.sum()) + else: + global_offset = float(offset_matrix.mean()) + + normalized = cluster.ReadWindowExtractionResult( + data_matrix=data_matrix - global_offset, + val_matrix=val_matrix, + metadata=list(result.metadata), + datasets=list(result.datasets), + regions_dict=result.regions_dict, + ) + return normalized, {"global_offset": global_offset} + + +def _select_feature_columns( + feature_matrix: np.ndarray, + feature_names: list[str], + *, + cluster_basis: str, +) -> tuple[np.ndarray, list[str]]: + if cluster_basis not in _SUPPORTED_CLUSTER_BASIS: + raise ValueError(f"Unsupported cluster_basis: {cluster_basis}") + if cluster_basis == "shape_plus_level": + return feature_matrix, feature_names + + if cluster_basis == "shape_only": + keep = [name not in _LEVEL_FEATURES for name in feature_names] + else: + keep = [name in _LEVEL_FEATURES for name in feature_names] + + selected_names = [ + name for name, keep_name in zip(feature_names, keep, strict=False) if keep_name + ] + if not selected_names: + raise ValueError(f"No features available for cluster_basis={cluster_basis!r}.") + selected_matrix = feature_matrix[:, keep] + return selected_matrix, selected_names + + +def _scale_features( + training_matrix: np.ndarray, + full_matrix: np.ndarray, + *, + feature_scaling: str, +) -> tuple[np.ndarray, np.ndarray, dict[str, Any]]: + if feature_scaling == "none": + return training_matrix, full_matrix, {"scaler": None} + if feature_scaling != "robust_zscore": + raise ValueError(f"Unsupported feature_scaling: {feature_scaling}") + + from sklearn.preprocessing import RobustScaler + + scaler = RobustScaler() + training_scaled = scaler.fit_transform(training_matrix) + full_scaled = scaler.transform(full_matrix) + return training_scaled, full_scaled, {"scaler": "RobustScaler"} + + +def _cluster_label_strings(labels: np.ndarray) -> list[str]: + values = [] + for label in np.asarray(labels): + label_int = int(label) + values.append("noise" if label_int < 0 else f"C{label_int}") + return values + + +def _cluster_profiles( + feature_matrix: np.ndarray, + feature_names: list[str], + cluster_labels: np.ndarray, +) -> pd.DataFrame: + if feature_matrix.shape[0] == 0: + return pd.DataFrame(columns=["cluster", "count", *feature_names]) + + labels = np.asarray(cluster_labels, dtype=str) + unique_labels = np.unique(labels) + codes = pd.Categorical(labels, categories=unique_labels, ordered=True).codes + counts = np.bincount(codes, minlength=len(unique_labels)).astype( + np.int64, copy=False + ) + + sums = np.zeros((len(unique_labels), feature_matrix.shape[1]), dtype=np.float64) + np.add.at(sums, codes, np.asarray(feature_matrix, dtype=np.float64)) + means = np.divide( + sums, + counts[:, None], + out=np.zeros_like(sums), + where=counts[:, None] > 0, + ) + + profiles = pd.DataFrame(means, columns=feature_names) + profiles.insert(0, "count", counts) + profiles.insert(0, "cluster", unique_labels) + return profiles + + +def _sample_training_rows( + feature_matrix: np.ndarray, + *, + training_sample_per_dataset: int, + random_state: int, +) -> tuple[np.ndarray, np.ndarray]: + if training_sample_per_dataset <= 0: + raise ValueError("training_sample_per_dataset must be positive.") + if feature_matrix.shape[0] <= training_sample_per_dataset: + indices = np.arange(feature_matrix.shape[0], dtype=int) + return feature_matrix, indices + sampled_matrix, _, indices = cluster.sample_rows( + feature_matrix, + n=training_sample_per_dataset, + random_state=random_state, + ) + return sampled_matrix, indices + + +def _predict_in_chunks(model: Any, feature_matrix: np.ndarray) -> np.ndarray: + labels: list[np.ndarray] = [] + for start in range(0, feature_matrix.shape[0], _PREDICTION_CHUNK_SIZE): + stop = start + _PREDICTION_CHUNK_SIZE + labels.append(np.asarray(model.predict(feature_matrix[start:stop]))) + return np.concatenate(labels, axis=0) + + +def _build_region_summary(assignments: pd.DataFrame) -> pd.DataFrame: + summary = ( + assignments.groupby( + ["region_id", "sample_id", "condition", "cluster"], + sort=True, + ) + .size() + .reset_index(name="count") + ) + totals = summary.groupby(["region_id", "sample_id", "condition"])[ + "count" + ].transform("sum") + summary["fraction"] = summary["count"] / totals + return summary + + +def _assignments_have_region_coordinates(assignments: pd.DataFrame) -> bool: + required = ("chromosome", "region_start", "region_end") + if not set(required).issubset(assignments.columns): + return False + return assignments.loc[:, list(required)].notna().all().all() + + +def _region_id_from_coordinates(row: pd.Series) -> str: + chrom = row.get("chromosome", row.get("chrom")) + start = row.get("region_start", row.get("start")) + end = row.get("region_end", row.get("end")) + region_id = f"{chrom}:{int(start)}-{int(end)}" + strand = row.get("region_strand", row.get("strand")) + if pd.notna(strand): + strand_value = str(strand) + if strand_value in {"+", "-", "."}: + return f"{region_id},{strand_value}" + return region_id + + +def _build_read_global_region_summary(assignments: pd.DataFrame) -> pd.DataFrame | None: + if not _assignments_have_region_coordinates(assignments): + return None + + summarizer = getattr(cluster, "summarize_read_cluster_region_associations", None) + if callable(summarizer): + association_frames: list[pd.DataFrame] = [] + include_strand = "region_strand" in assignments.columns + grouping_columns = ["sample_id", "condition"] + grouped = assignments.groupby(grouping_columns, sort=False, dropna=False) + for (sample_id, condition), sample_assignments in grouped: + try: + sample_summary = summarizer( + metadata=sample_assignments.to_dict("records"), + labels=sample_assignments["cluster"].to_numpy(), + include_strand=include_strand, + ) + except Exception: + association_frames = [] + break + if not isinstance(sample_summary, pd.DataFrame) or sample_summary.empty: + continue + normalized_summary = sample_summary.copy() + if "region_id" not in normalized_summary.columns: + coordinate_columns = {"chrom", "start", "end"} + if coordinate_columns.issubset(normalized_summary.columns): + normalized_summary["region_id"] = normalized_summary.apply( + _region_id_from_coordinates, + axis=1, + ) + normalized_summary["sample_id"] = sample_id + normalized_summary["condition"] = condition + association_frames.append(normalized_summary) + + if association_frames: + region_summaries = pd.concat(association_frames, ignore_index=True) + required_columns = { + "region_id", + "sample_id", + "condition", + "cluster", + "count", + "fraction", + } + if required_columns.issubset(region_summaries.columns): + return region_summaries + + summary_source = assignments.copy() + summary_source["region_id"] = summary_source.apply( + _region_id_from_coordinates, axis=1 + ) + return _build_region_summary(summary_source) + + +def _select_discovery_hits( + hits: pd.DataFrame, + *, + selection_mode: str, + top_n: int | None, +) -> pd.DataFrame: + resolved_top_n = _resolve_discovery_selection_top_n( + selection_mode=selection_mode, + top_n=top_n, + ) + if selection_mode == "all": + return hits.copy() + return hits.head(resolved_top_n).copy() + + +def _resolve_discovery_selection_top_n( + *, + selection_mode: str, + top_n: int | None, +) -> int | None: + if selection_mode == "all": + return None + if selection_mode != "top_n": + raise ValueError( + f"Unsupported selection mode: {selection_mode!r}. Supported modes are 'top_n' and 'all'." + ) + + resolved_top_n = _DISCOVERY_SELECTION_DEFAULT_TOP_N if top_n is None else int(top_n) + if resolved_top_n < 0: + raise ValueError("selection.top_n must be non-negative.") + return resolved_top_n + + +def _validate_shared_cluster_distribution_config( + *, + mode: str, + clusterer: str, + signal_normalization: str, + feature_scaling: str, + cluster_basis: str, + sample_count: int, +) -> None: + if mode not in {"read_global", "region_anchored"}: + raise NotImplementedError( + "The first workflow slice implements mode='read_global' and " + "mode='region_anchored' only." + ) + if sample_count < 2: + raise ValueError("shared_cluster_distribution requires at least two datasets.") + if clusterer != "minibatch_kmeans": + raise NotImplementedError( + "The first workflow slice supports clusterer='minibatch_kmeans' only." + ) + if signal_normalization not in _SUPPORTED_SIGNAL_NORMALIZATION: + raise ValueError(f"Unsupported signal_normalization: {signal_normalization}") + if feature_scaling not in _SUPPORTED_FEATURE_SCALING: + raise ValueError(f"Unsupported feature_scaling: {feature_scaling}") + if cluster_basis not in _SUPPORTED_CLUSTER_BASIS: + raise ValueError(f"Unsupported cluster_basis: {cluster_basis}") + + +def _selected_regions_to_region_spec(selected_regions: pd.DataFrame) -> list[str]: + if selected_regions.empty: + return [] + + chrom_column = "chrom" if "chrom" in selected_regions.columns else "chromosome" + region_spec: list[str] = [] + for row in selected_regions.itertuples(index=False): + chrom = getattr(row, chrom_column) + start = int(row.start) + end = int(row.end) + strand = getattr(row, "strand", ".") + strand_value = strand if strand in {"+", "-", "."} else "." + region_spec.append(f"{chrom}:{start}-{end},{strand_value}") + return region_spec + + +def _normalize_region_id_value( + row: pd.Series, + *, + default_region_ids: dict[str, str], +) -> Any: + region_id = row.get("region_id") + if pd.isna(region_id): + return region_id + + region_id_str = str(region_id) + if region_id_str in default_region_ids: + return default_region_ids[region_id_str] + + if "," not in region_id_str and region_id_str.count(":") >= 2: + region_core, strand = region_id_str.rsplit(":", 1) + strand_value = strand if strand in {"+", "-", "."} else "." + if region_core in default_region_ids: + return default_region_ids[region_core] + return f"{region_core},{strand_value}" + + chrom = row.get("chromosome", row.get("chrom")) + start = row.get("start") + end = row.get("end") + if pd.notna(chrom) and pd.notna(start) and pd.notna(end): + strand = row.get("strand", ".") + strand_value = strand if strand in {"+", "-", "."} else "." + return f"{chrom}:{int(start)}-{int(end)},{strand_value}" + + if "," in region_id_str: + region_core, strand = region_id_str.rsplit(",", 1) + strand_value = strand if strand in {"+", "-", "."} else "." + return f"{region_core},{strand_value}" + + strand = row.get("strand") + if pd.notna(strand): + strand_value = str(strand) + if strand_value in {"+", "-", "."}: + return f"{region_id_str},{strand_value}" + + return region_id_str + + +def _normalize_region_id_frame( + region_frame: pd.DataFrame | None, + *, + default_region_ids: dict[str, str], +) -> pd.DataFrame | None: + if region_frame is None or "region_id" not in region_frame.columns: + return region_frame + + normalized_frame = region_frame.copy() + normalized_frame["region_id"] = normalized_frame.apply( + _normalize_region_id_value, + axis=1, + default_region_ids=default_region_ids, + ) + return normalized_frame + + +def _normalize_cluster_region_ids( + clustering_result: SharedClusterResult, + *, + default_region_spec: list[str], +) -> SharedClusterResult: + default_region_ids = { + region_id.rsplit(",", 1)[0]: region_id for region_id in default_region_spec + } + + normalized_assignments = _normalize_region_id_frame( + clustering_result.assignments, + default_region_ids=default_region_ids, + ) + normalized_region_summaries = _normalize_region_id_frame( + clustering_result.region_summaries, + default_region_ids=default_region_ids, + ) + + return SharedClusterResult( + model=clustering_result.model, + assignments=normalized_assignments, + cluster_distribution=clustering_result.cluster_distribution, + condition_distribution=clustering_result.condition_distribution, + distribution_change=clustering_result.distribution_change, + cluster_profiles=clustering_result.cluster_profiles, + region_summaries=normalized_region_summaries, + plot_data=clustering_result.plot_data, + figures=clustering_result.figures, + metadata=clustering_result.metadata, + ) + + +def _require_region_summary_table_for_chip_atlas( + cluster_result: SharedClusterResult, +) -> pd.DataFrame: + if cluster_result.region_summaries is not None: + region_summaries = cluster_result.region_summaries.copy() + else: + assignments = cluster_result.assignments.copy() + if "region_id" not in assignments.columns: + if _assignments_have_region_coordinates(assignments): + assignments["region_id"] = assignments.apply( + _region_id_from_coordinates, axis=1 + ) + else: + raise ValueError( + "SharedClusterResult does not include region_summaries, and assignments " + "do not contain region coordinates to derive region ids." + ) + region_summaries = _build_region_summary(assignments) + + required_columns = {"cluster", "region_id"} + if not required_columns.issubset(region_summaries.columns): + missing = required_columns - set(region_summaries.columns) + raise ValueError( + "Cluster region summary table is missing required columns for ChIP-Atlas " + f"workflow: {sorted(missing)}" + ) + return region_summaries + + +def _rank_cluster_regions_for_chip_atlas( + *, + region_summaries: pd.DataFrame, + cluster_label: str, + min_fraction: float | None, + top_n_regions: int | None, +) -> list[str]: + cluster_rows = region_summaries.loc[ + region_summaries["cluster"] == cluster_label + ].copy() + if cluster_rows.empty: + return [] + + if "fraction" in cluster_rows.columns: + ranked = ( + cluster_rows.groupby("region_id", dropna=False)["fraction"] + .mean() + .sort_values(ascending=False) + .reset_index(name="score") + ) + if min_fraction is not None: + ranked = ranked.loc[ranked["score"] >= float(min_fraction)] + elif "count" in cluster_rows.columns: + ranked = ( + cluster_rows.groupby("region_id", dropna=False)["count"] + .sum() + .sort_values(ascending=False) + .reset_index(name="score") + ) + else: + ranked = ( + cluster_rows.groupby("region_id", dropna=False) + .size() + .sort_values(ascending=False) + .reset_index(name="score") + ) + + if top_n_regions is not None: + ranked = ranked.head(int(top_n_regions)) + return [ + str(region_id) + for region_id in ranked["region_id"].tolist() + if pd.notna(region_id) + ] + + +def resolve_regulatory_enrichment_spec( + *, + providers: Iterable[str] = ("screen", "unibind"), + species: str | None = None, + reference_genome: str | None = None, + target_genome: str | None = None, + crossmap_chain_file: str | Path | None = None, + crossmap_chain_url: str | None = None, + crossmap_chain_cache_dir: str | Path | None = None, + crossmap_executable: str | None = "CrossMap.py", + strict_provider_support: bool = False, +) -> regulatory_enrichment.RegulatoryEnrichmentSpec: + return regulatory_enrichment.RegulatoryEnrichmentSpec( + providers=tuple(providers), + species=species, + reference_genome=reference_genome, + target_genome=target_genome, + crossmap_chain_file=crossmap_chain_file, + crossmap_chain_url=crossmap_chain_url, + crossmap_chain_cache_dir=crossmap_chain_cache_dir, + crossmap_executable=crossmap_executable, + strict_provider_support=strict_provider_support, + ) + + +def resolve_unibind_track_paths( + *, + track_paths: Iterable[str | Path] | None = None, + trackhub_url: str | None = None, + collection: str = "robust", + assembly: str | None = None, + search_terms: Iterable[str] | None = None, + cache_dir: str | Path = "cache/unibind_tracks", + max_tracks: int | None = None, + allow_cached: bool = True, + timeout_seconds: float = 60.0, + convert_bigbed_to_bed: bool = False, + regulatory_spec: regulatory_enrichment.RegulatoryEnrichmentSpec | None = None, +) -> list[Path]: + resolved_assembly = assembly + if resolved_assembly is None and regulatory_spec is not None: + resolved_assembly = regulatory_spec.target_genome + return regulatory_enrichment.resolve_unibind_track_paths( + track_paths=track_paths, + trackhub_url=trackhub_url, + collection=collection, + assembly=resolved_assembly, + search_terms=search_terms, + cache_dir=cache_dir, + max_tracks=max_tracks, + allow_cached=allow_cached, + timeout_seconds=timeout_seconds, + convert_bigbed_to_bed=convert_bigbed_to_bed, + ) + + +def unibind_tfbs_extraction_workflow( + *, + regions: Any, + species: str | None = None, + collection: str = "robust", + tf_list: str | Path | Iterable[str] | None = None, + experiment_ids: str | Path | Iterable[str] | None = None, + name: str | None = None, + email: str | None = None, + endpoint_url: str = regulatory_enrichment.DEFAULT_UNIBIND_TFBS_EXTRACTION_URL, + submit_timeout_seconds: float = 120.0, + wait: bool = True, + poll_interval_seconds: float = 5.0, + timeout_seconds: float = 1200.0, + download_outputs: bool = False, + output_dir: str | Path = "cache/unibind_jobs", + allow_cached_downloads: bool = True, + download_timeout_seconds: float = 120.0, + regulatory_spec: regulatory_enrichment.RegulatoryEnrichmentSpec | None = None, +) -> UniBindJobResult: + resolved_species = species + if resolved_species is None and regulatory_spec is not None: + resolved_species = regulatory_spec.species + return regulatory_enrichment.run_unibind_tfbs_extraction( + regions=regions, + species=resolved_species or "homo_sapiens", + collection=collection, + tf_list=tf_list, + experiment_ids=experiment_ids, + name=name, + email=email, + endpoint_url=endpoint_url, + submit_timeout_seconds=submit_timeout_seconds, + wait=wait, + poll_interval_seconds=poll_interval_seconds, + timeout_seconds=timeout_seconds, + download_outputs=download_outputs, + output_dir=output_dir, + allow_cached_downloads=allow_cached_downloads, + download_timeout_seconds=download_timeout_seconds, + ) + + +def unibind_enrichment_workflow( + *, + regions: Any, + analysis_type: str = "oneSetBg", + background_regions: Any | None = None, + comparison_regions: Any | None = None, + species: str | None = None, + collection: str = "robust", + name: str | None = None, + email: str | None = None, + endpoint_url: str = regulatory_enrichment.DEFAULT_UNIBIND_ENRICHMENT_URL, + submit_timeout_seconds: float = 120.0, + wait: bool = True, + poll_interval_seconds: float = 5.0, + timeout_seconds: float = 1200.0, + download_outputs: bool = False, + output_dir: str | Path = "cache/unibind_jobs", + allow_cached_downloads: bool = True, + download_timeout_seconds: float = 120.0, + regulatory_spec: regulatory_enrichment.RegulatoryEnrichmentSpec | None = None, +) -> UniBindJobResult: + resolved_species = species + if resolved_species is None and regulatory_spec is not None: + resolved_species = regulatory_spec.species + return regulatory_enrichment.run_unibind_enrichment( + regions=regions, + analysis_type=analysis_type, + background_regions=background_regions, + comparison_regions=comparison_regions, + species=resolved_species or "homo_sapiens", + collection=collection, + name=name, + email=email, + endpoint_url=endpoint_url, + submit_timeout_seconds=submit_timeout_seconds, + wait=wait, + poll_interval_seconds=poll_interval_seconds, + timeout_seconds=timeout_seconds, + download_outputs=download_outputs, + output_dir=output_dir, + allow_cached_downloads=allow_cached_downloads, + download_timeout_seconds=download_timeout_seconds, + ) + + +def chip_atlas_search_peak_datasets_workflow( + *, + antigen: str, + genome: str = "hg38", + cell_type: str | None = None, + antigen_class: str | None = None, + cell_type_class: str | None = None, + threshold: str = "05", + match_mode: str = "contains", + max_results: int | None = None, + experiment_list_url: str = chip_atlas.DEFAULT_EXPERIMENT_LIST_URL, + cache_dir: str | Path = chip_atlas.DEFAULT_METADATA_CACHE_DIR, + allow_cached_metadata: bool = True, + timeout_seconds: float = 120.0, +) -> pd.DataFrame: + return chip_atlas.search_peak_datasets( + antigen=antigen, + genome=genome, + cell_type=cell_type, + antigen_class=antigen_class, + cell_type_class=cell_type_class, + threshold=threshold, + match_mode=match_mode, + max_results=max_results, + experiment_list_url=experiment_list_url, + cache_dir=cache_dir, + allow_cached_metadata=allow_cached_metadata, + timeout_seconds=timeout_seconds, + ) + + +def chip_atlas_download_peak_datasets_workflow( + *, + datasets: pd.DataFrame, + dataset_ids: Iterable[str] | None = None, + output_dir: str | Path = "cache/chip_atlas/peak_sets", + include_complete_sorted: bool = True, + include_top_n: int | None = 3000, + include_bottom_n: int | None = 3000, + stratify: str | int | None = None, + allow_cached: bool = True, + timeout_seconds: float = 180.0, + crossmap_target_genome: str | None = None, + crossmap_chain_file: str | Path | None = None, + crossmap_chain_url: str | None = None, + crossmap_chain_cache_dir: str | Path | None = None, + crossmap_executable: str | None = "CrossMap.py", +) -> pd.DataFrame: + return chip_atlas.download_peak_datasets( + datasets=datasets, + dataset_ids=dataset_ids, + output_dir=output_dir, + include_complete_sorted=include_complete_sorted, + include_top_n=include_top_n, + include_bottom_n=include_bottom_n, + stratify=stratify, + allow_cached=allow_cached, + timeout_seconds=timeout_seconds, + crossmap_target_genome=crossmap_target_genome, + crossmap_chain_file=crossmap_chain_file, + crossmap_chain_url=crossmap_chain_url, + crossmap_chain_cache_dir=crossmap_chain_cache_dir, + crossmap_executable=crossmap_executable, + ) + + +def chip_atlas_enrichment_workflow( + *, + regions: pd.DataFrame | str | Path | list[str], + genome: str = "hg38", + regions_genome: str | None = None, + antigen_class: str | None = "TFs and others", + antigen: str | None = None, + cell_type_class: str | None = "No description", + cell_type: str | None = None, + distance: int | None = None, + threshold: str | None = "100", + crossmap_chain_file: str | Path | None = None, + crossmap_chain_url: str | None = None, + crossmap_chain_cache_dir: str | Path | None = None, + crossmap_executable: str | None = "CrossMap.py", + params: dict[str, Any] | None = None, + wait: bool = True, + fetch_results: bool = True, + raise_on_failure: bool = True, + submit_url: str = chip_atlas.DEFAULT_SUBMIT_URL, + status_url: str = chip_atlas.DEFAULT_STATUS_URL, + result_url: str = chip_atlas.DEFAULT_RESULT_URL, + poll_interval_seconds: float = 5.0, + timeout_seconds: float = 600.0, +) -> ChipAtlasEnrichmentResult: + return chip_atlas.run_enrichment( + regions=regions, + genome=genome, + regions_genome=regions_genome, + antigen_class=antigen_class, + antigen=antigen, + cell_type_class=cell_type_class, + cell_type=cell_type, + distance=distance, + threshold=threshold, + crossmap_chain_file=crossmap_chain_file, + crossmap_chain_url=crossmap_chain_url, + crossmap_chain_cache_dir=crossmap_chain_cache_dir, + crossmap_executable=crossmap_executable, + params=params, + wait=wait, + fetch_results=fetch_results, + raise_on_failure=raise_on_failure, + submit_url=submit_url, + status_url=status_url, + result_url=result_url, + poll_interval_seconds=poll_interval_seconds, + timeout_seconds=timeout_seconds, + ) + + +def chip_atlas_cluster_enrichment_workflow( + *, + cluster_result: SharedClusterResult, + genome: str = "hg38", + regions_genome: str | None = None, + clusters: Iterable[str] | None = None, + min_fraction: float | None = None, + top_n_regions: int | None = None, + mode: str = "per_cluster", + antigen_class: str | None = "TFs and others", + antigen: str | None = None, + cell_type_class: str | None = "No description", + cell_type: str | None = None, + distance: int | None = None, + threshold: str | None = "100", + crossmap_chain_file: str | Path | None = None, + crossmap_chain_url: str | None = None, + crossmap_chain_cache_dir: str | Path | None = None, + crossmap_executable: str | None = "CrossMap.py", + params: dict[str, Any] | None = None, + wait: bool = True, + fetch_results: bool = True, + raise_on_failure: bool = True, + submit_url: str = chip_atlas.DEFAULT_SUBMIT_URL, + status_url: str = chip_atlas.DEFAULT_STATUS_URL, + result_url: str = chip_atlas.DEFAULT_RESULT_URL, + poll_interval_seconds: float = 5.0, + timeout_seconds: float = 600.0, +) -> dict[str, ChipAtlasEnrichmentResult]: + if mode not in {"per_cluster", "combined"}: + raise ValueError("mode must be 'per_cluster' or 'combined'.") + + region_summaries = _require_region_summary_table_for_chip_atlas(cluster_result) + available_clusters = [ + str(value) for value in pd.unique(region_summaries["cluster"]) + ] + selected_clusters = ( + [str(cluster_label) for cluster_label in clusters] + if clusters is not None + else available_clusters + ) + unknown = sorted(set(selected_clusters) - set(available_clusters)) + if unknown: + raise ValueError( + "Requested clusters were not present in cluster_result.region_summaries: " + f"{unknown}" + ) + + per_cluster_region_ids: dict[str, list[str]] = {} + for cluster_label in selected_clusters: + region_ids = _rank_cluster_regions_for_chip_atlas( + region_summaries=region_summaries, + cluster_label=cluster_label, + min_fraction=min_fraction, + top_n_regions=top_n_regions, + ) + if region_ids: + per_cluster_region_ids[cluster_label] = region_ids + + if not per_cluster_region_ids: + raise ValueError( + "No regions met the requested filters for ChIP-Atlas enrichment." + ) + + queries: dict[str, list[str]] + if mode == "combined": + union_region_ids = sorted( + {region_id for ids in per_cluster_region_ids.values() for region_id in ids} + ) + queries = {"combined": union_region_ids} + else: + queries = per_cluster_region_ids + + results: dict[str, ChipAtlasEnrichmentResult] = {} + for query_key, region_ids in queries.items(): + bed_frame = chip_atlas.region_ids_to_bed_dataframe(region_ids) + enrichment = chip_atlas_enrichment_workflow( + regions=bed_frame, + genome=genome, + regions_genome=regions_genome, + antigen_class=antigen_class, + antigen=antigen, + cell_type_class=cell_type_class, + cell_type=cell_type, + distance=distance, + threshold=threshold, + crossmap_chain_file=crossmap_chain_file, + crossmap_chain_url=crossmap_chain_url, + crossmap_chain_cache_dir=crossmap_chain_cache_dir, + crossmap_executable=crossmap_executable, + params=params, + wait=wait, + fetch_results=fetch_results, + raise_on_failure=raise_on_failure, + submit_url=submit_url, + status_url=status_url, + result_url=result_url, + poll_interval_seconds=poll_interval_seconds, + timeout_seconds=timeout_seconds, + ) + enrichment.metadata["workflow"] = { + "query_key": query_key, + "cluster_mode": mode, + "source_clusters": selected_clusters, + "region_count": len(region_ids), + "min_fraction": min_fraction, + "top_n_regions": top_n_regions, + } + results[query_key] = enrichment + return results + + +def modkit_dmr_pair_workflow( + *, + control_bed_methyl: str | Path, + experiment_bed_methyl: str | Path, + ref_genome: str | Path, + out_path: str | Path, + regions_bed: str | Path | None = None, + segment_path: str | Path | None = None, + bases: Iterable[str] = ("A",), + assign_codes: Iterable[str] | None = None, + min_valid_coverage: int = 0, + dmr_prior: float | None = None, + diff_stay: float | None = None, + significance_factor: float | None = None, + decay_distance: int | None = None, + max_gap_size: int | None = None, + log_transition_decay: bool = False, + fine_grained: bool = False, + prior_alpha: float | None = None, + prior_beta: float | None = None, + delta: float | None = None, + n_sample_records: int | None = None, + max_coverages: tuple[int, int] | None = None, + cap_coverages: bool = False, + missing: str | None = None, + threads: int | None = None, + io_threads: int | None = None, + batch_size: int | None = None, + interval_size: int | None = None, + header: bool = True, + force: bool = True, + suppress_progress: bool = True, + log_filepath: str | Path | None = None, + modkit_executable: str | Path | None = None, + pvalue_max: float = 0.01, + abs_effect_size_min: float = 0.1, + min_total_coverage: int | None = None, +) -> ModkitDMRPairResult: + return dmr.run_dmr_pair( + control_bed_methyl=control_bed_methyl, + experiment_bed_methyl=experiment_bed_methyl, + ref_genome=ref_genome, + out_path=out_path, + regions_bed=regions_bed, + segment_path=segment_path, + bases=list(bases), + assign_codes=None if assign_codes is None else list(assign_codes), + min_valid_coverage=min_valid_coverage, + dmr_prior=dmr_prior, + diff_stay=diff_stay, + significance_factor=significance_factor, + decay_distance=decay_distance, + max_gap_size=max_gap_size, + log_transition_decay=log_transition_decay, + fine_grained=fine_grained, + prior_alpha=prior_alpha, + prior_beta=prior_beta, + delta=delta, + n_sample_records=n_sample_records, + max_coverages=max_coverages, + cap_coverages=cap_coverages, + missing=missing, + threads=threads, + io_threads=io_threads, + batch_size=batch_size, + interval_size=interval_size, + header=header, + force=force, + suppress_progress=suppress_progress, + log_filepath=log_filepath, + modkit_executable=modkit_executable, + pvalue_max=pvalue_max, + abs_effect_size_min=abs_effect_size_min, + min_total_coverage=min_total_coverage, + ) + + +def modkit_dmr_multi_workflow( + *, + samples: dict[str, str | Path] | Iterable[tuple[str, str | Path]], + regions_bed: str | Path, + ref_genome: str | Path, + out_dir: str | Path, + bases: Iterable[str] = ("A",), + assign_codes: Iterable[str] | None = None, + min_valid_coverage: int = 0, + missing: str | None = None, + threads: int | None = None, + io_threads: int | None = None, + prefix: str | None = None, + header: bool = True, + force: bool = True, + suppress_progress: bool = True, + log_filepath: str | Path | None = None, + modkit_executable: str | Path | None = None, +) -> ModkitDMRMultiResult: + return dmr.run_dmr_multi( + samples=samples, + regions_bed=regions_bed, + ref_genome=ref_genome, + out_dir=out_dir, + bases=list(bases), + assign_codes=None if assign_codes is None else list(assign_codes), + min_valid_coverage=min_valid_coverage, + missing=missing, + threads=threads, + io_threads=io_threads, + prefix=prefix, + header=header, + force=force, + suppress_progress=suppress_progress, + log_filepath=log_filepath, + modkit_executable=modkit_executable, + ) + + +def modkit_dmr_multi_from_samples_workflow( + *, + samples: Iterable[SampleSpec], + regions_bed: str | Path, + ref_genome: str | Path, + out_dir: str | Path, + bases: Iterable[str] = ("A",), + assign_codes: Iterable[str] | None = None, + min_valid_coverage: int = 0, + missing: str | None = None, + threads: int | None = None, + io_threads: int | None = None, + prefix: str | None = None, + header: bool = True, + force: bool = True, + suppress_progress: bool = True, + log_filepath: str | Path | None = None, + modkit_executable: str | Path | None = None, +) -> ModkitDMRMultiResult: + sample_list = list(samples) + if len(sample_list) < 2: + raise ValueError( + "modkit_dmr_multi_from_samples_workflow requires at least two samples." + ) + + dmr_samples: dict[str, str | Path] = {} + for sample in sample_list: + metadata = sample.metadata or {} + pileup_path = metadata.get("pileup_path") + if pileup_path is None: + raise ValueError( + f"Sample {sample.sample_id!r} is missing metadata['pileup_path'] " + "required for modkit DMR multi workflow." + ) + dmr_samples[sample.sample_id] = pileup_path + + return modkit_dmr_multi_workflow( + samples=dmr_samples, + regions_bed=regions_bed, + ref_genome=ref_genome, + out_dir=out_dir, + bases=list(bases), + assign_codes=None if assign_codes is None else list(assign_codes), + min_valid_coverage=min_valid_coverage, + missing=missing, + threads=threads, + io_threads=io_threads, + prefix=prefix, + header=header, + force=force, + suppress_progress=suppress_progress, + log_filepath=log_filepath, + modkit_executable=modkit_executable, + ) + + +def discovery_cluster_workflow( + *, + samples: Iterable[SampleSpec], + motifs: Iterable[str], + genome_sizes: dict[str, int], + discovery: dict[str, Any], + clustering: dict[str, Any], + selection: dict[str, Any] | None = None, + quiet: bool = True, +) -> RegionDiscoveryClusterResult: + sample_list = list(samples) + motif_list = list(motifs) + selection_config = dict(selection or {}) + selection_mode = str(selection_config.get("mode", "top_n")) + selection_top_n = selection_config.get("top_n") + resolved_top_n = _resolve_discovery_selection_top_n( + selection_mode=selection_mode, + top_n=selection_top_n, + ) + _validate_shared_cluster_distribution_config( + mode=str(clustering.get("mode", "read_global")), + clusterer=str(clustering.get("clusterer", "minibatch_kmeans")), + signal_normalization=str(clustering.get("signal_normalization", "none")), + feature_scaling=str(clustering.get("feature_scaling", "robust_zscore")), + cluster_basis=str(clustering.get("cluster_basis", "shape_plus_level")), + sample_count=len(sample_list), + ) + + discovery_result = region_discovery.scan_genome( + samples=sample_list, + motifs=motif_list, + genome_sizes=genome_sizes, + **discovery, + ) + selected_hits = _select_discovery_hits( + discovery_result.hits, + selection_mode=selection_mode, + top_n=selection_top_n, + ) + if selected_hits.empty: + raise ValueError("No discovery hits remained after selection.") + + selected_regions = region_discovery.hits_to_bed(selected_hits) + matched_regions = _selected_regions_to_region_spec(selected_regions) + clustering_result = shared_cluster_distribution( + samples=sample_list, + motifs=motif_list, + matched_regions=matched_regions, + quiet=quiet, + **clustering, + ) + return RegionDiscoveryClusterResult( + discovery=discovery_result, + clustering=clustering_result, + selected_regions=selected_regions, + metadata={ + "selection": { + "mode": selection_mode, + "top_n": resolved_top_n, + "discovered_hit_count": int(len(discovery_result.hits)), + "selected_hit_count": int(len(selected_hits)), + } + }, + ) + + +def discovery_cluster_contrast_workflow( + *, + samples: Iterable[SampleSpec], + motifs: Iterable[str], + genome_sizes: dict[str, int], + discovery: dict[str, Any], + clustering: dict[str, Any], + contrasts: dict[str, Any], + selection: dict[str, Any] | None = None, +) -> RegionDiscoveryClusterContrastResult: + sample_list = list(samples) + motif_list = list(motifs) + contrast_config = dict(contrasts) + contrast_spec = contrast_config.get("contrast") + if contrast_spec is None: + raise ValueError( + "discovery_cluster_contrast_workflow requires contrasts['contrast']." + ) + + region_contrasts.validate_region_contrast_request( + analysis_unit=str(contrast_config.get("analysis_unit", "ensemble_region")), + representation=str(contrast_config.get("representation", "modified_fraction")), + signal_source=str(contrast_config.get("signal_source", "pileup_counts")), + test=str(contrast_config.get("test", "effect_size_only")), + ) + + discovery_cluster_result = discovery_cluster_workflow( + samples=sample_list, + motifs=motif_list, + genome_sizes=genome_sizes, + discovery=discovery, + clustering=clustering, + selection=selection, + ) + selected_region_spec = _selected_regions_to_region_spec( + discovery_cluster_result.selected_regions + ) + contrast_regions = contrast_config.pop("regions", None) + contrast_scope = "selected" + if contrast_regions is None: + contrast_regions = selected_region_spec + else: + contrast_scope = "custom" + + contrast_result = region_contrasts.score_regions( + samples=sample_list, + regions=contrast_regions, + motifs=motif_list, + **contrast_config, + ) + normalized_clustering = _normalize_cluster_region_ids( + discovery_cluster_result.clustering, + default_region_spec=selected_region_spec, + ) + + return RegionDiscoveryClusterContrastResult( + discovery=discovery_cluster_result.discovery, + clustering=normalized_clustering, + contrasts=contrast_result, + selected_regions=discovery_cluster_result.selected_regions, + metadata={ + **discovery_cluster_result.metadata, + "contrast_scope": contrast_scope, + "full_scan_windows": discovery_cluster_result.discovery.windows.copy(), + }, + ) + + +def _build_shared_cluster_result( + *, + mode: str, + motifs: list[str], + feature_blocks: list[np.ndarray], + training_blocks: list[np.ndarray], + metadata_rows: list[dict[str, Any]], + feature_names: list[str], + signal_normalization: str, + feature_scaling: str, + cluster_basis: str, + clusterer: str, + n_clusters: int, + training_sample_per_dataset: int, + artifact_policy: str, + random_state: int, + make_plots: bool, + matched_regions: Any, + sample_training_rows: dict[str, int], + sample_dataset_sizes: dict[str, int], + sample_normalization: dict[str, dict[str, float | None]], + cache_hits: dict[str, str], + cache_misses: list[str], + sample_rebuild_decisions: dict[str, str], + quiet: bool, + region_summaries: pd.DataFrame | None = None, +) -> SharedClusterResult: + full_matrix = np.vstack(feature_blocks) + training_matrix = np.vstack(training_blocks) + if training_matrix.shape[0] < n_clusters: + raise ValueError( + "Pooled training subset has fewer rows than n_clusters. " + "Reduce n_clusters or increase training_sample_per_dataset." + ) + + training_scaled, full_scaled, scaler_meta = _scale_features( + training_matrix, + full_matrix, + feature_scaling=feature_scaling, + ) + clustering_result = cluster.cluster_read_windows( + training_scaled, + method=clusterer, + n_clusters=n_clusters, + random_state=random_state, + ) + label_mapping = cluster.cluster_label_mapping( + clustering_result.labels_raw, + clustering_result.labels_size_ordered, + ) + if not hasattr(clustering_result.model, "predict"): + raise TypeError( + "Fitted clustering model does not support prediction for full assignment." + ) + predicted_raw = _predict_in_chunks(clustering_result.model, full_scaled) + predicted_ordered = cluster.apply_cluster_label_mapping( + predicted_raw, label_mapping + ) + + assignments = pd.DataFrame(metadata_rows) + assignments["cluster"] = _cluster_label_strings(predicted_ordered) + + cluster_distribution = distribution.build_cluster_distribution(assignments) + condition_distribution = distribution.build_condition_distribution( + cluster_distribution + ) + cluster_profiles = _cluster_profiles( + full_matrix, + feature_names, + assignments["cluster"].to_numpy(), + ) + plot_data = { + "cluster_distribution_bar": plotting.prepare_cluster_distribution_bar_data( + cluster_distribution + ), + "cluster_distribution_heatmap": plotting.prepare_cluster_distribution_heatmap_data( + condition_distribution + ), + } + if region_summaries is None and mode == "region_anchored": + region_summaries = _build_region_summary(assignments) + if region_summaries is None and mode == "read_global": + region_summaries = _build_read_global_region_summary(assignments) + + model = SharedClusterModel( + mode=mode, + motifs=motifs, + feature_names=list(feature_names), + preprocessing={ + "signal_normalization": signal_normalization, + "feature_scaling": feature_scaling, + "cluster_basis": cluster_basis, + **scaler_meta, + }, + estimator=clustering_result.model, + cluster_labels=sorted(assignments["cluster"].unique()), + fit_metadata={ + "clusterer": clusterer, + "n_clusters": n_clusters, + "artifact_policy": artifact_policy, + "training_sample_per_dataset": training_sample_per_dataset, + "training_rows": int(training_scaled.shape[0]), + "sample_training_rows": sample_training_rows, + "metrics": clustering_result.metrics, + }, + ) + return SharedClusterResult( + model=model, + assignments=assignments, + cluster_distribution=cluster_distribution, + condition_distribution=condition_distribution, + distribution_change=None, + cluster_profiles=cluster_profiles, + region_summaries=region_summaries, + plot_data=plot_data, + figures={}, + metadata={ + "mode": mode, + "artifact_policy": artifact_policy, + "cache_hits": cache_hits, + "cache_misses": cache_misses, + "sample_rebuild_decisions": sample_rebuild_decisions, + "signal_normalization": signal_normalization, + "feature_scaling": feature_scaling, + "cluster_basis": cluster_basis, + "make_plots": make_plots, + "matched_regions": matched_regions, + "sample_dataset_sizes": sample_dataset_sizes, + "rows_after_filtering": sample_dataset_sizes, + "sample_normalization": sample_normalization, + "quiet": quiet, + }, + ) + + +def shared_cluster_distribution( + *, + samples: Iterable[SampleSpec], + mode: str, + motifs: Iterable[str], + matched_regions: RegionSpec = None, + signal_normalization: str = "none", + feature_scaling: str = "robust_zscore", + cluster_basis: str = "shape_plus_level", + clusterer: str = "minibatch_kmeans", + n_clusters: int = 8, + training_sample_per_dataset: int = 100_000, + artifact_policy: str = "prefer_cached", + random_state: int = 42, + make_plots: bool = True, + cores: int | None = None, + quiet: bool = True, +) -> SharedClusterResult: + """ + Fit one shared clustering model on pooled read windows and assign all reads back + onto those fixed cluster boundaries. + """ + + sample_list = list(samples) + motif_list = list(motifs) + if not sample_list: + raise ValueError("samples must contain at least one sample.") + if not motif_list: + raise ValueError("motifs must contain at least one motif.") + _validate_shared_cluster_distribution_config( + mode=mode, + clusterer=clusterer, + signal_normalization=signal_normalization, + feature_scaling=feature_scaling, + cluster_basis=cluster_basis, + sample_count=len(sample_list), + ) + + if mode == "region_anchored": + if matched_regions is None: + raise ValueError( + "mode='region_anchored' requires matched_regions; per-sample regions_bed is not used " + "as an implicit fallback." + ) + pileup_paths: dict[str, str | Path] = {} + cache_hits: dict[str, str] = {} + cache_misses: list[str] = [] + sample_rebuild_decisions: dict[str, str] = {} + + for sample in tqdm(sample_list, desc="Processing samples", disable=quiet): + requested_artifact = _requested_pileup_artifact( + sample, + motifs=motif_list, + matched_regions=matched_regions, + signal_normalization=signal_normalization, + feature_scaling=feature_scaling, + cluster_basis=cluster_basis, + ) + resolved_artifact = resolve_artifact( + requested_artifact, + _coerce_artifacts(sample), + artifact_policy=artifact_policy, + ) + if resolved_artifact is not None: + pileup_paths[sample.sample_id] = resolved_artifact.path + cache_hits[sample.sample_id] = str(resolved_artifact.path) + sample_rebuild_decisions[sample.sample_id] = "cache_hit" + else: + pileup_paths[sample.sample_id] = _require_pileup_path(sample) + cache_misses.append(sample.sample_id) + sample_rebuild_decisions[sample.sample_id] = "rebuilt_from_raw" + + feature_matrix, metadata_rows = region_analysis.build_region_feature_table( + samples=sample_list, + motifs=motif_list, + matched_regions=matched_regions, + pileup_paths=pileup_paths, + cores=cores, + quiet=quiet, + ) + sample_by_id = {sample.sample_id: sample for sample in sample_list} + for row in metadata_rows: + sample = sample_by_id[row["sample_id"]] + _merge_sample_metadata(row, sample.metadata) + sample_row_positions: dict[str, list[int]] = { + sample.sample_id: [] for sample in sample_list + } + for index, row in enumerate(metadata_rows): + sample_id = row["sample_id"] + if sample_id in sample_row_positions: + sample_row_positions[sample_id].append(index) + sample_row_indices = { + sample_id: np.asarray(indices, dtype=int) + for sample_id, indices in sample_row_positions.items() + } + sample_dataset_sizes = { + sample.sample_id: int(sample_row_indices[sample.sample_id].size) + for sample in sample_list + } + sample_normalization = { + sample.sample_id: {"global_offset": None} for sample in sample_list + } + region_matrix = np.asarray(feature_matrix, dtype=float) + if signal_normalization in {"per_sample_global", "control_regions"}: + for sample in sample_list: + sample_id = sample.sample_id + sample_indices = sample_row_indices[sample_id] + if sample_indices.size == 0: + continue + if signal_normalization == "control_regions": + control_regions = sample.regions_bed + if control_regions is None or control_regions == matched_regions: + raise ValueError( + "signal_normalization='control_regions' in region_anchored mode " + "requires sample.regions_bed to provide separate control regions." + ) + control_artifact = _requested_pileup_artifact( + sample, + motifs=motif_list, + matched_regions=control_regions, + signal_normalization=signal_normalization, + feature_scaling=feature_scaling, + cluster_basis=cluster_basis, + ) + resolved_control_artifact = resolve_artifact( + control_artifact, + _coerce_artifacts(sample), + artifact_policy=artifact_policy, + ) + control_path = ( + resolved_control_artifact.path + if resolved_control_artifact is not None + else _require_pileup_path(sample) + ) + control_matrix, _ = region_analysis.build_region_feature_table( + samples=[sample], + motifs=motif_list, + matched_regions=control_regions, + pileup_paths={sample.sample_id: control_path}, + cores=cores, + quiet=quiet, + ) + offset = float(np.asarray(control_matrix, dtype=float).mean()) + else: + offset = float(region_matrix[sample_indices].mean()) + region_matrix[sample_indices] = region_matrix[sample_indices] - offset + sample_normalization[sample_id] = {"global_offset": offset} + + if cluster_basis == "shape_only": + region_matrix = region_matrix - region_matrix.mean(axis=1, keepdims=True) + feature_names = [f"shape_{idx}" for idx in range(region_matrix.shape[1])] + elif cluster_basis == "level_only": + region_matrix = region_matrix.mean(axis=1, keepdims=True) + feature_names = ["region_mean_mod_fraction"] + else: + feature_names = [f"pos_{idx}" for idx in range(region_matrix.shape[1])] + + feature_blocks: list[np.ndarray] = [] + training_blocks: list[np.ndarray] = [] + sample_training_rows: dict[str, int] = {} + for sample in sample_list: + sample_matrix = region_matrix[sample_row_indices[sample.sample_id]] + feature_blocks.append(sample_matrix) + training_matrix, _ = _sample_training_rows( + sample_matrix, + training_sample_per_dataset=training_sample_per_dataset, + random_state=random_state + len(training_blocks), + ) + training_blocks.append(training_matrix) + sample_training_rows[sample.sample_id] = int(training_matrix.shape[0]) + + return _build_shared_cluster_result( + mode=mode, + motifs=motif_list, + feature_blocks=feature_blocks, + training_blocks=training_blocks, + metadata_rows=metadata_rows, + feature_names=feature_names, + signal_normalization=signal_normalization, + feature_scaling=feature_scaling, + cluster_basis=cluster_basis, + clusterer=clusterer, + n_clusters=n_clusters, + training_sample_per_dataset=training_sample_per_dataset, + artifact_policy=artifact_policy, + random_state=random_state, + make_plots=make_plots, + matched_regions=matched_regions, + sample_training_rows=sample_training_rows, + sample_dataset_sizes=sample_dataset_sizes, + sample_normalization=sample_normalization, + cache_hits=cache_hits, + cache_misses=cache_misses, + sample_rebuild_decisions=sample_rebuild_decisions, + quiet=quiet, + ) + + feature_blocks: list[np.ndarray] = [] + metadata_rows: list[dict[str, Any]] = [] + training_blocks: list[np.ndarray] = [] + selected_feature_names: list[str] | None = None + sample_training_rows: dict[str, int] = {} + sample_dataset_sizes: dict[str, int] = {} + sample_normalization: dict[str, dict[str, float | None]] = {} + cache_hits: dict[str, str] = {} + cache_misses: list[str] = [] + sample_rebuild_decisions: dict[str, str] = {} + + for sample_index, sample in enumerate(sample_list): + requested_artifact = _requested_extract_artifact( + sample, + motifs=motif_list, + matched_regions=matched_regions, + signal_normalization=signal_normalization, + feature_scaling=feature_scaling, + cluster_basis=cluster_basis, + ) + resolved_artifact = resolve_artifact( + requested_artifact, + _coerce_artifacts(sample), + artifact_policy=artifact_policy, + ) + extract_path = sample.extract_h5 + if resolved_artifact is not None: + extract_path = resolved_artifact.path + cache_hits[sample.sample_id] = str(resolved_artifact.path) + sample_rebuild_decisions[sample.sample_id] = "cache_hit" + else: + cache_misses.append(sample.sample_id) + sample_rebuild_decisions[sample.sample_id] = "rebuilt_from_raw" + + extracted = cluster.extract_read_windows( + hdf5_file=extract_path, + motifs=motif_list, + regions=matched_regions, + ) + control_result = None + if signal_normalization == "control_regions": + control_regions = sample.regions_bed or matched_regions + if control_regions is None: + raise ValueError( + "signal_normalization='control_regions' requires sample.regions_bed " + "or matched_regions." + ) + control_artifact = _requested_extract_artifact( + sample, + motifs=motif_list, + matched_regions=control_regions, + signal_normalization=signal_normalization, + feature_scaling=feature_scaling, + cluster_basis=cluster_basis, + ) + resolved_control_artifact = resolve_artifact( + control_artifact, + _coerce_artifacts(sample), + artifact_policy=artifact_policy, + ) + control_path = ( + resolved_control_artifact.path + if resolved_control_artifact is not None + else extract_path + ) + control_result = cluster.extract_read_windows( + hdf5_file=control_path, + motifs=motif_list, + regions=control_regions, + ) + extracted, normalization_meta = _normalize_read_windows( + extracted, + signal_normalization=signal_normalization, + control_result=control_result, + ) + sample_normalization[sample.sample_id] = normalization_meta + + feature_matrix, feature_names = cluster.read_window_feature_matrix(extracted) + feature_matrix, feature_names = _select_feature_columns( + feature_matrix, + feature_names, + cluster_basis=cluster_basis, + ) + if selected_feature_names is None: + selected_feature_names = feature_names + elif selected_feature_names != feature_names: + raise ValueError("Feature names must match across all samples.") + + feature_blocks.append(feature_matrix) + sample_dataset_sizes[sample.sample_id] = int(feature_matrix.shape[0]) + training_matrix, _ = _sample_training_rows( + feature_matrix, + training_sample_per_dataset=training_sample_per_dataset, + random_state=random_state + sample_index, + ) + training_blocks.append(training_matrix) + sample_training_rows[sample.sample_id] = int(training_matrix.shape[0]) + + for metadata in extracted.metadata: + row = { + "sample_id": sample.sample_id, + "condition": sample.condition, + "replicate": sample.replicate, + } + _merge_sample_metadata(row, sample.metadata) + row.update(metadata) + metadata_rows.append(row) + + if selected_feature_names is None: + raise ValueError("No features were generated for the requested samples.") + return _build_shared_cluster_result( + mode=mode, + motifs=motif_list, + feature_blocks=feature_blocks, + training_blocks=training_blocks, + metadata_rows=metadata_rows, + feature_names=list(selected_feature_names), + signal_normalization=signal_normalization, + feature_scaling=feature_scaling, + cluster_basis=cluster_basis, + clusterer=clusterer, + n_clusters=n_clusters, + training_sample_per_dataset=training_sample_per_dataset, + artifact_policy=artifact_policy, + random_state=random_state, + make_plots=make_plots, + matched_regions=matched_regions, + sample_training_rows=sample_training_rows, + sample_dataset_sizes=sample_dataset_sizes, + sample_normalization=sample_normalization, + cache_hits=cache_hits, + cache_misses=cache_misses, + sample_rebuild_decisions=sample_rebuild_decisions, + quiet=quiet, + ) diff --git a/dimelo_test.ipynb b/dimelo_test.ipynb new file mode 100644 index 0000000..87f8686 --- /dev/null +++ b/dimelo_test.ipynb @@ -0,0 +1,8242 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Mac and Linux setup\n", + "\n", + "Use a conda/mamba environment with `modkit 0.6.1` installed (see `README.md`).\n", + "If you launch notebooks outside an activated shell, connect the correct kernel (`dimelo-test` by default) so Python, `dimelo`, and `modkit` resolve from the same environment.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Colab setup\n", + "\n", + "If you are running in Google Colab, run the cells below. **It is expected that Colab will make you restart your runtime to enable new package versions.**\n", + "This notebook is pinned to `modkit 0.6.1`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You don't seem to be running on Google Colab so this cell is not going to be useful for you.\n", + "Make sure you are running your notebook in a conda environment set up as per README.md.\n", + "Once you have this notebook running in a correctly configured environment, proceed to the rest of the cells.\n" + ] + } + ], + "source": [ + "# Lets Colab access your Google drive\n", + "try:\n", + " from google.colab import drive\n", + "except:\n", + " print(\n", + " \"You don't seem to be running on Google Colab so this cell is not going to be useful for you.\\n\"\n", + " \"Make sure you are running your notebook in a conda environment set up as per README.md.\\n\"\n", + " \"Once you have this notebook running in a correctly configured environment, proceed to the rest of the cells.\"\n", + " )\n", + "else:\n", + " drive.mount('/content/drive')\n", + " # Install condacolab to let us get the modkit dependency\n", + " !pip install -q condacolab\n", + " import condacolab\n", + " condacolab.install()\n", + " # Install pinned modkit version for this notebook\n", + " !conda install -y nanoporetech::modkit==0.6.1\n", + " # Clone the repo, change the active path to be inside the repo, and install the package\n", + " !rm -rf dimelo-toolkit\n", + " !git clone https://github.com/streetslab/dimelo-toolkit\n", + " import os\n", + " os.chdir('dimelo-toolkit')\n", + " !pip install ipywidgets==7.7.1 .\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After runtime restart in previous cell, run this cell to make sure your notebook is set up." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " from google.colab import drive\n", + " drive.mount('/content/drive')\n", + " import os\n", + " os.chdir('dimelo-toolkit') \n", + "except:\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# System and Versions Information" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conda Environment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run this cell to list the packages present in your conda environment" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "CondaError: Run 'conda init' before 'conda activate'\n", + "\n" + ] + } + ], + "source": [ + "!conda activate dimelo-toolkit" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## System Information" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run this cell to list system information about your machine" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "System: Darwin\n", + "Release: 25.5.0\n", + "Version: Darwin Kernel Version 25.5.0: Thu Apr 23 21:19:59 PDT 2026; root:xnu-12377.121.5~4/RELEASE_ARM64_T6020\n", + "Processor: arm\n" + ] + } + ], + "source": [ + "import platform\n", + "print('System:',platform.system())\n", + "print('Release:',platform.release())\n", + "print('Version:',platform.version())\n", + "print('Processor:',platform.processor())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Git Repo Version" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run this cell to capture the exact repo version you're running, in case we need to debug anything" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "48d55c3d37523f09a7aead10b19b0afe426f2930\n" + ] + } + ], + "source": [ + "!git -C . log -1 --format=%H" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modkit Version Requirement (`0.6.1`)\n", + "\n", + "This notebook is intentionally pinned to `modkit 0.6.1`.\n", + "It will fail fast if your active `modkit` version does not match, so parsing/extraction behavior is reproducible.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "modkit required: 0.6.1\n", + "modkit active : 0.6.1\n" + ] + } + ], + "source": [ + "import re\n", + "import shutil\n", + "import subprocess\n", + "\n", + "# Notebook requirement: keep parsing/extract behavior fixed to 0.6.1.\n", + "REQUIRED_MODKIT_VERSION = \"0.6.1\"\n", + "AUTO_INSTALL_REQUIRED_MODKIT = False\n", + "\n", + "\n", + "def _installed_modkit_version(binary: str = \"modkit\") -> str | None:\n", + " try:\n", + " proc = subprocess.run([binary, \"--version\"], capture_output=True, text=True, check=True)\n", + " except Exception:\n", + " return None\n", + " text = (proc.stdout or proc.stderr or \"\").strip()\n", + " m = re.search(r\"(\\d+\\.\\d+\\.\\d+)\", text)\n", + " return m.group(1) if m else text\n", + "\n", + "\n", + "def ensure_modkit_061(\n", + " required: str = REQUIRED_MODKIT_VERSION,\n", + " binary: str = \"modkit\",\n", + " auto_install: bool = AUTO_INSTALL_REQUIRED_MODKIT,\n", + ") -> str:\n", + " installed = _installed_modkit_version(binary)\n", + "\n", + " if installed != required and auto_install:\n", + " installer = shutil.which(\"mamba\") or shutil.which(\"conda\")\n", + " if installer is None:\n", + " raise RuntimeError(\n", + " \"modkit version mismatch and no conda/mamba installer found on PATH. \"\n", + " \"Install modkit manually or switch to the intended environment.\"\n", + " )\n", + " subprocess.run([installer, \"install\", \"-y\", f\"nanoporetech::modkit=={required}\"], check=True)\n", + " installed = _installed_modkit_version(binary)\n", + "\n", + " # Clear cached capability detection so flags match the active binary.\n", + " try:\n", + " from dimelo import run_modkit\n", + "\n", + " run_modkit._get_modkit_capabilities_cached.cache_clear()\n", + " except Exception:\n", + " pass\n", + "\n", + " if installed != required:\n", + " raise RuntimeError(\n", + " f\"Notebook requires modkit {required}, but PATH resolves to {installed!r}. \"\n", + " \"Activate the intended environment, or set AUTO_INSTALL_REQUIRED_MODKIT=True.\"\n", + " )\n", + "\n", + " print(f\"modkit required: {required}\")\n", + " print(f\"modkit active : {installed}\")\n", + " return binary\n", + "\n", + "\n", + "MODKIT_BINARY = ensure_modkit_061()\n", + "\n", + "\n", + "def run_modkit_checked(args: list[str]) -> None:\n", + " ensure_modkit_061(binary=MODKIT_BINARY)\n", + " cmd = [MODKIT_BINARY, *[str(a) for a in args]]\n", + " print(\"$\", \" \".join(cmd))\n", + " subprocess.run(cmd, check=True)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modkit Version Comparison (Cached Results)\n", + "\n", + "This cell summarizes the cached benchmark at `modkit_test_results.json` and compares runtime/output behavior across tested versions.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "version", + "rawType": "str", + "type": "string" + }, + { + "name": "runtime_mean_s", + "rawType": "float64", + "type": "float" + }, + { + "name": "runtime_std_s", + "rawType": "float64", + "type": "float" + }, + { + "name": "runtime_min_s", + "rawType": "float64", + "type": "float" + }, + { + "name": "runtime_max_s", + "rawType": "float64", + "type": "float" + } + ], + "ref": "9b72b298-84e1-4344-bdfd-2b213cc094ad", + "rows": [ + [ + "0", + "0.6.1", + "2.0819532913155854", + "0.2763177122597796", + "1.7662932079983875", + "2.2800389159237966" + ], + [ + "1", + "0.2.4", + "5.246847764006816", + "0.37088222422172723", + "5.00622979097534", + "5.67396429204382" + ] + ], + "shape": { + "columns": 5, + "rows": 2 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
versionruntime_mean_sruntime_std_sruntime_min_sruntime_max_s
00.6.12.0819530.2763181.7662932.280039
10.2.45.2468480.3708825.0062305.673964
\n", + "
" + ], + "text/plain": [ + " version runtime_mean_s runtime_std_s runtime_min_s runtime_max_s\n", + "0 0.6.1 2.081953 0.276318 1.766293 2.280039\n", + "1 0.2.4 5.246848 0.370882 5.006230 5.673964" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgsAAAFjCAYAAACzEEhkAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAALndJREFUeJzt3QeUFGX6/v17SEMGAUmSVVTiuqIsiguCyAILgqIIKEF0VSSDSnAJBmDNCCz8RIWFvwqKgKAkkSRBlOSCIEoSZFEQJCOx33M95+0+3ROKmaFnunvm+zmnzkxXV1c9VV1dddcT43w+n88AAACSkS25NwAAAAgWAADAJZGzAAAAPBEsAAAATwQLAADAE8ECAADwRLAAAAA8ESwAAABPBAsAAMATwQKiSv369a1atWoZsq24uDgbOnSoRbsKFSpYp06dAq8nTZrk0r527VrPz2nftFxmOS80IfL859/u3bsjnRRkIIIFJGvHjh322GOPWaVKlSx37txWsGBBu+2222zUqFF2+vRpjlwMGj58uM2aNcui0ZYtW1yAw00IiD4EC0jSZ599ZtWrV7cPP/zQmjdvbqNHj7YRI0ZYuXLl7KmnnrKePXty5KLcs88+myioi/ZgYdiwYUkGCwsXLnQTIu+hhx5y51X58uUjnRRkoBwZuTHEhl27dtkDDzzgLgaLFy+2UqVKBd578sknbfv27S6YQHTLkSOHm8Lpjz/+sFy5clm2bBn7nKFtIrJOnjxp+fLls+zZs7sJWQs5C0jkpZdeshMnTtg777wTEij4XXPNNSE5CxMnTrQGDRpY8eLFLT4+3qpUqWLjxo1L8sjOmzfP6tWrZwUKFHDFGjfffLO9//77ST5l3nHHHZY3b1676qqrXJoSOnPmjA0ZMsSlR9stW7asPf30025+wuV69+5tV155pdtuixYt7Oeff07RN7906VJXPqscFj31Ki1aR+vWre3o0aNu3b169XL7nj9/fuvcuXOi7Z8/f96ef/55u/rqq106VQdh4MCBiZbTALAvvPCClSlTxu239v+7775LUTp///13u+WWW9xnt23blmSdBf2vC/5//vMf97+m4LoQye371KlTXS6F9l3pOnbsWLL1IZIqz9b+/v3vf7cVK1a4NKpIS0VbkydPDvncfffd5/7XfvvTpzQkVWchHN+L/L//9//spptusjx58liRIkVckLx3794UHfN9+/ZZly5drHTp0u57rVixoj3xxBN29uzZwDI7d+50+6V169j95S9/SRRoh2Nf9Plu3brZe++9Z9ddd507xtqv5cuXhyz3008/WdeuXd0y2ueiRYu69CXMzfF/j8uWLXPLa9s6t5L7jlV/pnHjxlasWDG3Xh2Lhx9+OGSdOvf69u3rfqc6XkrDK6+84s77pPZFOWCqv6Rlq1atavPnz0/R94L0Qc4CEpkzZ467mN96660pOjoKDPRj1k1YT7L6vC4wFy9edDkRfrrI6AKiZQcMGGCFCxe2DRs2uItAu3btQm58f/vb3+yee+6x+++/36ZPn27PPPOMKxZp0qSJW0br1vZ0A/rHP/5hN9xwg23atMlef/11++GHH0Ky2h955BF3U9A2tE/KLWnWrFmqvnkVwegi2L9/f5ezomKZnDlzuidspVc3z6+++srtoy6UgwcPDtm+btC6+OtiuWbNGre+rVu32syZMwPL6TMKFpo2beqm9evX21133RVy80nKb7/9Zo0aNbLDhw+7i7uCkqRMmTLFpUU3bB0zSW7ZYAp09GTfr18/d5NKy1O+jpn2XzfXjh072rvvvusCFd3QdD789a9/tR49etibb77pAil9n+L/mx7fy4svvmj//Oc/3Tmm43Lw4EH3eaVF56XOz+T873//c8fxyJEj7lhef/31LnjQuXrq1Cl3jH799Vd3vum19k03Zp0HOm+1XKtWrcK2L6Lvftq0aW5busH++9//dr+jr7/+OlBp+JtvvrFVq1a5oEg3f93w9ftVIKYAXQFNMP2OFWRrW7rZJ+XAgQPuPNVySruOm9Y7Y8aMwDIKCLTfS5YscefAn/70J1uwYIEr0tRx0+82mH7X+ry2r8BJ58W9995re/bscccREeADghw9elRhvu/uu+9O8XE5depUonmNGzf2VapUKfD6yJEjvgIFCvhq167tO336dMiyFy9eDPxfr149t/3JkycH5p05c8ZXsmRJ37333huYN2XKFF+2bNl8X375Zci6xo8f7z6/cuVK93rjxo3uddeuXUOWa9eunZs/ZMgQz31bsmSJW65atWq+s2fPBua3bdvWFxcX52vSpEnI8nXq1PGVL18+8Nq//UceeSRkuX79+rn5ixcvdq8PHDjgy5Url69Zs2Yhx2PgwIFuuY4dOwbmTZw40c375ptvfPv3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99///tf397//3W2nZ8+egWXKly/ve/31193///rXv9w21qxZE5I2//KHDh3ylSlTxvfcc88FRiJMikYUzZMnT8gohXPmzHHzjh075l5rtNHrr7/eN3/+fN+OHTt8EydOdCMjLl261L2/ZMkSjRTmq1Gjhm/hwoW+7du3u+1XrVrV9+CDD7p91nH58MMPfRs3bnSf0ToKFSoU2OaMGTN8OXPm9I0dO9aNdPrqq6+647p48eLAMtqG9un999/3/fjjj74ePXr48ufP77YFZFUEC0AU0g25bt26gdfnz5/35cuXL2QIYN2YdWNbvXq1ez1w4EDfdddd57t48WJgGd0UdaO7cOGCu1FrmGHdTP10A9QNO6lg4emnn/aVKlXKt3nz5kRpSy64SM65c+dc4DJ58uTAvLZt2/ratGnj/v/jjz9coLNq1aqQz3Xp0sUtFxwszJo1K2QZBTsaajkpCYOFW2+91ffoo4+GLHPffff5mjZtGnitbTz77LOB1ydOnHDz5s2b57mPQGZGMQQQpTRst1/27NmtaNGiVr169cA8/3DCBw4ccH+3bt1qderUCSkKuO222+zEiRP2888/u6G+Ncxw7dq1Q+ofKCs+IRVPqNhD2fxVq1a97H1RvYL777/f3nvvvUBxyCeffGLt27d3r7dv326nTp2yRo0auSIS/6QiioRDlNeqVSvkdZ8+feyRRx6xO++800aOHJlo+WA6RjomwfRa85M79vny5XPFG/7jDGRFBAtAlMqZM2fIawUBwfP8QYHK+sPt9ttvd5UHP/zww7CtU4GB6j3opjtr1ixXL+Fvf/ube08BjXz22We2cePGwLRly5aQegv+m3ewoUOH2nfffWfNmjWzxYsXW5UqVWzmzJlhP/bpcZyBWEGwAGQSN9xwg61evdq1EvBbuXKlq/hXpkwZV6lPN8E1a9YE3v/999+TbFqoSpKqSDh8+HB75ZVXPLerioIKLC7l1ltvtbJly9q0adNcDsN9990XuCnrBq8Ki3v27HEVN4MnfeZSKleu7CpZLly40O655x6bOHFissdIxySYXmv7AJJH00kgk+jatavr70CtKLp162bbtm2zIUOGuGz6bNmyuWz9Ll262FNPPeWKNIoXL26DBg1y7yV3c587d641adLEFSMk1xGT+llYvny5PfDAA+6GrxYOyVGriPHjx7sAZcmSJYH5Cmj69evnbvh6gq9bt64dPXrU3chVBNCxY8ck13f69Gm3P61bt3atKFTc8s0339i9996b5PJaVsUhN954oyu2mDNnjus3YtGiRZc4ukDWRrAAZBJXXXWVu7nrhqhmh6qPoODg2WefDSzz8ssvuyz/5s2buxt037593U05Obppq2igadOmrt6EApGEnnvuOXvsscdczsWZM2dCcjaSKop48cUXrXz58onqDqijpyuvvNI1n9y5c6dr8vjnP//ZBg4cmOz6lKZDhw655qC//vqrC1SUszBs2LAkl2/ZsqWNGjXK5Zb07NnTBRjKhVATUADJi1MtR4/3AQBAFkedBQAA4IlgAQAAeCJYAAAAnggWAACAJ4IFAADgiWABAAB4IlgAAACeCBYAAIAnggUAAOCJYAEAAHgiWAAAAJ4IFgAAgHn5/wD0C/j+Ua1dHQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Compatibility flags\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "version", + "rawType": "str", + "type": "string" + }, + { + "name": "supports_force_allow_implicit", + "rawType": "bool", + "type": "boolean" + }, + { + "name": "supports_modified_bases", + "rawType": "bool", + "type": "boolean" + }, + { + "name": "supports_reference_long_opt", + "rawType": "bool", + "type": "boolean" + }, + { + "name": "supports_reference_short_opt", + "rawType": "bool", + "type": "boolean" + } + ], + "ref": "be7427c9-cb8b-4f54-bc76-a1521287ea3b", + "rows": [ + [ + "0", + "0.2.4", + "True", + "False", + "False", + "True" + ], + [ + "1", + "0.6.1", + "False", + "True", + "True", + "True" + ] + ], + "shape": { + "columns": 5, + "rows": 2 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
versionsupports_force_allow_implicitsupports_modified_basessupports_reference_long_optsupports_reference_short_opt
00.2.4TrueFalseFalseTrue
10.6.1FalseTrueTrueTrue
\n", + "
" + ], + "text/plain": [ + " version supports_force_allow_implicit supports_modified_bases \\\n", + "0 0.2.4 True False \n", + "1 0.6.1 False True \n", + "\n", + " supports_reference_long_opt supports_reference_short_opt \n", + "0 False True \n", + "1 True True " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pileup comparison summary\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "row_delta", + "rawType": "int64", + "type": "integer" + }, + { + "name": "row_delta_pct_vs_024", + "rawType": "float64", + "type": "float" + }, + { + "name": "outputs_identical_sha256", + "rawType": "bool", + "type": "boolean" + } + ], + "ref": "f7677d08-9e7d-4538-949e-05d164428e89", + "rows": [ + [ + "0", + "1603", + "0.3127572979954657", + "False" + ] + ], + "shape": { + "columns": 3, + "rows": 1 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
row_deltarow_delta_pct_vs_024outputs_identical_sha256
016030.312757False
\n", + "
" + ], + "text/plain": [ + " row_delta row_delta_pct_vs_024 outputs_identical_sha256\n", + "0 1603 0.312757 False" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Recommendations\n", + "- Prefer modkit 0.6.1 for pileup runtime in this workflow (~60.3% faster on average).\n", + "- Backend-aware command building is required: use --force-allow-implicit for 0.2.4 only.\n", + "\n", + "Re-checking required modkit binary after comparison...\n", + "modkit required: 0.6.1\n", + "modkit active : 0.6.1\n" + ] + } + ], + "source": [ + "import json\n", + "from pathlib import Path\n", + "\n", + "import pandas as pd\n", + "from matplotlib import pyplot as plt\n", + "\n", + "results_path = Path(\"modkit_test_results.json\")\n", + "if not results_path.exists():\n", + " print(f\"No benchmark file found at {results_path}.\")\n", + " print(\"This notebook still requires modkit 0.6.1 even without cached comparison results.\")\n", + "else:\n", + " results = json.loads(results_path.read_text())\n", + "\n", + " runtime_summary = pd.DataFrame(results.get(\"runtime_summary\", []))\n", + " if not runtime_summary.empty:\n", + " runtime_summary = runtime_summary.sort_values(\"runtime_mean_s\").reset_index(drop=True)\n", + " display(runtime_summary)\n", + "\n", + " fig, ax = plt.subplots(figsize=(6, 3.5))\n", + " ax.bar(runtime_summary[\"version\"], runtime_summary[\"runtime_mean_s\"], color=[\"#4C78A8\", \"#F58518\", \"#54A24B\"])\n", + " ax.set_ylabel(\"Mean runtime (s)\")\n", + " ax.set_xlabel(\"modkit version\")\n", + " ax.set_title(\"Cached modkit runtime comparison\")\n", + " plt.show()\n", + "\n", + " best_version = str(runtime_summary.iloc[0][\"version\"])\n", + " if best_version != REQUIRED_MODKIT_VERSION:\n", + " print(\n", + " f\"WARNING: cached benchmark fastest version is {best_version}, \"\n", + " f\"but notebook remains pinned to {REQUIRED_MODKIT_VERSION} for reproducibility.\"\n", + " )\n", + "\n", + " edge_flags = pd.DataFrame(results.get(\"edge_case_flags\", []))\n", + " if not edge_flags.empty:\n", + " print(\"Compatibility flags\")\n", + " display(edge_flags)\n", + "\n", + " pileup_cmp = results.get(\"pileup_comparison\", {})\n", + " if pileup_cmp:\n", + " print(\"Pileup comparison summary\")\n", + " display(pd.DataFrame([pileup_cmp]))\n", + "\n", + " print(\"Recommendations\")\n", + " for rec in results.get(\"recommendations\", []):\n", + " print(f\"- {rec}\")\n", + "\n", + "print(\"\\nRe-checking required modkit binary after comparison...\")\n", + "MODKIT_BINARY = ensure_modkit_061(binary=MODKIT_BINARY, auto_install=False)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Initialization" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from matplotlib import pyplot as plt \n", + "import urllib, gzip" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dimelo module path: /Users/ngamarra/Documents/GitHub/dimelo-toolkit/dimelo/__init__.py\n", + "dimelo version: unknown\n" + ] + } + ], + "source": [ + "# Ensure this notebook imports the local repo version of dimelo\n", + "import importlib\n", + "from pathlib import Path\n", + "import dimelo\n", + "\n", + "importlib.reload(dimelo)\n", + "print('dimelo module path:', Path(dimelo.__file__).resolve())\n", + "print('dimelo version:', getattr(dimelo, '__version__', 'unknown'))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# Base input and output directories\n", + "test_data_dir = Path('./dimelo/test/data')\n", + "output_dir = Path('./dimelo/test/output')\n", + "\n", + "output_dir.mkdir(exist_ok=True)\n", + "\n", + "# Input files\n", + "ctcf_bam_file = test_data_dir / 'ctcf_demo.sorted.bam'\n", + "ctcf_guppy_bam_file = test_data_dir / 'winnowmap_guppy_merge_subset.updated.bam'\n", + "ctcf_target_regions = test_data_dir / 'ctcf_demo_peak.bed'\n", + "ctcf_off_target_regions = test_data_dir / 'ctcf_demo_not_peak.bed'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cell below only needs to run once; if you try to run it with the reference genome already downloaded it will likely just not complete." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reference genome already present. Set force_redownload=True to if you want to download it again.\n" + ] + } + ], + "source": [ + "ref_genome_url = 'https://s3-us-west-2.amazonaws.com/human-pangenomics/T2T/CHM13/assemblies/chm13.draft_v1.0.fasta.gz'\n", + "ref_genome_gz = output_dir / 'chm13.draft_v1.0.fasta.gz'\n", + "ref_genome_file = output_dir / 'chm13.draft_v1.0.fasta'\n", + "force_redownload = False\n", + "if ref_genome_file.exists() and not force_redownload:\n", + " print('Reference genome already present. Set force_redownload=True to if you want to download it again.')\n", + "else:\n", + " urllib.request.urlretrieve(ref_genome_url,ref_genome_gz)\n", + "\n", + " with gzip.open(ref_genome_gz,'rb') as gzip_file:\n", + " with open(ref_genome_file,'wb') as output_file:\n", + " for chunk in gzip_file:\n", + " output_file.write(chunk)\n", + "\n", + " ref_genome_gz.unlink()\n", + " print(\"Reference genome downloaded and decompressed.\")\n", + "# !curl https://s3-us-west-2.amazonaws.com/human-pangenomics/T2T/CHM13/assemblies/chm13.draft_v1.0.fasta.gz -o ./test/output/chm13.draft_v1.0.fasta.gz\n", + "# !gunzip ./test/output/chm13.draft_v1.0.fasta.gz" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PARSING quiet mode for notebook progress: False\n", + "PARSING overwrite mode: True\n" + ] + } + ], + "source": [ + "import importlib\n", + "import gzip\n", + "from pathlib import Path\n", + "from dimelo import parse_bam, run_modkit\n", + "\n", + "# Ensure notebook uses latest local backend code and capability logic.\n", + "importlib.reload(run_modkit)\n", + "importlib.reload(parse_bam)\n", + "\n", + "# Reset cached capability detection after reloads/version changes.\n", + "run_modkit._get_modkit_capabilities_cached.cache_clear()\n", + "\n", + "# Notebook-safe default: suppress live tqdm progress from parse/extract in Jupyter.\n", + "# This avoids mangled carriage-return output while preserving deterministic results.\n", + "PARSE_QUIET = False\n", + "\n", + "# Output overwrite policy for parse cells below.\n", + "# False: reuse existing complete outputs under output_name.\n", + "# True: force regeneration and replace existing outputs.\n", + "PARSE_OVERWRITE = True\n", + "\n", + "print(f\"PARSING quiet mode for notebook progress: {PARSE_QUIET}\")\n", + "print(f\"PARSING overwrite mode: {PARSE_OVERWRITE}\")\n", + "\n", + "\n", + "def _has_any_bed_rows(bgzip_bed: Path) -> bool:\n", + " if not bgzip_bed.exists() or bgzip_bed.stat().st_size < 80:\n", + " return False\n", + " try:\n", + " with gzip.open(bgzip_bed, \"rt\") as f:\n", + " next(f)\n", + " return True\n", + " except Exception:\n", + " return False\n", + "\n", + "\n", + "def _has_any_extract_rows(extract_h5: Path) -> bool:\n", + " if not extract_h5.exists() or extract_h5.stat().st_size < 1024:\n", + " return False\n", + " try:\n", + " import h5py\n", + "\n", + " with h5py.File(extract_h5, \"r\") as h5:\n", + " return \"read_name\" in h5 and len(h5[\"read_name\"]) > 0\n", + " except Exception:\n", + " return False\n", + "\n", + "\n", + "def ensure_pileup_output(*, output_name: str, overwrite: bool | None = None, **pileup_kwargs):\n", + " if overwrite is None:\n", + " overwrite = PARSE_OVERWRITE\n", + " out_dir = Path(pileup_kwargs[\"output_directory\"])\n", + " pileup_out = out_dir / output_name / \"pileup.sorted.bed.gz\"\n", + " regions_out = out_dir / output_name / \"regions.processed.bed\"\n", + "\n", + " existing_complete = (\n", + " pileup_out.exists()\n", + " and Path(str(pileup_out) + \".tbi\").exists()\n", + " and regions_out.exists()\n", + " and _has_any_bed_rows(pileup_out)\n", + " )\n", + "\n", + " if (not overwrite) and existing_complete:\n", + " print(f\"Reusing existing pileup outputs: {pileup_out}\")\n", + " return pileup_out, regions_out\n", + "\n", + " if (not overwrite) and pileup_out.exists() and not _has_any_bed_rows(pileup_out):\n", + " print(f\"Detected empty/invalid pileup output at {pileup_out}; regenerating.\")\n", + " overwrite = True\n", + "\n", + " return parse_bam.pileup(output_name=output_name, overwrite=overwrite, **pileup_kwargs)\n", + "\n", + "\n", + "def ensure_extract_output(*, output_name: str, overwrite: bool | None = None, **extract_kwargs):\n", + " if overwrite is None:\n", + " overwrite = PARSE_OVERWRITE\n", + " out_dir = Path(extract_kwargs[\"output_directory\"])\n", + " extract_out = out_dir / output_name / \"reads.combined_basemods.h5\"\n", + " regions_out = out_dir / output_name / \"regions.processed.bed\"\n", + "\n", + " existing_complete = (\n", + " extract_out.exists()\n", + " and regions_out.exists()\n", + " and _has_any_extract_rows(extract_out)\n", + " )\n", + "\n", + " if (not overwrite) and existing_complete:\n", + " print(f\"Reusing existing extract outputs: {extract_out}\")\n", + " return extract_out, regions_out\n", + "\n", + " if (not overwrite) and extract_out.exists() and not _has_any_extract_rows(extract_out):\n", + " print(f\"Detected empty/invalid extract output at {extract_out}; regenerating.\")\n", + " overwrite = True\n", + "\n", + " return parse_bam.extract(output_name=output_name, overwrite=overwrite, **extract_kwargs)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Basic Use Overview" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For details on parameters and functionality breakdown, please read the README sections on **Basic Use**, especially **Parameters and what they mean**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Parsing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pileup and Update Tags" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Attempting to parse a malformed BAM file will raise an error, thanks to input BAM format checking. Passing a bam file that doesn't meet the specifications directly to `modkit` will typically result in an empty ouput file; `modkit` will find no valid base modifications and return all-zero data.\n", + "\n", + "Megalodon and Guppy bam files are considered malformed because they do not meet the latest .bam spec. Some newer basecallers output correctly formatted files without requiring additional processing.\n", + "\n", + "However, `modkit` comes with two tools that can update bam files to meet spec." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This is expected to fail. Read the error message below:\n", + "\n", + "\n", + "Base modification tags are out of spec (Mm and Ml instead of MM and ML). \n", + "\n", + "Consider using \"modkit update-tags dimelo/test/data/ctcf_demo.sorted.bam new_file.bam\" in the command line with your conda environment active and then trying with the new file. For megalodon basecalling/modcalling, you may also need to pass \"--mode ambiguous.\n", + "Be sure to index the resulting .bam file.\"\n", + "If you are confident that your inputs are ok, pass \"override_checks=True\" to convert to warning and proceed with processing.\n" + ] + } + ], + "source": [ + "try:\n", + " pileup_file, pileup_regions = parse_bam.pileup(\n", + " input_file=ctcf_bam_file,\n", + " output_name='ctcf_demo_pileup_on_target',\n", + " ref_genome=ref_genome_file,\n", + " output_directory=output_dir,\n", + " regions=[ctcf_target_regions,ctcf_off_target_regions],\n", + " motifs=['A,0','CG,0'],\n", + " # parsing can optionally specify mod codes. \n", + " # The default is Y or a for adenine and m or Z for cytosine, corresponding to methylation\n", + " # motifs = ['A,0,Y','CG,0,Z'], \n", + " thresh=128,\n", + " window_size=1000,\n", + " # cores = 1, # uncomment this line if your process appears to be terminating early (reduces memory usage)\n", + " # quiet = True,\n", + " # cleanup = False,\n", + " # log=True,\n", + " quiet=PARSE_QUIET,\n", + " modkit_executable=MODKIT_BINARY,\n", + " )\n", + " print('The code executed successfully.')\n", + "except Exception as e:\n", + " print('This is expected to fail. Read the error message below:\\n\\n')\n", + " print(e)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To fix this, follow the suggestion from the error and run `modkit update-tags`..." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "modkit required: 0.6.1\n", + "modkit active : 0.6.1\n", + "$ modkit update-tags dimelo/test/data/ctcf_demo.sorted.bam dimelo/test/output/ctcf_demo.updated.tmp.bam --mode ambiguous\n", + "Updated BAM ready: dimelo/test/output/ctcf_demo.updated.bam\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;32m>\u001b[0m done, 1024 records processed\n" + ] + } + ], + "source": [ + "import shutil\n", + "from pathlib import Path\n", + "\n", + "import pysam\n", + "\n", + "ctcf_bam_file_updated = output_dir / \"ctcf_demo.updated.bam\"\n", + "ctcf_bam_file_updated_tmp = output_dir / \"ctcf_demo.updated.tmp.bam\"\n", + "\n", + "# Always regenerate this file for deterministic notebook reruns.\n", + "for stale in [\n", + " ctcf_bam_file_updated,\n", + " Path(str(ctcf_bam_file_updated) + \".bai\"),\n", + " ctcf_bam_file_updated_tmp,\n", + " Path(str(ctcf_bam_file_updated_tmp) + \".bai\"),\n", + "]:\n", + " stale.unlink(missing_ok=True)\n", + "\n", + "run_modkit_checked([\n", + " \"update-tags\",\n", + " str(ctcf_bam_file),\n", + " str(ctcf_bam_file_updated_tmp),\n", + " \"--mode\",\n", + " \"ambiguous\",\n", + "])\n", + "\n", + "# Quick sanity check before promoting the file into the main path.\n", + "pysam.quickcheck(str(ctcf_bam_file_updated_tmp))\n", + "shutil.move(str(ctcf_bam_file_updated_tmp), str(ctcf_bam_file_updated))\n", + "Path(str(ctcf_bam_file_updated_tmp) + \".bai\").unlink(missing_ok=True)\n", + "\n", + "print(f\"Updated BAM ready: {ctcf_bam_file_updated}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using updated BAM: dimelo/test/output/ctcf_demo.updated.bam\n" + ] + } + ], + "source": [ + "print(f'Using updated BAM: {ctcf_bam_file_updated}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "...and reindex the resulting file." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Indexed dimelo/test/output/ctcf_demo.updated.bam -> dimelo/test/output/ctcf_demo.updated.bam.bai\n" + ] + } + ], + "source": [ + "import pysam\n", + "from pathlib import Path\n", + "\n", + "index_path = Path(str(ctcf_bam_file_updated) + \".bai\")\n", + "index_path.unlink(missing_ok=True)\n", + "\n", + "# Rebuild the BAM index from scratch for deterministic reruns.\n", + "pysam.index(str(ctcf_bam_file_updated))\n", + "print(f\"Indexed {ctcf_bam_file_updated} -> {index_path}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, the updated .bam file can be parsed, both for pileup and read extraction:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No specified number of cores requested. 10 available on machine, allocating all.\n", + "Detected multiple motif contexts with modkit 0.6.x; running per-motif pileups and merging outputs to avoid mixed-motif empty output behavior.\n", + "Modification threshold of 190.0 assumed to be for range 0-255. 190.0/255=0.7450980392156863 will be sent to modkit.\n" + ] + }, + { + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[14]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m pileup_file, pileup_regions = \u001b[43mensure_pileup_output\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 2\u001b[39m \u001b[43m \u001b[49m\u001b[43minput_file\u001b[49m\u001b[43m=\u001b[49m\u001b[43mctcf_bam_file_updated\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 3\u001b[39m \u001b[43m \u001b[49m\u001b[43moutput_name\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mctcf_demo_pileup_on_target\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 4\u001b[39m \u001b[43m \u001b[49m\u001b[43mref_genome\u001b[49m\u001b[43m=\u001b[49m\u001b[43mref_genome_file\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 5\u001b[39m \u001b[43m \u001b[49m\u001b[43moutput_directory\u001b[49m\u001b[43m=\u001b[49m\u001b[43moutput_dir\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 6\u001b[39m \u001b[43m \u001b[49m\u001b[43mregions\u001b[49m\u001b[43m=\u001b[49m\u001b[43m[\u001b[49m\u001b[43mctcf_target_regions\u001b[49m\u001b[43m,\u001b[49m\u001b[43mctcf_off_target_regions\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 7\u001b[39m \u001b[43m \u001b[49m\u001b[43mmotifs\u001b[49m\u001b[43m=\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mA,0\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mCG,0\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 8\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# parsing can optionally specify mod codes.\u001b[39;49;00m\n\u001b[32m 9\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# The default is Y or a for adenine and m or Z for cytosine, corresponding to methylation\u001b[39;49;00m\n\u001b[32m 10\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# motifs=['A,0,Y','CG,0,Z'],\u001b[39;49;00m\n\u001b[32m 11\u001b[39m \u001b[43m \u001b[49m\u001b[43mthresh\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m190\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 12\u001b[39m \u001b[43m \u001b[49m\u001b[43mwindow_size\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m1000\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 13\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# cores=1, # uncomment to reduce memory usage\u001b[39;49;00m\n\u001b[32m 14\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# cleanup=False,\u001b[39;49;00m\n\u001b[32m 15\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# log=True,\u001b[39;49;00m\n\u001b[32m 16\u001b[39m \u001b[43m \u001b[49m\u001b[43mquiet\u001b[49m\u001b[43m=\u001b[49m\u001b[43mPARSE_QUIET\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 17\u001b[39m \u001b[43m \u001b[49m\u001b[43mmodkit_executable\u001b[49m\u001b[43m=\u001b[49m\u001b[43mMODKIT_BINARY\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 18\u001b[39m \u001b[43m)\u001b[49m\n", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[13]\u001b[39m\u001b[32m, line 71\u001b[39m, in \u001b[36mensure_pileup_output\u001b[39m\u001b[34m(output_name, overwrite, **pileup_kwargs)\u001b[39m\n\u001b[32m 68\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mDetected empty/invalid pileup output at \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mpileup_out\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m; regenerating.\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 69\u001b[39m overwrite = \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m71\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mparse_bam\u001b[49m\u001b[43m.\u001b[49m\u001b[43mpileup\u001b[49m\u001b[43m(\u001b[49m\u001b[43moutput_name\u001b[49m\u001b[43m=\u001b[49m\u001b[43moutput_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43moverwrite\u001b[49m\u001b[43m=\u001b[49m\u001b[43moverwrite\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mpileup_kwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/GitHub/dimelo-toolkit/dimelo/parse_bam.py:637\u001b[39m, in \u001b[36mpileup\u001b[39m\u001b[34m(input_file, output_name, ref_genome, output_directory, regions, motifs, thresh, window_size, cores, log, cleanup, overwrite, quiet, override_checks, modkit_executable)\u001b[39m\n\u001b[32m 614\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m_run_pileup_command\u001b[39m(\n\u001b[32m 615\u001b[39m extra_args: \u001b[38;5;28mlist\u001b[39m[\u001b[38;5;28mstr\u001b[39m],\n\u001b[32m 616\u001b[39m *,\n\u001b[32m 617\u001b[39m base_command: \u001b[38;5;28mlist\u001b[39m[\u001b[38;5;28mstr\u001b[39m] = base_pileup_command,\n\u001b[32m 618\u001b[39m motifs_for_progress: \u001b[38;5;28mlist\u001b[39m[\u001b[38;5;28mtuple\u001b[39m[\u001b[38;5;28mstr\u001b[39m, \u001b[38;5;28mint\u001b[39m, \u001b[38;5;28mstr\u001b[39m]] = group_motifs,\n\u001b[32m 619\u001b[39m ) -> \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 620\u001b[39m run_modkit.run_with_progress_bars(\n\u001b[32m 621\u001b[39m command_list=base_command + extra_args,\n\u001b[32m 622\u001b[39m input_file=input_file,\n\u001b[32m (...)\u001b[39m\u001b[32m 634\u001b[39m quiet=quiet,\n\u001b[32m 635\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m637\u001b[39m \u001b[43m_run_pileup_command\u001b[49m\u001b[43m(\u001b[49m\u001b[43mgroup_mod_thresh_command_list\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 639\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m split_pileup_runs:\n\u001b[32m 640\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mopen\u001b[39m(output_bedmethyl, \u001b[33m\"\u001b[39m\u001b[33mw\u001b[39m\u001b[33m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m merged_file:\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/GitHub/dimelo-toolkit/dimelo/parse_bam.py:620\u001b[39m, in \u001b[36mpileup.._run_pileup_command\u001b[39m\u001b[34m(extra_args, base_command, motifs_for_progress)\u001b[39m\n\u001b[32m 614\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m_run_pileup_command\u001b[39m(\n\u001b[32m 615\u001b[39m extra_args: \u001b[38;5;28mlist\u001b[39m[\u001b[38;5;28mstr\u001b[39m],\n\u001b[32m 616\u001b[39m *,\n\u001b[32m 617\u001b[39m base_command: \u001b[38;5;28mlist\u001b[39m[\u001b[38;5;28mstr\u001b[39m] = base_pileup_command,\n\u001b[32m 618\u001b[39m motifs_for_progress: \u001b[38;5;28mlist\u001b[39m[\u001b[38;5;28mtuple\u001b[39m[\u001b[38;5;28mstr\u001b[39m, \u001b[38;5;28mint\u001b[39m, \u001b[38;5;28mstr\u001b[39m]] = group_motifs,\n\u001b[32m 619\u001b[39m ) -> \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m620\u001b[39m \u001b[43mrun_modkit\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun_with_progress_bars\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 621\u001b[39m \u001b[43m \u001b[49m\u001b[43mcommand_list\u001b[49m\u001b[43m=\u001b[49m\u001b[43mbase_command\u001b[49m\u001b[43m \u001b[49m\u001b[43m+\u001b[49m\u001b[43m \u001b[49m\u001b[43mextra_args\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 622\u001b[39m \u001b[43m \u001b[49m\u001b[43minput_file\u001b[49m\u001b[43m=\u001b[49m\u001b[43minput_file\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 623\u001b[39m \u001b[43m \u001b[49m\u001b[43mref_genome\u001b[49m\u001b[43m=\u001b[49m\u001b[43mref_genome\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 624\u001b[39m \u001b[43m \u001b[49m\u001b[43mmotifs\u001b[49m\u001b[43m=\u001b[49m\u001b[43mmotifs_for_progress\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 625\u001b[39m \u001b[43m \u001b[49m\u001b[43mload_fasta_regex\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43mr\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43m[.*?\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43m]\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+(\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43md+)\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+Reading\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 626\u001b[39m \u001b[43m \u001b[49m\u001b[43mfind_motifs_regex\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43mr\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+(\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43md+)/(\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43md+)\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+finding\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+([A-Za-z0-9,]+)\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+motifs\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 627\u001b[39m \u001b[43m \u001b[49m\u001b[43mcontigs_progress_regex\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43mr\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+(\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43md+)/(\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43md+)\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+contigs\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 628\u001b[39m \u001b[43m \u001b[49m\u001b[43msingle_contig_regex\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43mr\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+(\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43md+)/(\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43md+)\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+processing\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43ms+([\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43mw]+)[^\u001b[39;49m\u001b[33;43m\\\u001b[39;49m\u001b[33;43mw]\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 629\u001b[39m \u001b[43m \u001b[49m\u001b[43mbuffer_size\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m50\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 630\u001b[39m \u001b[43m \u001b[49m\u001b[43mprogress_granularity\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m25\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 631\u001b[39m \u001b[43m \u001b[49m\u001b[43mdone_str\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mDone\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 632\u001b[39m \u001b[43m \u001b[49m\u001b[43merr_str\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mError\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 633\u001b[39m \u001b[43m \u001b[49m\u001b[43mexpect_done\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 634\u001b[39m \u001b[43m \u001b[49m\u001b[43mquiet\u001b[49m\u001b[43m=\u001b[49m\u001b[43mquiet\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 635\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/GitHub/dimelo-toolkit/dimelo/run_modkit.py:393\u001b[39m, in \u001b[36mrun_with_progress_bars\u001b[39m\u001b[34m(command_list, input_file, ref_genome, motifs, load_fasta_regex, find_motifs_regex, contigs_progress_regex, single_contig_regex, buffer_size, progress_granularity, done_str, err_str, expect_done, quiet)\u001b[39m\n\u001b[32m 390\u001b[39m \u001b[38;5;66;03m# Grab output bytes for as long as they're coming\u001b[39;00m\n\u001b[32m 391\u001b[39m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[32m 392\u001b[39m \u001b[38;5;66;03m# Wait for the process to be ready to provide bytes\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m393\u001b[39m ready, _, _ = select.select([master_fd], [], [], \u001b[32m0.1\u001b[39m)\n\u001b[32m 394\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m ready:\n\u001b[32m 395\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 396\u001b[39m \u001b[38;5;66;03m# Read a single byte\u001b[39;00m\n", + "\u001b[31mKeyboardInterrupt\u001b[39m: " + ] + } + ], + "source": [ + "pileup_file, pileup_regions = ensure_pileup_output(\n", + " input_file=ctcf_bam_file_updated,\n", + " output_name='ctcf_demo_pileup_on_target',\n", + " ref_genome=ref_genome_file,\n", + " output_directory=output_dir,\n", + " regions=[ctcf_target_regions,ctcf_off_target_regions],\n", + " motifs=['A,0','CG,0'],\n", + " # parsing can optionally specify mod codes.\n", + " # The default is Y or a for adenine and m or Z for cytosine, corresponding to methylation\n", + " # motifs=['A,0,Y','CG,0,Z'],\n", + " thresh=190,\n", + " window_size=1000,\n", + " # cores=1, # uncomment to reduce memory usage\n", + " # cleanup=False,\n", + " # log=True,\n", + " quiet=PARSE_QUIET,\n", + " modkit_executable=MODKIT_BINARY,\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pileup rows: 246104\n" + ] + } + ], + "source": [ + "import gzip\n", + "with gzip.open(pileup_file, \"rt\") as f:\n", + " n = sum(1 for _ in f)\n", + "print(\"pileup rows:\", n)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extract" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No specified number of cores requested. 10 available on machine, allocating all.\n", + "Modification threshold of 190.0 assumed to be for range 0-255. 190.0/255=0.7450980392156863 will be sent to modkit.\n", + "Threshold provided. The extract text will stay probability-valued and read_by_base_txt_to_hdf5 will binarize at write time.\n" + ] + } + ], + "source": [ + "extract_file, extract_regions = ensure_extract_output(\n", + " input_file=ctcf_bam_file_updated,\n", + " output_name='ctcf_demo_extract',\n", + " ref_genome=ref_genome_file,\n", + " output_directory=output_dir,\n", + " regions=[ctcf_target_regions,ctcf_off_target_regions],\n", + " motifs=['A,0','CG,0'],\n", + " # parsing can optionally specify mod codes.\n", + " # The default is Y or a for adenine and m or Z for cytosine, corresponding to methylation\n", + " # motifs=['A,0,Y','CG,0,Z'],\n", + " thresh=190,\n", + " window_size=2000,\n", + " # cores=1, # uncomment to reduce memory usage\n", + " # cleanup=False,\n", + " # log=True,\n", + " quiet=PARSE_QUIET,\n", + " modkit_executable=MODKIT_BINARY,\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plotting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot Enrichment Profile" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "from dimelo import plot_enrichment_profile" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3224e68446bd487c9f0afbac2866253c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/50 [00:00" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZIAAADZCAYAAAAKarbhAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAXxJJREFUeJztnQeUE1UXx+9Weu+9SEekdwQUkCZdUUBRLKiAoiB+ogIiAoqKBRFEBSwUQVGkiPTee++9984uy/K+83+Tl7xMJmXSs/t+58xJMplMJpOZd9/tUYwxRgqFQqFQeEm0tx9UKBQKhUIJEoVCoVD4jNJIFAqFQuETSpAoFAqFwieUIFEoFAqFTyhBolAoFIrgCpKff/6Z5syZY339zjvvUNasWalOnTp07Ngx345GoVAoFClfkAwbNozSpUvHn69Zs4ZGjx5NI0aMoJw5c9Jbb70ViGNUKBQKRRgTZTYhMX369LR3714qXLgw/e9//6MzZ87QL7/8Qrt27aKGDRvShQsXKDVw//59On36NGXKlImioqJCfTgKhULhMxAHN27coPz581N0tOd6RqzZL8qYMSNdunSJC5L58+dTnz59+Pq0adPSnTt3KLUAIVKoUKFQH4ZCoVD4nRMnTlDBggUDJ0iaNGlCL730ElWuXJn2799PLVq04OuhkRQtWpRSC9BExAnPnDlzqA9HoVAofOb69et8gizGt4AJEvhEPvjgAz6A/vnnn5QjRw6+ftOmTdSpUydKLQhzFoSIEiQKhSIlYdZcb9pHorBJ7ixZstC1a9eUIFEoFKl6XPMqj2TFihX0zDPP8JDfU6dO8XW//vorrVy50pvdKRQKhSKCMS1IYM5q2rQpDwHevHkzJSYm8vWQYAgNVigUCkUIuXWc6PgfRBdWB+0rTftIPv74Yxo7dix17dqVpk6dal1ft25d/p7CRnJyMiUlJalTolBEADExMRQbGxv54fwXVhKt7kKU51GiRovCU5Ds27eP6tev77AedrWrV6+SN877zz77jM6ePUsVK1akUaNGUY0aNQy3RWTYwIEDuWMfWfRffvklvfnmm6b3mZCQQH379uWCEBoVNKzvvvuO8uTJQ/7i5s2bdPLkSR6XrVAoIgPkyeXLl4/i4+MpYkm2pGHEpA/aV5oWJHnz5qWDBw86hPrCP1K8eHFT+/r99995Hgo0nJo1a9JXX33FB3UIq9y5cztsf/v2bf4dTz75pNMsek/2ic+izMv06dO5AOzVqxe1b9+eVq1aRf7SRCBEcFHmypUr8mc4CkUKBxO+u3fv8oTqI0eOUMmSJU0l5IUV9yyCJFarQBIUmEmGDRvGypUrx9auXcsyZcrEVqxYwX777TeWK1cu9s0335jaV40aNVjPnj2tr5OTk1n+/PnZ8OHD3X62SJEi7MsvvzS9z6tXr7K4uDg2ffp06zZ79uyB2sDWrFnj8bFfu3aNfwaPeu7cucN2797Nbt++7fH+FApF6Ll16xa/d3EPRyy7PmVsEjG2uqvpj7oa11xhWiN59913eXmQRo0acQ0BZq40adLQ22+/Ta+//rrH+4H0h4mqf//+1nWYATRu3JjX8PIGT/aJ9+G3wDpBmTJleKY+tqlVq5bhvmECE4EFIkzOHUoTUSgii4jVQgxNW8HTSEyfNQyO77//Pl2+fJl27txJa9eu5ergkCFDTO3n4sWL3ASk90vgNXwb3uDJPvEI+ycqFpv53uHDh3MzmFhUeRSFQhGWJAffR+K1+MVgXK5cOT6bX7hwIe3Zs4dSMtByEOIsFmT2KxQKRdgRAh+JaUHSsWNH+vbbb/lzFGmsXr06X/fQQw/xHBNPQdl5hNudO3fObj1ew6HvDZ7sE48wgekjzNx9L8x3ohxKuJdFWbp0KdccxW+cOHGigwY2btw4rlVBlUdAwocffkiVKlUK+LEhSAPfF2oGDBhA3bt3D4v/B/z9999UokQJfv0aRSKGmueff57atm3r0baoAu7rbzh69Cg/R1u3biV/8fTTT9MXX3xBKZ6zC7TH6DTB+06zzpg8efKwrVu38ueTJk1iJUqU4A6q7777jlWqVMnUvuAY79Wrl51jvECBAj47213tUzjb//jjD+s2e/fuDYiz3chh99xzz/HPvfLKKw7v9ejRg7+HbXxhyZIlfD9Xrlzhr+H0P3funN2x4xyMGjWKnT59mv9/N27cYBcvXmT+YsKECSxLliwO68+fP8+/L5ScOXOGB4ocPXo04N/VoEED1rt3b7t1iYmJ/Bju379vXZc7d272v//9j506dYpdv36dhRu4Jtu0aePRtpcuXfL5Nxw5coRfw1u2bGH+YseOHSxbtmx8DHCGq3vXKUm3GJP+y5AzOVZztm98M2jOdtMaCcw62bNn58/nzZtHHTp04GGuLVu2pAMHDpjaF8J0f/jhB951Eaax1157jW7dukXdunXj7yPpUXacQ5PADAULnqM8C54jHNnTfcK/8eKLL/LtlixZwp3veK927dpOHe3+BpoAcljksvvIbZk8eTJ3+vsbVCGQw6mPHz/OAw7wnyFmHv8f2gOIApyBBOHQ+L5Q8uOPP/LyPkWKFAnJ98MsDO1XBGMg5+j8+fM8TB19IMxWXg03MD6E42948MEH6YEHHqDffvvNfzu9spVoWgaiKWHkpI/Poj3mqhe87zQrsUqWLMl+//13dvPmTR7yu2jRIr4eWkqOHDnM7o7PigsXLszi4+O5NoGwYnk2J8/OxSxFv2A7T/cJMNvA7B+zk/Tp07N27drxGaIZfNFIMLN78MEHedi0ANrdQw89xN+Tf3NCQgJ7/fXX+blOkyYNq1u3Llu/fr3dPufMmcP/l7Rp07KGDRtybUDWSGTtQLwnLzivgwYNYhUrVrTb708//cRDvXEe8+bNaxdW/cUXX/DfgPNXsGBB9tprr3GtRtaI5AX7N9Ikjx07xlq3bs0yZMjAtYQnn3ySnT171vq+OK5ffvmFfzZz5szsqaeespvxIpQbx4Lfnz17dtaoUSN+fTqjfPny7Ntvv7Vbh2sImiy0h6xZs3INYdy4cXw/zz//PMuYMSN74IEH2Ny5c+0+t3TpUla9enXrOYJWkZSUZP2vjc61rDEanSus8wRxLQ0dOpQfL/7jwYMH8+9/++23+fUNbXz8+PF2n9u+fTt75JFHrOfr5Zdftv534N69e+ytt97i+8P7/fr1Y127dvVYI9FrYfjfcIzdunXj57FQoULs+++/t/vMunXruEUD13jVqlXZjBkzHDQSaBTNmjXj1wp+7zPPPMMuXLjA38M5g5a9fPly6/affvopv2/k6wnnp169ev7TSBY11mb/WK7tY2HBzBLa8ZxbETSNxLQgGT16NIuNjeU3G25wmI4AckgwiKUWfBUkI0eO5AOeAM8xwOoFyRtvvMHzYDCA7dq1i7+HAQLmA3D8+HF+8/Xp04eb6CCcYH50Jkhg5lq4cCF/HwIJAhQDh16QwFSJgearr75i+/bt49vKAgDPFy9ezAdGTCZKly7NhYkw3eBzGPSxfyxioJIFCa4dDB64sTdu3MgFPgYReWKA48Lg0759ez6QYKDAgP3ee+/x92Gaw/WI84ljwSCJa1QeGGVw3qKiohwmF/hOCLIhQ4aw/fv388eYmBjWvHlzLlCwDr8PkyVhmjt58iQXpJiUIBfpr7/+Yjlz5rQKTZhQateuzQdqcR5wrmVBgnOF84vXf/75J98G6zwB1wKOGQIe/z0EP/bTtGlTPnCL34EB9sSJE/wzEIz58uWznk/8d8WKFbO75jAA4xrD8eA6fvHFF/n3+CJIIJDwvxw4cICbmaOjo/kxA/xXGPA7d+7Mdu7cyWbNmsWKFy9uJ0hwrrBN//79+bnevHkza9KkCReIAgg8fBfOO96HcJ85c6bdsf377798PSZofhEkQohMIsY29WFhwYwC2vFc2hi+ggTgpseMQb5ZZ8+ezVauXMlSC74KEvgKIABgp8eCQRuzK1mQ4KbHIABtRXD37l0uWEaMGMFf48aC1iCDWbEzQQJwc4rZsUAvSPAd77//vsfnA1qBrJE685HIgmT+/Pl8sIYwFEBYCiEnjguDtayBYMCoWbMmf75p0ya+vaf+DvHb5e8Ug588U8WAj5nvs88+a12HQV72pUGYQYDKvg4MlhB8YoJl5CPR+7DwaEYTEeA6wfkU3wVwPA8//LDD75gyZQp/DaEIISFrbNBoMbCLmTsEjbi+ADQcaJ2+CBJoDwKcL2gUY8aM4a+hneDake8XvCcLEgjExx57zO57IByxDQQxgADGxKRjx478noAA17Nt2zaX14spQXI/2V6QTPJqOPU/07Nrx3J1V/gmJIKqVavyRQb2doU5XwHOGSKqINDxHFFnMocOHeK+DBTEFMTFxfG6YSLcGo8oBSMDf48vwF6PVsJIOnUGQr6RW7N3716enHnv3j3u50GSqqc+EBw7/EVyTg5CyhFhhvcQESgivWSbO/w6OEaAWmo4zgoVKnAfw2OPPUZPPPEEZcuWzfA7hV8KraH1IPJQgOgp+IywX4HITxLfjWPEuZYTT/FfiTprgfB36SlfvrxdEh2OEb4A/e+QjxnnLEOGDHbHjCRjlBHCeTlz5ozdNYVChtWqVfOpbpx8bnG+4COSjwnvy/+J/hretm0b92nCl6cH90mpUqW472nSpEl8X/B/oRafkb8Q4Dr1mcTL9q/zNaWwIDkh6AmJXgkS3CT//PMPd9rC6S0zcuRIfx1biueFF17gdb5EoclwQdxsrkIzH3/8cR7IMHToUO5cRa01BDHgevC3Mx3CUwYDEQY+MVAuWLCAVq9eTfPnz+cFOpEwu27dOipWrJjDvoSwvnLlChfm7r5HXicEhvjucMDdMYt1oT5mX48JwrlVq1b06aefOryHiYUA1wFAwjQWWWCK9UD/33tF4kX717FhEGBwPzkyMtsXLVpEpUuXpjFjxvCYbMwSJkyYQOPHj/drzHdqoFmzZnzghdaB2bQeRJhgliUXk8S2GzZs4DN3ULZsWVq/fr3d51BtwBcw+4cWgP/aCES6YRDA/49IN8wGocHI4LhRZcAVOHYkdsrJnbt37+b5FeL3eQIGJcyqBw8eTFu2bOHf/ddffxlui3OKHCB8j6/g+FFWR56p47/C+StYsKDH5yGY4Jgxu0cko3zM0GpwXyOqEQMzBLEA2ib+80Ae0/bt27lG6+warlKlCq/+jesS+TbyIoQFNBMUZEXUJjSq5557zkFYoRoH/hu99u8ViRfsX9+3n1QHnev7iKZhEsfCX5AgHBd1tXbs2MFVUSQhYiBo0KABr8qr8BzMpqHWY1DDcz24QTDr79evHw+1xnYvv/wyV8sx+wevvvoqD7vGNjBNIIQY5jJfQYIiBMU333zD948mZpjtA9y8EGh4ffjwYd4dE9WWZXDDYxYJYYTSNUamBNQ7g+moS5cufP8QiAj5xrUEU4onYMBDQ7WNGzdyDXnGjBm8ZA8GJyNE7TV/dPPs0aMHv/ZRYw4mvpkzZ9KgQYN4aLkwN+E84BihxeE8hFozwLnGfYtBFoMqJoI4/meffdZquuvduzd98sknPEkSvwu/05sWEZ7SuXNnPhnAtY1rfO7cufT555/bbdOzZ0+uTXTq1IlPpCA0/vvvPx66D0GNBV1bMSHDOkxuIZz0CYjo7grzp19Iumb/+n6Iew9t6GEvzGIczbdhI0gw8OFmF7ZT2Jxht/zoo48M1U6Fa9xlyeOGRq4ObnTMypAzgxtI+ABgh4cwx00P2zcGdH90qsRAgwx09GmBHR6mLJEnhO+BCRP/N+zxsEvDXyKDPA0IuaeeeoqbEUaMGOHwHRg8MPjit6D4JwZ4tAlAKwBPwblbvnw5tWjRgmtGH3zwAR88mjdv7vQzL730Es/j8XVQL1CgAB/0IABxTvB7IeBxDAJMujBJgIaF8wBh5yk4P/6YFMjA7IjrB4MyfFDwJ8HHJKpVAPTqwfWGawC+CmhY7dq1o0CB8WPWrFl8clq5cmVumtSPJcivgeYEgQFBgAkIsufhT4PQhokVPYq+//57vj20KlRvwH8BDQxA48F9AoHlF4QJKVw0krtXbM+jYomig9hTxaxXH6GliGoAZcuWtYbXIY8E0SGpBW+jthShB1FDyP2YPHkyC1cOHz7Mw5oRwqvwDwhpR8iwK0zdu4d+to/YWlA/dH9V4mX7Y5nmGDEZVlFbsInDLADTAWaBmL1gJgGTQrAywxUKX8BMH7NVXLfhCjQd1AJDgyWF/xz+wjwbEI0kOYQaySJbWwxOrGN0WyAxLUhg0oDtG8C5iecwReCCVxFbikgBBSqDUaTSW+ATCEdgmnMVCAEfRzDCnr0BJk2/IgRJXBbNX3J9N0w8mKlQULl1jOjKZvt1UY4+17ASJHI7XTiD9U5WhUKRcoGvwlV0Jt5PFUBgJFsCSOIya4Ik6TrR/lFEpd8I7rGcX+G4LtwFiQBRMiIpDjMUfYKiQqFIeSDABlF7qZ6lzYnO/GcTJIJNvYMvSPQmNhAVHd6CBMmICMFDBIXocYHQQETpIBJGxM8rFApFiuTaHpsQ0QuSUHDPIEs/2j4BNNBEe2NnRA4BtBGRPYrnCKX0uw1SoVAowo2NOv9VnKVse6i4n+i4rkDr8BYky5Yt41ntyIIV4DmiIRDPr1AoFCmaZN3AHedCI9k1nOi/2kQH/OhLvnmE6OQ/mp8GMKN8KO/rogVFkKDAHjQSPUgUSjWONoVCkXqJ1xUETeOkIdyds0Tb3iO6tJZow2taCRN/8E9xouVtbC11hY+kRHeiGEudu9wNKKwFyWeffcZLKsDZLsBzlFXQlzVQKBSKFMfdy47ht0Ygiktml+8VJ+w4NYvowhqinR/ZerS32k/UYBZRfueVHULmbEcJC7lUNgq+oSgaIjhEUTc8RzXbtm3bBu5oFQqFItwESd7GRKfnOkZL6aOpjvxCVPtnzTR2+yRRpgfMf7dcyn//t0RXt9tex6QhSl9AW4KMR4IENZcUCoVrUAm4Xr16vKrznDlzXG6LisEo8IhKtYh6RPVi+B5VJnuYA3+E3kRVqhfRzaNE+78hylLedVguhMiSpkTnlxE9upAor/OeP4boC0Mm3bA9D2ZtLW8ECYq3KRQK1/z000/c7ItHlNV35TNEEUtUVv75559535QBAwbwyrXIDDdquqUIE67tMg61LdRWEyRMahkgawsCaCIQImDvV+YFiV44XdkiHUcaChVeJyQqFIEGWrw/Gtl5A3pzmal0IUoFwV949uxZXrX3vffec6qNQMtHZdo2bdrwdb/88gsv447qtE8//bS/fobC3+ibWekzyZkkSNa/4rjdLCmZ855WasoUIpveiHDXSBQKj1TuO6eJotNq5bTT5fU5KQpCxKCzalBAOTldcz2XTJs2jcqUKcND4dEXAyXO0btH9i0Kjhw5woUNyuYL0FAKfkeYx5QgCWP0hRlz1dMJEhOtCcRnTX2/gbksDARJcPPoFSkXqOwJF4hunyBKOOc8kiWFAnMWBAiAj+TatWs858oICBEgGkkJ8Fq8pwhTRM+RtLmJincjqvyZ/VB644Dn1/59W0dIj7nnQpDA2R4ilEYSyrIG0UFuPhNI9DMlb9R2A/OSpdB00DHTdh6dKdHcSrT3RQQjGnpBuDRs2DBwB6kIvq11j6VBW6bSRLXGGxdJnFmUqDPzTShEmGkrLDSS0aNH85akcDJCvdf3INczffp0bkbA9uiUht4NMjAnGC3IgRHg+/TvoxthUEhOILq2m+iKgTMupWBGxXcCrEIwL4ViMeMfgcBACDyc6xAiWBCBhc6V0Ez05M2blz+eO3fObj1ei/cUYcilDUQXVjqprnvf/uXmvkTp8nlvpvLmM4mXKKw1kvbt23u8QzS4MgMclOhxjXL0ECJwQiJ6BbO83LlzO2y/evVqXjQSrV3R/hU9ypG7gp7faPsKzpw5Y/eZf//9l7dARctaGbQHlttuoqVoUJBn6/AtBLnAWqQIkkgAAgSOcrT31fcCx3U5ZcoU3oJXBlFaEBjoZy96oly/fp33dn/ttdeCevwKL3u0syTXTvi9I23PKwwm2vMZUbr8RDf2e6ZdOGPf1xSOeKSRwBEoFvTIxg0gZ7Zv2rSJr8P7ZkEzLAzm3bp14+XoIVDQV3r8eEltlPj666+5Dbpfv368S+OQIUN4L3O55zRuUnlBX/BHHnnErpeKEBzyduivEnBun9ZKJ7iq3BmJyIlSqYjZs2fTlStX+EQFExl5wcQF2ooeaL9wxn/88cf0zz//8E6NXbt25RqNSugNY+QJX6IuKTGL82ZfVKAV0ZPXiKp84btGcsLFRD1DEQprQTJhwgTrAodgx44deeQJtA8shw8f5pEmOXPmNPXld+/e5UJIjl6Jjo7mrxG9YgTWy9sDaDDOtoe5AMlhuNH1wJSVI0cOqly5Mjd7YXbpjMTERD5rlBfT3L+nRTbBtOWsjEKkIoc9piIgKHA9Gk2iIEgw4dq+fTs3pX744YfW99555x2ec4J2utWrV+fhw/PmzVM5JJGiZd88aP9eRvtJqh3pC2kZ7zFpffeROANJkYU7UsQ426EpoGd7TIzNRojnME+hJ4nsh3DHxYsXebFHo+iVvXv3Gn4GUS1mol2Q8AXNQ2+ee+ONN7gmkz17dm4uQ6gmTGLO2gXDlIbWwn4fbBHhlD5/0Dua+R1mIIRD0XY0yMyaNcvpezVq1OA5I7dv3+YTGtnxDq0EplUsighBvn9j0nn+ubSWCTZC42XOzvfTgRFRNT/2og+Gsx2zdqNBHuvQkyTcgODr0qWLw0wPgg839kMPPcRt2LBxoxQ+NA8jIGjgOBXLiRMn/Ddrv+vokI24mVoYlLIOV5YsWUKPPvqoiuCKdOT7t5CB37j8+64/H2sgfC5tNPH99+3rewkqfUqhxrRGAl8GzESHDh3iMy4AJyHMRHjPDDCFQZsxE72C9Z5uv2LFCu60h0PfHXD0Q0gePXrUrteKIE2aNHzxCadO9fATwH4RkFwjCfbBhB8tW7bkiyLCkQfyqgYawEMfEe0a6vzzeo1ElDjJUc18kE7lz4n+1QI1KHfoQ8xNayQoFQ/7Lmbw9evX5wvMQXB+mzFrgfj4eN7rHY56AbQavK5du7bhZ7Be3h4sWLDAcHvYr7H/ihUruj2WrVu3cv+MUaSY34AgMTJhoeDbjYOR67B26h+JcAGpUBhd5zlqEMUZlFyIiiaqM9l+XQkpYi9ea01ux8XVRJct9bJWdSL6r5Zj9ryI7pwu+eFiM7jvhxLOGgkGWwgSLMLhjEgub4GJCUUhq1WrxjUchP+iTL3QbhDNUqBAAe6jAOh70qBBAy7IMMtDn3g4NMeNG2e3Xxwb8k2wnR445qFFIZIL/hO8fuutt3hmMkrmBxQIEqOB9+5VLYIrLgiRY/7mjn24tV1wQUoMbVakTqz3rYv5d9FOWhn3hZbGUoWllIM0uRy3PzxRW55KJDo2VVt3bglR/qb22y1v67wrY/qCFGq8ymyHCWjp0qXcvNW5c2e+DtVOIVAymiyOhAzgCxcu0MCBA7nDHHH1iF4RDvXjx49z4SWAQx+5Iyh4h6J4KLuNQncih0QAAQNHJ3JO9MBEhfcRRQOfCOL6IUgg1AIOZhJ3DWYcweIuhD8jis/i/GZBVBk6rXnqKHeWCHX7GFGmkpEfSKBQyBp2tJvrOVoyYcVK42FMPNHDf2n5KGuft//MtR2uw4JFvxPRjx0lWhrMJkqTM6SlUQRRDKOtCY4dO8bzODDAYxDev38/z8+ApoDXyANJDUDjQcgnHO96jSwhIYGHR0NAOZQETzhPdOu48U4Rix5rojaHWaAyi9LWWR90DEcE1/cSJd0kyljMc5XZlcMQQiR7ZS8PWKEILi7v3ePTiVZ2JMpdn6ixcR01qxlqarymNbQ5ZmzSWtHBeU7Iw386OvNnFiO6dVR7/jQ0/Zigj2t+9ZFAYMAMhSSsdOlsUQjt2rVz8F0oDIhzlbQZFZyCc+DeLeNtIEQACjD6A2g4ker7CTLHjiH6UZ2u8He2uxk2o+OInrhC1OqAsRABee2rINhhdG9GWYxHdacGTIgE1bSFSCjkXcBRLoOEq1OnTvnz2FIm4oIAyB9BpruV+5q2AmHjzPTkC8m3TJQw8TDB0BMhge9S5i23JfMvWGQ3ClW6q9Zz+rS2lCoFH6Vnf5XCR0TUlCfXcrwTASLIUNj5e2u6EhV71rgES1b3gUOhwLRGgqgqJBHqOXnyZPBqVUUymE1kLq0tafM5XiwwfaEUdSCQhYe7THSPa2VJggR226wVHE1mqTTr3QxHjtieO0llspKUpAkR4E06k8JL1r2kPd694vspTGPgeJc5u1Bry/tfTaINPYiSrlo+Z656SNgKEhSmk3u4I0MX5R3Qf7pFixb+Pr6USVwmbYEzGyGDRv2YA2EOkvfpL0Ei7zNdAc3xJzsYOSoM2BXI471zx15QOOPkSaJt22yv8bkwzANOecjlTK5s9n1/ad2kGZyaS3RuKdGl9UQHxlhWRhHFBziqNFiCBOG0q1at4gUW4ZhC1JYwa336aegzLCOOzGWMzV5GJUd8JsAaiRCK+n2nkkrAiDpE/SwEnyAysFChQtSqVSs73+GWLVt4pGK+fPn4NkWKFKGWLR+n5ctn8ShDYBTUh/fef38gVaqUj+rVS0c9ejSm48c1zTXBi/5ICj/0ag+kRpJ0TZts2n0me1j6R7wSJAULFqRt27bx0FuEzKLgIbLacYMENJkvpYIoLRG+Jw/AgRh87TQSd/v3QpA4+2wqMG2hIgKSXxcvXswTc1HRF2HsyFXq2bMn3wZVqGvVqsU1eNSA27NnD9+mZct2NHbsB3TzplYqR/hKZEaMGEGjR39D/fuPpQkT1lG6dBno9debUmJiQsj62qcq5JBco6x2sxiVS5E5PN5+Yhk+7aP8l0eCxj2irajCH0QFR5DIAzz8MRmLOt/UU9Oa2I6b6Sy/Iz6Hff0wb38L9u1NzwZ/YCaPhoh69OjBzbxoyia3Iyhfvjy98MILPMkWpYWQRKvv2ZM7d1mqVetFq0YC4IbE1yOFCuthTu7d+wNq0KANf3/w4F+oadM8tGzZ31S06NN++ckKDyIe0VOkdC//nKqSPYhOzSa67SQd4M5J+9eJfoqkDJUgQc+E5s2bU1xcHH/uitatW/vr2FLfRSqH5wai4KGRZoBwXyQOotS1nC3r+U4tj9KgCzsuTHaIe+cl8738LRAi08wluPqNjjfty1C44PLly1yzGDp0qGFPm6xZs/I2vJcuXeIVIfSIdsK5c0dZtZEtW1BCiAh5tkePHuFms9q1tUJ9iGkpVSoLValSk7ZvX0OJiUqQBBzhv0xrX3ncJ6qP1hYIk/2jNXM2nOyCpBsUKXgkSNBsBxcyTFeuGu9gRmYU0aVwg5ixy31KAqGR2AkqCzcPaTfJ9f2eF4+TsR6nJEgwlUYtIsTT4zelcB/JwYMHudaA9s/OQOIukAuCbtiwgZu+hLP8p5+mUtWqj5NodQNfya1bmu8FZMmiDWJZs2ohv3nz5qFLl87aOeoVAULcO4Eo+VPgcW0BF9cRza+lPT/ys/12NX+kiBYkcnn4cCwVH/Fg5qtPQkIIMLLP/XXhYjAXyYYChDG6GuQ96icimbYcEOvue29egmYQCvDdHmKyOIQVtDD4+++tdOMG2lmXpOjoe7xfvNwzTY7ggmCJi0OJH8shWvyucLbjtpQqCSkCpZFE2+fP+Z2cNW3PUXNLJkxDf4FHlx6aP6EJFYC99waufEVgs91hhropJRcEQhu5ccjR3CULLo+6NxqYthyiuLw0bUE4QciGYjHhH0G9N2jjzpqxiW0A2hoIELWVNWsJKlSohOU1UXqd/ILwEC0Szp7V2ieIXOALF85Rzpx5+ekNZfm2VEEgNRJP8adZLRSCBC1xRaVfRJsg7Ffhz3/BSUifP9rwYpSBdnN1p/tt78PDG2Mu2kp2tjsVJClbi8VEC+2eR48ezZ3qeq5evcrzr7CdPkReqjJk9X/ESnYCmK1Q9wnCZM2aRVZBgvsRFayrVNHaJ6hbMoVoJCBvE3IAkWI5LSavSDVtodcHfCMIb4Qajza1cp0tfUdChUkcwvz8yL0bnndglBvncDwRAMzFnESsS9mCBECI1K1bl7dCQPtcmK1QJRu9csaMGcNDfX/88UeeQ4LILdxD0FJ27bpJK1fO4/tAkzcIETjYL19G5WvNtAVt57XX3qTPP/+YihcvSWnSFKMBAwZQ/vz5qU4dzWd58CBRNS9cXAoPYRZBEhUEjaTWeKK/C9leo4K2vyLFQqmR/PbbbzxrHfHvuKhRGRJFG40WhRcEsg6VmRwO/bboJ+L+Q5bHKOfmAJR9SeEgCXHz5s3ced63b1/e1qBJkyY8GRGCRBQ2RZ269OnT8z47cLx37/4obdiwmCZNmkqPP645XCFMqlUrSuPGfUj3LH9Bz57vUMeOr9OQId2pevXq/F5EpFimTGk9yohXOPMb3vLM9BpM01ZaXbfXQJVM8iMeTYXRGwRJhwBq9q+//ko5coS+K1eKwZVj1yOHtwvuaBE/ngsS6aa6c5oonXHLY7vjA0bHCG1IX/olBYNs9W+//ZYvzkDlbDRcE+aonTs1J3nlyrZTePv2be7/qFq1oVWQ3LsXRa+++hH17/8RPfCAbX9wsm+2VOzA/uCMV3gIfJAIOMlQyL3/IZimregAWigChOk4D9TqV0LEz6DhjTN8zQrXR4O5aoKDGZqp7He+keXRQJCEQee2cEZoEPB5yHJ4yZIl1LDho1yQYBv8JUKg6AUFhJColequ2KNChyi+6InGHEpne6PFFO54JPq++eYb6t69O2/0gueugO1X4Ud4zS0/zlDis2tRSegR704jsZaAj3Z/gxltE5PBvaBMxYhIK71wgA+lWbOWPCkRQgRahxA6siNeID4vhI3CA+QJkyc+SlHGPViCpNZEWxfFbOHfGM6jEerLL7+kLl26cEGC586A/0QJEj/jq0YCDQTlqAUY8J1lbBsJEvhJXAkC9Jp3hphmq8ZWhogAL11rH+1vi9G0DSFEnGkk8jrlIzGBnPzriblqzxfB9VcUf04r7Hg/wX1vk0gRJDBnGT1XBIF7tz0u1WEIzy6XBAmfUTnzuTDHQR8zMTTg0gMBc+eMrUqx4axOfI/qkGiESDzU547IAgLmKmx39apzjUSsUxqJCeTq2sLE5WrCI7bPWYeCRoHIacuhcmEDiKmM58ylNO1B3zkN5aR9Owj71whfhDMPJakdN3Yc9OFwN4reun2SKOGcJugASqI4EACNBH0h9Bn6EQgGfZH7gZInRggBgTBggchql1EaiR80fZ5rtZ1YspPMThRrBEWe8ubbUjweaSR9+vTxeIcjR46k1A7yAUQip7N8GwdQMBHdBTHoot2uv3JM9M52YePNWJwo8bL9e3zAF1FY0TZnO8xX6I0gO+rlstrajh2/2+pBdiJIsH/kuOC3e9pnQfSF4J0YXQQOhDlCOEAwGAkHZ9qH0eWkfCR+ECSWXKvb17TqAShQa/++RSUM08ZSocajUQq9RmQQL49kK1GADgXpMHgiYVGhldlHrsCFCxf4BRlttghScjyRdWZ0lyjWy0oCfKDWrUu8R3TPsr+4IkS3jklvJhHdtQz6wkDPD+Go5ieB1iRtyhdBHLQW3XHiN+D7o5hx6vUVS7Y9bMAZPIzwEr/n1jWi+MhsVo7TioRDIWs9zUovVsw4MkvUScV7yIT3JVo81ZCYYHdv4Kq/fZfo/OXTlDVvKetk0Hodi3YGEeCvCFtBgnBEWeNAb3aUSsmWTZPOSETs1q0bPfzww4E70ggCQQfIKYA/6dgxeaD2EBarmXC47fYiUbo73sWvo8SKvr/01fT2JqfblmgUzkV7zUWf/5E+1jZKwawlOyzToC7WVceSK3cs+7xi4Fu7Jb7vIlEGJ7km3BdzSnuOWP8Ey2fik7X8mwiMuYdT3FK6jgoUsO/XLgOhILYTvhQjIYG/UWwHLcZIk1Ho/4Rr9oEiPDzuLmW9s5byli5JdHGtrSSJ6JfOT3BkTl4CTRQzWbq0QIECNH/+fN6wR2bnzp28ntDp06e9Ki+BrnIol12xYkUaNWoULzXhDCR0oUQEutKhzATqF8n94p9//nku6GRQCwmZwHIPCbRFnTVrFtcYOnToQF9//TVlzOhZ/wvUOsqSJQvP8s+Mmt4GoFIyzFtecX4l0fqXbK9b7jE/1dz4BtHZ+drzOlM0v0iGIvbbJF4hOjWTaLeWcKoRRZSlnGN70UcX2xzv67oTXVhue6/6WKI8DXX7vky0wOKcbLnbMUR4dhn3v+/A90T7nEQK4qZuvMx9t7kwY9kyoldeIcqfn2ixmxQBzOFOnCDq0sVW7deIpk2JMGfBZV9TKiCrsCQeLm1OVKQLUYUB2inZ+xXRwbG208MYxd27SDHQqoVJt8lqossbiGIzEa17QTPBPumjzzLM8WRcMyLWmy+CyUYP1nlTFfj333/nPpixY8dSzZo1eSc4DPqokmrUuhclJjp16kTDhw/nJSUmT57M64DB3IayFIJmzZrRhAkT7CqtyiCc+cyZM7wWUlJSEteokCuD/fkLCCiETHtFxmxEdyVtJibRnFoNbeH4D7bX+bXifg6kzUd0p5T9d+GGyZCD6IJOm5r3AFG9P4gKd4BqY/+ZNPFE+t8and62TXyMvU8Dmobd5y1BAHruX7TfTgYyOvoWUdrIsltv2qQN+lWqOJ4yPc2be7ZPlJ/HPlFcuEEDSr0guXBVZ6IHXiISnSNnlNMeDwwjqvyBNvG4d8r5dSXY2JPoimTWj1NmLb9FbaFeEAZdtAs9efIkX/7880/eRrR9+/Zmd8dNZS+//DLfZ7ly5bhAgX/BWfFHaA0QEv369aOyZcvSkCFDqEqVKg5lKSA4UDFVLMIMB0SvbBTRg/CqV68e14KmTp3qlUYVEPS9B1zlaxghZ+tWHO56W73TGoKknGMnP87KJ4yDAGIMRkQ5eUtfxn73p+7L3PP1btK1PaoHFl5s3Kg9oiyKv7BUqacD4V+WKbBsH0h0bhHR6k7G7ycgZJ0R7TcoY6PXmGUhAuIN2j0ovBMkGOjRdrdz585UpEgRvuA5BvfvvvvO1L5g9tm0aRM1btzYdkDR0fz1mjVrDD+D9fL2ABqMfvulS5dyjQYBAa+99hpvcyrvA+1PUfdIgH3iu1Ga24jExESujclLQEEykoze12EmTr78u663RQSXQwTZg0QFWjn/jGzWArEGJkG5UuqSpvbvbf/AM4HhTMD4K2EzBAifiBIkASDRdp9bSS+F1J+eRzRFGvayVzfezgilkfhPkEBbgMDAwIxoLizwN2CdUb9qV6BZFlrzoiikDF6L9qJ6sN7d9hBqv/zyC6+8Cv/JsmXLuPATbYBF22B9pBX6RTj7XpjSYDsUS6FCUpnnQBCry1KTHX6eAEe3s6ZZetLlI8rziO11hqKuB3HM6PSFJo36vcsaycU19sIwWqcFOYvfd3YM4qaWBWaECRJEYfkLpZGQ8xIm8rUJc5VM0U5EFYcR1f9bC3F3hcglUfgvIRFCAwMvFrMCJNA8/fTT1Lp1a6pQoQL3n8yePZv3x4aW4i39+/fnDiixnIAHNNDU/s13jcTTEvW5JUEiMuHljHh9bopegzC6CfXO8/2jbc8Ld7R/79gUc4JE+FMiTCOBELl2TXOcB0KQHDpki9qOGJDcurAh0YkZvu9LbgYnzJ76XCq9SbZ8f6KCbdz3Gomway2sBQkikdC4B7NyYdqCmQi+CrP93HPmzMnjtc+d05KABHgt2ovqwXoz24teEfiug+j+Y9nH+fP2FT+RFwPNytl+4HNBFIO8BJxiXWxRVif/8U6QeBoeK28nwh5lYVGkk33oZJIusAKRLe7YPoDo8C/GfpnNb9men19OtP41LUnMmTATAjLCfCQbNmiPsKo6K43iDYUL20qq7NlDkcXy9kTnlxGtQBCHj8jX8SFLsInIATHcXgqrd1eQMY1qneE3QfL+++9zxzb6kwjT1rBhw7izGiG5ZoiPj+dJjDBBCSCM8BpdGY3Aenl7gMgrZ9sDBATAFIfcDrEPtD+Ff0awePFi/t1wvocVImHwiH04s1vE7MnTzHjcyIJSr2uP8iBed7LNRIAZpD6z3dMKv2ufc9y3AO2AJ0cRLWyghWYeGONcIxG/K8JmiSLgEfkj/gS5IyKN648/KLJAiG0ggLkUEw1XGknWirbn7nK1yr/nv2NL7YIE+RmIdoIDG+1EsfTo0YN++OEHmjhxoukDQOgvPov9IpoK+0Xfa0RxAXSSg1lJ0Lt3bx5x9cUXX9DevXvpww8/pI0bN1KvXlorSnSOQ0TX2rVreZ4JhE6bNm2oRIkS3CkPEO0FPwqixdavX0+rVq3in4dJDO1Lw4qSPWzPT/zt+efumzRtyVFXYuaV7KS8it5RbpZLG7WqpvqSE3Mr2G+HmaQQJFWkXBL8JvG7IsxHIhIHc+qC8vyBCJpcv54iB7PRiO6QJyh7PiOaGmdrIZ1PCvgo/wHRw38S5ajmXiOBAOlwybEOnsJ7QQLzT5kyUiKZBazDe2ZBD+vPP/+cBg4cSJUqVaKtW7dyQSEc6sePH+f5HoI6derwXI9x48bx5MU//viD/v77b2sOCUxl27dv5z6SUqVK8bBkaD0rVqywyyWZNGkSP+ZGjRrxZEaEAGOfYUe+x2zPV7QLnEbyQHdH80B6XfKi2NfZhbZ19aYRtTZZEXrPCFtWfCU5EdKgd4oQJHIOTYlXJUESWRpJIAWJ6Jw4dy66LFJ4g2CQc0uJjkp5WzE+JpaeW0Z0xpZ0bK2IIK6lDJJTKlddokK6dAVnggTmZcMipwqvExIxeMO0pW9whXV4zxugDQiNQo+Rg/zJJ5/kixEokvjff/+5/U4ECfgz+TBg6KObPMXaGtTDvzhbJcd1NccRbX2PqGwf4xsNEWGFjf8HK5gFntH9H1kqEO0Y6H7wuLSB6PRc7TkcoS12EB35VQtnFhn7EdbGV0ShB0KQVJciWRH/giBFs2XegsbhCUTrX7ZfB3MpSpNgwGdJ5jtsLtJVVpCBli1rv4atGZycLF8FXCrAtCAZMWIE7+C2cOFCq18CeRmIYpqLqZDCv+Rt7F0Pd5FM5aq7oQxKnzTfah8qiZlY3Um21/qoluIvuN9v3d+1kMuj0n6QMCab1OpOJVplyUKWOTbZ3syF3JbKlkTGtLm10t+HfiLKHTk13vbu1R5zBMBvi30+/jjR7NnaaxR2ePFFCk+2Ocltml/bFt7d+qD/HNzxekFiEOkgazAySpC4xfR8pUGDBrzaLzLc4bDGgox2lDRRRRsDADSKJ66Y708ifAeW8tgeka0iUUYXMal67aZQW/f7RDZwrZ+JKn+u5avoHfvRaR2z+GVbtqCITtAUfdYWhHDHPoovXIG5SRTSthTO9jv/SMF9umIP4YW7wRl5U/rMcr0vZNsAohuHPPs+mKZkQSLaQMvkcjIhUYLELV4pvnBIDx06lJdGwfLxxx+Hn5M6JQH/gJiZyb1KXCF6jRTyQ0ilQG/aylTSw8/FEJXtS5TDoBAnwoBlLQhhxnal7VG6YL1jnkoJySxyfDpFAjOkNAkXNUl9AsrqiBFh3jERla2NKiG4KvOjZ1Nvol0fE80qoV3rt9zkdcFHIvs5jDSSnE4iP5UgcYtXBacTEhK4Qxu5GPrcETi5FQEgXUGt/APU72wPud/+rkWQ+NNJqDdtwVxg6vMGEWQwbck5KBdWOH6PUR0vmOx4WflznjfFChOzFghkzxBUCn7nHaLduzUtyJ/5Kj5x8zDRP5aIAE+Q2xToEb4z8KfBdYhrRv48NHT5OjPykdw1KK/C96V8JH4XJIioQkguypsY9eEQZUgUfkZELekTAZ1x4Dv/1wfSCwJPc0dcKcAwbclhlfC7QPvCjNP6vU4u09wNiI5Ps5WDiYDy8WDMmMB+D4wDyKtFtR+UjntEKloQUpa1Mbe9s9BuTKhuu9FAqowk2iCFzuvznow6HRZ9hujAWK2d7o4PbetVxJb/TVvo4YGIKYTkQhuRFyVEAoiYQblKrjICPdf9hac5KWY+D9MWzAwtthNV+UqL2S/Vy7PvFQlk7ioEhwEoZr1ypfY8GAP7o49qjz5UBfI/Ip/DU5yFdrszZTbd4OhTQyh77vqaebVEd+MgFAiMx3cTVRhkvz6T1BlU4R9BgnIkSCLUF05UBBhhU/bkZpR7lRXt4r9jkG8+RGOZxqCEjnDAowd7md6aYMH35Glk28ZZCLNVkHjZPCyISK1xqFQQxiVRoGHaNAofbh01Xq83MxV+ynX5G3eTKSQZQuN4KkHLOcpeVQthx7XVdB1Rje/dH2uNcTahpHoX+1+QPPHEEz4VP1QEQ5BIN6CrKCyzyJpBtsq+FdQDxbtpYbxGoEOj9XvdCJJt73lu8gsRousAij8EY1yqVcvml9m5k0KP6DpoRK0JNuEBXFUtwCRpy9vO91Xuf7bnEBw1xhA12+jYGdQdCObozOwz3xX+85Eg8RCmLWSKo7puHCrFSbzxxhtmd6nwt2lLdjIaOar9oZEYJnS5QR7sERJcvKvzbWUB48y0JRd+3DWMqJKbBl4hAmMfqvICFyXh/J6cWKkS0datRGvXEknNQ0ODM62xSGeiQk9oobdIQoQGcfQ3m0aCKEW8Rqkg+Akv2+rjOYB9oCS8IvwFyZQpU3jPdrSQhWYCB7sAz5UgCQONRBYk3mbGGxLleYE7I+5JgsSVEAFyIprTMi+SYLuyjcK5UOOtW5omUsTkxNhb8F1ouQtBguitkCMLkvwtiU7P0Z4Xe0Y72HR5tdpX4NhUm0ay5lmtGjTqs9Wf4dzJjqCSxsuUGSpSBAmq/w4ePJjeffdd3lFQEcYaCQZ7f9pRZOen6YgtA9OWu7h/dxqJXB7cmYksDBBl3SFEpHJvAadsWfvvDwkQ8JgEyQ3W0ktN4fTJqLJPDBoJhAg4+ZdjbkmW8kTXdtlMUcqXETmCBO1xUWhRCZEw1khEEyx/16GSbdbeaCSedGu0bpvVvbP9niRIrmylcEX0QCuu62gcaIQ5C1qJp5V1/AomDvNraRObx/fatEsM+qhuAL+FUY03Zy0CUCk40VKHH9T8UfOlnVlAlL9FIH+Jwg2mVYrnnnuOfv/dm4gdhU/EWQRJkgeCxFrmXYre8gfyje2NIKn8mXbjN1nlftuMlna/rhLCZI3k6jatl8mF1RRuiO7NlnY4QaNqVfT80b5f+GiCSuJFm3YsNAdcN9mraGG2j/zrujXuheX2SYR/ZNOao4EyfbQGbNi2cAeiWJU0GFEaCXJFULgRFXbRi0TvbB85cqQ/j0+hrw10ejbRhVVaGWxnnJoVmPMm+148LQapL4nf0jKguCNzaaLavxKlL+DYTdGVw//sIqJcdSicEF0QXDTxDAhp02qlWJC/gvIsyHYPKnJvkNP/ao/u+qKDrJbKDeeWBOjAFCEXJDt27KDKlbXQz526uELZ8a4IkEYCFtTTQhP1QFvxtemUt7WPAgEcsa4o01czkci1ucIwOVEIkmBrJODppzVBMnNmCASJ/F8csbRY9iQMF5MId5QN9o9R+FWQLFmiZgkhQV/kzsjovXs40b6vba8f0PV78BXZPh0OoOZYG0uS25Z+RHs+D0tBIkxbwdZIQJMm2uPmzURJSVpf95BoJCJqy11fdOCudHzBNkTpVEJ0OKHCriIFvRkH9mc916WqgCCzYyfLFIsIc76mOwdhwMmTodNISpYkypoVhVZhTQjylxsJdX1BTiPcFQOFyVMRVihBEqkayY2DjtucnKn7jBdJg67IVsV134ZQItr/wocURty8SXTQ8leVLx/874fSKsqlzLGkboREIxF40rFTf902kHx+9f/xzM+iCCpKkEQK+sglOblPhFrqwyWNYvR9ofoYrUKqqEMUThT0oMlWCFi+XLNCoiJvqMrTPWNxNf3wQxhoJKjc64n0Q7dOgEKL2asFpuSPIrT9SBQhAP2rYaoS5iu90Nhi4Hz0tyDJWYMoZ5iaFfI2IkJyu9k+3wFm3jztsWLF0B1D27a2fBb0jA9Em18HID2Nqg24qrml79bZ9qSWaCrnC8ktBxRhg9JIIgWYBFrstM3OZEGSfJfooEFFU3/1u44EYtK7b4YUAkRgY7BqbBmRMSNRQYt83bcvSF+KfjgigtCuk6aJ3CaEfsM5j3bNNX8iqjPJvpumIrI1kgMHDvDoLaMOiQMHDvTXsSn0oBOgKBeCbnOYqaGXR7KTsin+1kjCGZEgaWSXDxFodSuCHFuEOPEa5VLg9IfDvU4w0mxQRFOQsTjRjQO+Rf498IJ/jksRHoLkhx9+oNdee41y5sxJefPmdSjaqARJgBHOSnQQPPY70WOr7GfhKKO9+1PHelUpHZG0GEa9SYYOtT0PhaNdBqlfCxYQbd8epC/M8qCtqdqZ/2zr8zUL0gEowtq09fHHH9PQoUPp7NmztHXrVtqyZYt12YxgdS8YPXo0FS1alFcUrlmzJq1fv97l9tOnT6cyZcrw7VHKfu5cW//mpKQk+t///sfXZ8iQgfLnz89bA58+bd8pEN8HwScvn3zyCYU9cgHDi6u1kiDzJbtJhcFEBVpp5bS9KawY6eG/cPDKjb1CCMq3y1nmoUQUcAxYCPDNo1rFBYDzf3a+7b2qUm5TBuUsT4mYFiRXrlzh/Uj8Bep2oePioEGDuCCqWLEiNW3alJvNjFi9ejV16tSJXnzxRS682rZtyxeRZX/79m2+nwEDBvDHGTNm0L59+6h169YO+/roo494y2CxoI1w2KOvhLugrn1mN2bmDf4hKt+fUhWy0PR3sUofM9pRniTUiBBgzNF01mj/sLytVnEBBRRl8xX6i5R6najs21rL2tIRcI8pTBPFmLnpGwbw6tWr06uvvkr+ABoI9oeGWQA+l0KFCvFBHaXq9aDy8K1bt2j2bFu+QK1atahSpUo0duxYw+/YsGED1ahRg44dO0aFCxe2aiRvvvkmX7zh+vXrlCVLFrp27RplzhxEB+DiJracCSOMSqekBuAvmmbJP3jyhn1JmRBw5w4RLgv4SY4fJyokVU4PBTgOaEXJyZqvpEABL3d05xxR2lz2tdYwhEyxvM5Q1L6lbsN5RPmb+nbwiqDh7bhm2kdSokQJPttfu3atzx0SUZJ+06ZN1L+/bfaM8vSNGzemNWvWGH4G66HByECD+fvvv51+D04KTFdZkeIrAVPWkCFDuHDp3LkzvfXWWxQba3xKEhMT+SKf8JDgrDeHvs9DakNu4MXzF0IrSNat0wbvXLlsEVOhBJc15lBHjhAdPuylIIEZFRow2uLWszSf0veZ0fdlR6FORYrHtCAZN24cZcyYkZYtW8YXGbMdEi9evMirCefRZWrh9V40mzYAvhmj7bHeiISEBO4zgTlMlrA4zipVqlD27Nm5uQzCDOYtZ9WLhw8fzht6hRxEawlQYltOTGx7nFJ3RFu0lqcQBpFbKJQIGjUKn35L6IcCQYLlYW+KE+z7Sns8/jvRhd5EuSy+uYRzxtvnbRI+P14RXoLkCK7CCAGO944dOxKsd2PGjLF7T9ZqUA4/Pj6eXnnlFS4w0hi0sYOgkT8DjQQmuKAjwijBI/O0GaLCPtktDCK3xByriqWqTLgIkkWLfMglyVjC9nxBHaJWB4gylXAuSLJpVcIVKR+fEhIxQJt0sdiBEOKYmBg6d87+QsRrhBYbgfWebC+ECPwiCxYscGvvg6/m3r17dPSoTjW3AOGCfchLyIHzUuHIsSkhPSsHDhAttLix2rensAGNroTZzSvgG5GZVZLo6FTn7QVyWjz8ihSPV4Lkl19+4f6RdOnS8QUz+l9/NV86A1pA1apVaRGmSRbgbMfr2k5SgbFe3h5AUMjbCyGCxMmFCxdSDg9qQiCUGf6Z3LnDt/c3p4Gl8l7FoURpc9pKxVf+PKSHFVacDnZ1Qns+taTxlClD9MADFDaIWwSC5K43SpuRpofs9Us6yVT4Se16LNjOuwNVpHzTFnwIcLb36tWL6tbVzCorV67kUVzwecBhbQaYi9C+t1q1ajyy6quvvuJRWd26dePvIwekQIEC3OQEevfuTQ0aNKAvvviCWrZsSVOnTqWNGzdy340QIk888QQP/UVkF3wwwn8CfwiEFxz269ato0ceeYQyZcrEX+O4n3nmGcqWLRuFNQVa2EdmVfuW6IGXiLJbppupmeLdiA5PIEobgsYfFqCgi2z2F8IsGRtJkTlzwjdJtGoV0SOPmNwBSvEYBXhc3mi/rtp32iRHkXpgJilatCj7+eefHdZPnDiRv+cNo0aNYoULF2bx8fGsRo0abO3atdb3GjRowJ577jm77adNm8ZKlSrFty9fvjybM2eO9b0jR45glDVclixZwrfZtGkTq1mzJsuSJQtLmzYtK1u2LBs2bBhLSEjw+JivXbvG94lHRZhwYiZjk4ixf6uG7BAOHIAoYSw+nrErV1jY0aGDdnyffWbiQ/fuMHb3BmPre2rnV79MibN/fVfdE5GKt+Oa6TwSZJMj+Q9hwDIwI8HchSip1EDI8kgUzkG12X8raTXGOoSmm+OECZomAjPS6tUUdgwbRvT++0TVqmnJiW6DqhIuEP1TnOjeTc+/pFOyfZ6JIsWPa6b/bQiQadOmGWaol0Q7NoUiVKTLb+seGaLsdtE86rEwTZ9oaskN3LiRaPFiDz5w5FdHIVL9O6IWBkW7Ko0garRUCZFUiGkfCXIpkF2+fPlyq49k1apV3AFuJGAUiqARl8X2/N4tonj7BNRAg3zV+ZYSUy1bUliCyC00uvrtN6LJk7U8F5dcNRAY6YsQZa1AlLOOVu9N+OpK9QzIMSvCH9MaSYcOHbijGqG7yCbHgucotNiunYrSUIRJvS053yZIrFhBdOOG1ptdhNqGIy+9pD2OH090+bKbjZMtTaWylLOty/aQ9ijbxRCppUi1eNWPBCG7v2FKo1CEGyK7/b8aPtUdQ1va7t21548+SgRl210Uuaj227AhSv1Q2IKsdhwfijeioKQQLA7AfXp8uva8TB+tqVTaPLYulNBC/q1MlK+p1slQkWrx6HKX60rhuatFoQgphZ+2Pb+6y6tdoB2tECIAvgRLNLpL/vpLe6xencIaCBHRx91lcuL5ZfbleKB1oIe6IFslog4XiRrYCqgqUiceCRLkVoiy7ih8iNf6RaxXKEJKHUlTvmApeGUCtKRBroWeWbOIMmVyXoL92DEitOOJiSHq0oXCHmGFdhlZdsnWF6hItZrGpVXQzlk0W1OkWjy6AhYvXsyT+QBa7CoUYQvs9iW6Ex0cR3TzkMcf+/lnoueft18HH0L9+kS9ehHNm0d08ybRiBFEBt0NaKqlGG6tWkThXhwB4HehcPfu3US7dhl3cFy1KRfVtRSbPn6xCM/UR9T/wYNEzZpp5wf+IIXCdELisWPH2P379x3WYx3eSy2ohMQwZs9IW3Lc0akOb+PyHTWKsYoVGevfn7EyZRh7rMI89lbzL1hUVDJP2PvuO/vtY2O1RD4sV6/a72/CBNt748eziKFVK+2YP/jA8b3ERMbef+Ibfg7/fLOd9ffJCz6vSFkELSERRRZRbl1fk+rSpUt8HUqSpAZUQmIEJCYKJKf7rVua6UqfN8smaRFInyz+nvqNfoZi0qS3e//UKVtfkQcftLWsRchvhgxawyiAHiQwb0UCU6YQde6s1QNDoUk5COurTy7Qm4W1ezw5X2val2emodaC84DzoUgZBC0hEXIHfUf03Lx5k2e9KxQhJ1tFovr/2F7fvWZ92rixoxDRKuhovPvoKxTzZwai88vttkAjqIkTtefo6iwitPAohMjp05EjRECrVkTp0hEdOqQlKKKQI4pf4/bePOdf63Yx965QuXLa70SwJrZBZjyoUEHb3h/te/fvR54aqnn7vi9FcPHYSyZ6cUCIoGhj+vS2GRu0EOSWoN2tQhEshC5tWOajwOO25+eXEhVswwd9IQAAeqed3neQrh6UVgoWNiB64ipRvC3J8bnniJYu1QRK166aTwXlRgBm9pHmL8iYkah1a1SlIKpRw/69Z+tJ1byrjbJGe4lAgoEDtc/KQglCxtt4GxQPxzkFH36Iit6aQEEYNoIc0FkbQuupp4jee0/1ywo3PDZtoVIuQFdElGxHFV0BnqMH+ttvv51qyqQo01Zo+6HPnUv0xBPaa+R4PGmUD/dvFaIrW4jyt6RFybOpcWNG1YtvoEr1y9K4oauJcj9s6/PujHanidLZJARMQKUM2sBAuEDQRBr//EPUpo3j+uPj61OhNCu0/JEqXxh+FucdA7sMtDUjE5gzMPogtBrBDp7SuzfRl18qYRJW45pZZ8zzzz+vKt4qZ3tQ+eYbxqpWZWzvXsa+/dbR6SuWzJkZW7xY+uCWd61O9/RpbrJuDX4yrl4rL3DO69fpMPpuE4Wjw4p79xhr3177DXnzMtasGWM/fHfF9tvPLHT5eTjln37adh5q1zb3/WPH2j7booX9a1dL9erasSvCw9lu2keCfiHoJKjn8uXLKiExhFy9SvT551p11xYttNmaWPr3t9nxBXjtaZjF0KHaftA2VuQSoMULzBH4zkDEVyAnAzN/fO8bbxBt2qQ1ikIorkBvjkE+bMeO2rnglHvH+l776jPo1UZjXX9phcFERZ4iarbJfv0fOYiSbIULEQYMRzV8BXA2Q0My6M4cEcCn8+ef2rVw5gzRv/8SvVRjsMddOGGYwLkQodNr1hDt2ePZd+O7Xn1Vew5DBsxXr7yiHUenTpqJC529UcZFiBDk+YANG7SQbEWYYFZiNWvWjI0ePdph/ZgxY1jz5s1ZaiGcwn/RW8KTWVxUlBbuumKFbd2WLa73vW2bZ/seNoyxy5cZu33bt99y8KB2TNAuXH1fnz6MJSUxduECYzVrOv7Oo0cZGzeOsZOj8vOZ9RtNv2JX/3zEWAvZ0t/xQBKlWTmWWWVYqsGFNuaKdu1s/8GzzzJ26JDzbX//3f4/O33afE8VLFu3srDk/n3Gdu/WNDQc5yefsBQ9rpkWJNmyZWO7cYZ07Nmzh2XPnp2lFsJFkPz0k/FA+9JLngkALNHR2uDbti1jgwZpQgG9xOTcCTPLkCHuj/vMGU3wCGAaqlfPeH/ZsmmCoW9fxkaOZCw52Xifc+c6fnZ89+cdBYdogIXl7FLnB6n/XHIqsKXcvW77vRveMPXRDRsczz/+k+PH7be7eVO73sQ2f/7pXfMwLOnSOb8ewK1bjM2fr+UFnTyprfvtN8beecf157wVHmPGMFahgvF13K0bC3uCJkjSp0/Ptm/f7rAe69LhX00lBEqQ4KZbulS7ub74QrvgoUXcvWvb5uOPGStdGv4q20UK+zYS5TCjv3PHfp/QQBo3ZiwuzjvBkDOntl/YpDt1YixDBs1ncf26duM4+9yvv6Jzpk1j6NiRsXz5bO9nysTYunWMzZvHWP36jp8/e9b8+cO5kffx48svOAoEdPA7u4SxQxNc7+zAOMb+KZl6Ov8lJzH2X23b7z2/0vQuXnzR+FrAQI5rG4M5tBWxHs1WDfKb3TJ0qP3+xT4wOcF3Yb/PPGO/DbpWyq8LF7Z/DS3JW5KTja9h/WJG80rRgqRhw4asV69eDut79OjB6mFKmUrwtyCZOdP1BVijhnaxzp5t/D5mXp6A2eCPPzJ24oS2P8zOoHbr9xcTo30n3odDVcboxodGAS1DFm7eLL17M/bee4xdvOj9uVy0iLEcORh7/HHGLi3qZxsYZxZn7LIbW54e/Fjx+TvnWIrm5GyvzVp6EBjh7r/+6CPfDhcTLXl/ImjAl6VhQ88DJ2BaXbaMsREjGMuSxbYPTPJ69tRMcH/8oe1P3r88KfQnuFRxr2Kej2oFhw+HsSBZuXIl73P+8MMPsw8//JAveI51y5cvZ6kFfwkSzPLxpxuZm/AomwD0C2zS5csztnq1v36Tps1gf/A9eHsxDxjgeKwPPcRYkyaMtW7NWK5cmqainxHihvQ7dy4wtu4Vxk7N834fU9NqA+vNoyxFs3OoTYgcmezz7hBB99RTxtduoULeaSJ6hA/C2ZI1qzawQrudPFmLDMNEBc9xHbr6LCZtMrg3rlzRNH+YWY0+06+f8XH+8INtmzZtfNdMIIwwGZw1S5uEwuJQoIDjOTYrtIImSMCWLVtY586dWbly5VjVqlVZt27d2P79+1lqwpsTjhkC/tyyZbXHBx+0n8kUKcLY1KnaDQYNQ9hwjUIijxxhYc/69ZqN3NmAAQ0G5i5oRJjdhS3TstoG2Hk1Gds5nLFNfRk7t4KlGO7dYeyPnNpv3OxkNPQSXPfwvcFXgcGuVi3Nz+Ev5PBjaNEYYHF/nDrl+T5wjVaq5HifzZiB+oKMFS3qXpsZOdL1d2Bf8vZvv208sdTfLxBc778Pt4I2bjjzwegXmPfCttaWwvvEHYSwitISMsjcRVgjQh+dldhYvpzo9de1pLc33wzvxkkpjjkPEl1z0tuk6XqiHGHegMQVV7YSbR9EdEoqKYPyMgVbUaSA0i6oWICulKh75i0IY69Xz776gTNQ2OOxx7Sk2C4m2gZMn67dvyinA1CyEA3Tjh/X6sAJsO6117Sum6tWaTXc3IFQ6rZttXBpJOhiLDGs+hCAhESfBElCQgLdxb8oYSobMoLx5oTfvq2V7b5yRWuWhBpHEAioL1S6dMAPWeEt55YRLWpoey33Kgc1fyLKUJQo76ORdY7R+GuuruIiGlc98h9RTOqum4eBu0MHLfNf0KAB0ciRmuDC/eptOZgbN7TPI1/GUyBYkFeFWnF4RLkY5C4VLarVOZMqVvlE0ATJ7du36Z133qFp06bxir96VPVfRYoj6TrRvGpESTeIWh8iik1PtOdzoi397Ler+SNR7gZEmUpQ2HNxLdH82rbXWcoTlX+PqGjnUB5VWIHKzuPGEX39taalGDU88xbGNCsDLBFIrESdNlgcoOX06KEluSLBtnJlrf9NzZqUskqkIDqrbNmy7I8//uDhvuPHj2dDhgxhBQsWZL8hxMcLvv32W1akSBGWJk0aVqNGDbYOMaEumDZtGitdujTf/sEHH2Rz5sxx6I0yYMAAljdvXh4E0KhRIwcfzqVLl7ifJ1OmTCxLlizshRdeYDdu3Ii4PBJFkNAbrpNuM7aqC2MLGzmGF699mbHL2xi7eYyxpFuM3T4T3L/p5nHGLqyxHLeTZIkZ+WzHu39sUA9PEb4EzUdSuHBh+uWXX6hhw4ZcYm3evJlKlChBv/76K02ZMoXmopqeCX7//Xfq2rUrjR07lmrWrMlLsEyfPp327dvn0PMErF69murXr0/Dhw+nxx9/nCZPnkyffvopP44HLY0R8Brv//zzz1SsWDFerXjHjh20e/dua6n75s2b874q33//PSUlJVG3bt2oevXqfH+eoIo2KqysfJro+O+uT0jBtkT1pmttaVHW/vYJooRzRMl3iOKyEN2/q62jKKICrYjSaB1JnXJ1B9HRSdrUNjYDUUw6ooPfG3eFhJkK291PJCrUnujGQaKr27X3Hl0UeSY5RcAImmkrY8aMfECGQClYsCDNmDGDatSoQUeOHKEKFSrwviRmgPDAAP7tt9/y1/fv36dChQrR66+/Tu8a9DR96qmn6NatWzQbhXks1KpVi5ewhzDCz8mfPz/17duXVyMGOCl58uShiRMn0tNPP0179uyhcuXK0YYNG6iaxfs9b948atGiBZ08eZJ/3h1KkCjsuLabKDmBaOdHRJe3EN0+bnCCoojS5tYEiDvisxGlL0RUoDXRndNEh8cT5XqY6H4S0dVtmgDylbjMRE/aerUoFNe9FCQe9yMRFC9enAsNCJIyZcpwXwkEyaxZsyhr1qym9gVH/aZNm6g/qgpaiI6OpsaNG9MaVH8zAOtFbxRB06ZN6e+//+bPcWxnz57l+xDgxEBg4bMQJHjEsQohArA9vht9Vdq1a+fwvYmJiXyRT7hCYbvIymmP9bXrkO4nE51fRpR8m2h1F83PggZaRkIk4wNEUTGakLh1xHJzXNEWoTmACyscP5sXnbrOEbH7ROweUcaSRDlrar6au1eJMpchup9AtOlNonu3iO6c0UJ5WDJR043qD1T4BdOCBCagbdu2UYMGDbjG0KpVK65NwDw0EiENJrh48SJ3zkNbkMHrveg6ZACEhNH2WC/eF+tcbaM3m8XGxlL27Nmt2+iBqWwwwqsUCk+IjrGZjDDrh6MeDm6YsGCKylpBMznhuQwMBOihAjMVBnsIg6hoTQjkeVRz5EfFalV5+XMP4zsbLVL/myJ8BMlbb71lN4vHgA+tAn6Shx56iFIq0JpkTQgaCUxwCoVHxGUiytfE/XYQDNmrENX4Xp1YRcRgKq0NWkejRo3oANrEWShSpAi1b9/eKyGSM2dOiomJoXO6Js14nTdvXsPPYL2r7cWju23Onz9v9z56rKCnirPvTZMmDbcZyotCoVAoTAqSuLg42r5dstn6CFr0Vq1alRYtsqndcLbjNdr5GoH18vZgwYIF1u0RpQVhIG8D7QG+D7ENHq9evco1KcHixYv5d8OXolAoFAoTmI0zfvPNN9n//vc/5i+mTp3K80EmTpzI+5x0796dZc2alZ211BB/9tln2bvvvmvdftWqVSw2NpZ9/vnnvAfKoEGDWFxcHNuxY4d1m08++YTvY+bMmby8fZs2bVixYsXYHam+Ohp0Va5cmeesoBBlyZIlWSfUSPcQlUeiUChSGte8zCMx7SOBCWj8+PG0cOFCrk1k0BW3MetwRzjvhQsXaODAgdzRjTBehOIKZ/nx48d5NJWgTp06PNfjgw8+oPfee49KlizJI7ZEDglA5j1ChLt37841j3r16vF9ihwSMGnSJOrVqxc31WH/HTp0oG+++cbs6VAoFIpUj+k8kkceecTpe1FRUdxElBpAnDVCiE+cOKH8JQqFIkUggogwAUfahF8FCfwimPHLmkFqB4mLKmpLoVCkRDBBRsK5XwUJIqtQTgS5F0hIREZ4DpSjTMXAMX/69GnKlCkT18TMSnylyajzoq4Z31D3kv/PDcTBjRs3eHUPM4qDRz4SmHCQMQ5BcvToUT6IpnZwks1IbD0qhFidF3XN+Ad1L/n33JgxaZkSJHBEI5M9X758fPaN0iLQUow4fPiw6YNQKBQKReTikSAZN24cTzo8ePAgvfHGG/Tyyy9zk45CoVAoFB6H/zZr1ow/Iomvd+/eSpB4CTLkBw0axB8V6ryoa8Z71L0UPudG9WxXKBQKhU+oeF6FQqFQ+IQSJAqFQqHwCSVIFAqFQuETSpAoFAqFwieUIPETQ4cO5QUl06dP77TlMApQtmzZkm+D5M5+/frxIpgyS5cupSpVqvBoCzQLQ595PaNHj6aiRYvyIpQoe79+/XqKJHDsyEeSl08++cShLM/DDz/MfyMydEeMGOGwn+nTp/N2z9imQoUKNHfuXEppRPp/bZYPP/zQ4drAfyxISEignj178soaGTNm5Dlu+t5DntxnkcDy5ct5B1pkmeM8iHbichY6it0ivy9dunS80aDcKwqgx1KXLl14UiLGpRdffJFu3rxp+l5zS2CKEac+Bg4cyEaOHMn69OnDsmTJ4vD+vXv32IMPPsgaN27MtmzZwubOncty5szJ+vfvb93m8OHDLH369HwfKKk/atQoFhMTw+bNm2dXdj8+Pp6NHz+e7dq1i7388su8ZP65c+dYpFCkSBH20UcfsTNnzliXmzdvWt9HCes8efKwLl26sJ07d7IpU6awdOnSse+//96unQDOzYgRI/i5+uCDDxzaCUQ6KeG/NgvaQpQvX97u2rhw4YL1/VdffZUVKlSILVq0iG3cuJHVqlWL1alTx9R9FinMnTuXvf/++2zGjBm8tPtff/1l9z7aZWCs+fvvv9m2bdtY69atDdtlVKxYka1du5atWLGClShRwq5dhif3micoQeJnJkyYYChIcFFER0db+6yAMWPGsMyZM7PExET++p133uE3kcxTTz3FmjZtan1do0YN1rNnT+vr5ORklj9/fjZ8+HAWSYLkyy+/dPr+d999x7Jly2Y9LwA9cEqXLm193bFjR9ayZUu7z9WsWZO98sorLKWQEv5rbwQJBj4jrl69yicL06dPt65DTyIMsmvWrPH4PotESCdI7t+/z/Lmzcs+++wzu/OD3k4QBgATLHxuw4YN1m3+/fdfFhUVxU6dOuXxveYJyrQVJNasWcPNL6LPCmjatCkvrrZr1y7rNlBPZbAN1oO7d+/yhFB5G9T8wmuxTaQAUxbME5UrV6bPPvvMzvSA31K/fn3eQVM+D/v27aMrV654dK4inZT0X5sF5hmYc1AgFmYZmKoAzgfafcvnBGavwoULW8+JJ/dZSuDIkSO8f5N8LlAjC+ZP+VzAnIWSVgJsj+sIHWM9vdc8wXRjK4V34E+XL24gXuM9V9vgJrhz5w7/Y5OTkw232bt3b8T8NSizAz9Q9uzZafXq1dS/f39eXVo0RcN5QMtkZ+cqW7ZsTs+VOJeRzsWLF1PEf20WDITwC5YuXZpfE4MHD+b2+507d/L/FgOe3gcp/++e3GcpgbOW3+LqHsAjfEQysbGx/L6Tt3F3r3mCEiQuePfdd+nTTz91eQL37Nlj5wxMrZg5V3369LGue+ihh/jg8Morr9Dw4cNV6ZhUTvPmze2uDQiWIkWK0LRp07hDWRGeKEHigr59+9Lzzz/v8gRC/faEvHnzOkTciGgTvCce9REoeI2IC9xEqLiMxWgbsY9IPFcYLGDaQosCzESdnQdPzlWoz4O/yJkzZ9j+18EE2kepUqV4wdgmTZpwkx+698laiXxOPLnPUgJ5Lb8Fvw1RWwK8Rrtysc358+ftPof7DJFc7u4j+Ts8QflIXJArVy4+g3a1yLZFV9SuXZt27Nhh98cuWLCAC4ly5cpZt1m0aJHd57AN1gN8V9WqVe22QW8YvBbbROK52rp1K7fbCjUcvwWhj7CHy+cBQkao2u7OVaQTzv91MEGo6qFDh/hgifMRFxdnd05gy4cPRZwTT+6zlECxYsX4QC+fC5jA4fuQzwWELnxLArRCx3WEyZun95pH+BRKoLBy7NgxHm44ePBgljFjRv4cy40bN+zCEh977DG2detWHtKbK1cuw/Dffv368WiU0aNHG4b/IjJj4sSJPCqje/fuPCRUjlIJZ1avXs0jtnAODh06xH777Td+Hrp27WoXfYKQxGeffZaHJOI347zow39jY2PZ559/zs8Von1SYvhvJP/X3tC3b1+2dOlSduTIEf4fI4wX4bvnz5+3hv8WLlyYLV68mIf/1q5dmy8CT+6zSOHGjRvWcQRDNdIL8BxjjQj/xfUwc+ZMtn37dtamTRvD8N/KlSuzdevWsZUrV7KSJUvahf96cq95ghIkfuK5557jf7Z+WbJkiXWbo0ePsubNm/M4bdwcuGmSkpLs9oPtK1WqxPMHihcvzsOJ9SC/BDcTtkGIKGLEI4VNmzbxMF2ESKdNm5aVLVuWDRs2jCUkJNhth7j4evXq8YG0QIEC/KbRM23aNFaqVCl+HhA2PWfOHJbSiOT/2hsQ7p4vXz7+e/G/4/XBgwet72OQ7NGjBw9ZxYDXrl07nmsi48l9FgksWbLEcEzBWCNCgAcMGMAFAe6TRo0asX379tnt49KlS1xwYHKLEOhu3bpZJ7dm7jV3qDLyCoVCofAJ5SNRKBQKhU8oQaJQKBQKn1CCRKFQKBQ+oQSJQqFQKHxCCRKFQqFQ+IQSJAqFQqHwCSVIFAqFQuETSpAoFAqFwieUIFEoFAqFTyhBolAoFAqfUIJEoQgRDRs2pF69evEF3e1QOn7AgAGof8ffRyOzrl278iqs6dOn57060D1QcOzYMWrVqhV/P0OGDFS+fHmaO3eu+j8VQUcJEoUihPz888+8ax16aHz99de8S+SPP/7I30N/l40bN9I///zDW6JCwLRo0cJa8rtnz56UmJjIy4CjdDoai2XMmFH9n4qgo4o2KhQh1EjQNwO9xKOioqydJiE4Zs6cyRs6rVq1iurUqcPfu3TpEhUqVIgLnyeffJJ3EOzQoQMNGjRI/YeKkKI0EoUihNSqVcsqRESjIZivdu/ezTUV0YAI5MiRgzccQsti8MYbb9DHH39MdevW5cJk+/btIfkNCoUSJApFhPLSSy/R4cOH6dlnn+WmrWrVqtGoUaNCfViKVIgSJApFCEFrVJm1a9dSyZIleVtY9NeW34dpC61l5ZaxMHW9+uqrNGPGDOrbty/98MMPQT1+hQIoQaJQhBD0G+/Tpw8XEFOmTOEaRe/evbkwadOmDb388su0cuVK2rZtGz3zzDNUoEABvh68+eab9N9//9GRI0do8+bNtGTJEipbtqz6PxVBJzb4X6lQKAQI771z5w7VqFGDYmJiuBDp3r07f2/ChAn89eOPP053796l+vXr8/DeuLg4/n5ycjKP3Dp58iRlzpyZmjVrRl9++aU6uYqgo6K2FIoQRm1VqlSJvvrqK/UfKCIaZdpSKBQKhU8oQaJQKBQKn1CmLYVCoVD4hNJIFAqFQuETSpAoFAqFwieUIFEoFAqFTyhBolAoFAqfUIJEoVAoFD6hBIlCoVAofEIJEoVCoVD4hBIkCoVCofAJJUgUCoVCQb7wfzWY1bDQBtS7AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(4, 2)) # If you want a different sized plot, you can us matplotlib commands\n", + "plot_enrichment_profile.by_modification(\n", + " mod_file_name=pileup_file,\n", + " regions=ctcf_target_regions,\n", + " window_size=1000,\n", + " motifs=['A,0','CG,0'],\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " regions_5to3prime=False, # if set to True, regions on the reverse (-) strand will be flipped so everything is 5 prime to 3 prime\n", + " smooth_window=50,\n", + " palette = { # you can pass down kwargs to sns barplot if you want to\n", + " 'A,0':'blue',\n", + " 'CG,0':'orange'\n", + " }\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "450d66b42aa24f10b41558c594692e95", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/50 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_enrichment_profile.by_regions(\n", + " mod_file_name=pileup_file,\n", + " regions_list=[ctcf_target_regions,ctcf_off_target_regions],\n", + " window_size=1000,\n", + " motif='A,0',\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " regions_5to3prime=False, # if set to True, regions on the reverse (-) strand will be flipped so everything is 5 prime to 3 prime\n", + " smooth_window=50,\n", + ")\n", + "plt.xlabel('position relative to center of feature')\n", + "plt.ylabel('mA/A') # You can also use matplotlib commands to rename axes and more" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot Enrichment" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "from dimelo import plot_enrichment" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "a5bed285765d43a9b7ef1cd90fd0f87a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/50 [00:00" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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YO569+4tiYmJsm/fPmnWrJkp27hxo7z44ovmNQAAgKALQC+//LJUqFBBJk6cKD///LMpq1ixoiQnJ8ugQYN80UYAAAD/BqDQ0FB5+umnzZGZmWnKGPwMAACCfiFEHQe0atUqWbBggYSEhJiyI0eOyNmzZ73dPgAAAP/3AB08eFA6dOgghw4dkosXL0q7du2kZMmS8tJLL5nnOj0eAAAgqHqABg4cKE2bNpXTp09L0aJFXeX33XefWQwRAAAg6HqA1q5dK+vXr5fw8PAc5dWqVZPDhw97s20AAACB0QOka/3oys9X++mnn8ytMAAAgKALQPfcc49MnjzZ9VwHQevg5xEjRkjHjh293T4AAAD/3wLT9X/at28vdevWlQsXLshDDz0ke/fulbJly5pZYQAAAEEXgCpXrizffPONLFy4ULZv3256f3Rl6IcffjjHoGgAAICgCUDmTYUKSc+ePb3fGgAAgEAJQB988IH87W9/k8KFC5vHeencubO32gYAAOC/ANSlSxc5evSolCtXzjy+Fh0Q7WmGGAAAQIELQDr13dNjAACAoJ0GX6ZMGcnIyDCPH3nkEfn111993S4AAAD/BqCsrCzXzu9vv/22mf4OAAAQ1LfA4uLizNif2NhYcTgcMmDAgGtOeZ81a5a32wgAAHDzA9A777wjr7zyiuzbt88MdD5z5gy9QAAAILgDUPny5eXFF180j6tXry5z586V6OhoX7cNAAAgMBZC3L9/v29aAgAAEEgBaOrUqdK3b1+JiIgwj/Oi44MAAAAKfADS8T+615cGIH18LTo+iAAEAACCIgC53/biFhgAALBiHSAAAADreoCSkpKu+4STJk36I+0BAAAIjAC0devWHM+3bNkily9fltq1a5vne/bskbCwMLNQIgAAQFAEoNWrV+fo4SlZsqTZEqN06dKm7PTp09K7d29p3bq171oKAADgrzFAEydOlHHjxrnCj9LHY8aMMa8BAAAEXQDSTVFPnDiRq1zL2CUeAAAEZQC67777zO2upUuXyk8//WSOJUuWSGJiotx///2+aSUAAIA/t8JISUmRwYMHy0MPPSSXLl36v5MUKmQC0IQJE7zZNgAAgMAIQMWKFZPXXnvNhB3dHV7VqFFDihcv7ov2AQAA+D8AOWngKVOmjOsxAABA0I4BunLlirzwwgsSFRUlVatWNUepUqVk9OjR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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_enrichment.by_regions(\n", + " mod_file_name=pileup_file,\n", + " regions_list=[ctcf_target_regions, ctcf_off_target_regions],\n", + " motif='A,0',\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " sample_names=['on target', 'off target'],\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "35635fc4a5364f339106d27b14ffb878", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/50 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_enrichment.by_modification(\n", + " mod_file_name=pileup_file,\n", + " regions=ctcf_target_regions,\n", + " motifs=['A,0','CG,0'],\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " )\n", + "plt.title(ctcf_target_regions)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "787fb81945da4a4dbb1343ca73885187", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/100 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_enrichment.by_modification(\n", + " mod_file_name=pileup_file,\n", + " regions=ctcf_off_target_regions,\n", + " motifs=['A,0','CG,0'],\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " )\n", + "plt.title(ctcf_off_target_regions)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot Reads" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "from dimelo import plot_reads" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, \"CTCF target data, 100 known binding locations\\nsort_by=['shuffle'] i.e. default\")" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAl4AAAHcCAYAAAAHsaTIAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsfQe4LUWRf1/SI0mWuICASBREEARRdxVFFxXEFfUhoiImFF0UXP6r4JpQzAEwK+p7IAiCAoIsYFxyziggYAAkvUeO8/9qOD3U1FRVV/f0zDn3vvl9373nnJkO1bm6qrp6qiiKwg0YMGDAgAEDBgzoHAt1n8WAAQMGDBgwYMCAgfEaMGDAgAEDBgzoEYPEa8CAAQMGDBgwoCcMjNeAAQMGDBgwYEBPGBivAQMGDBgwYMCAnjAwXgMGDBgwYMCAAT1hYLwGDBgwYMCAAQN6wsB4DRgwYMCAAQMG9ISB8RowYMCAAQMGDOgJA+M1YMAMxb/+67+WfwPiMTU15d73vvctEFX3m9/8pizvz372s2DYt771re4Zz3hG1vy5NIGej3/8465v/OUvfynz/uEPf+gmFV20wYB+MTBeA6Jw/fXXu3e9611u3XXXdYsvvrhbZpll3Ate8AL31a9+1T344IPlZAkTV+gPMwQw8e+6665u1VVXdYsttphbeeWV3atf/Wp3/PHHNyZE7u/5z3++SvP//d//lXTdc88906K1x03vAw88UOYP7dIHzjvvPPfe977Xbbnllm7RRRct21TD9773PbfRRhuV/W/99dd3X//619lwf/vb39xuu+3mlltuubKf7rzzzu6GG27oqBQDBuTD3//+93IMXnLJJUO1zkAsMm4CBkwfnHzyye71r3+9mzVrlnvLW97iNt10U/fII4+4P/zhD27//fd3V155ZSkleOYzn1nFue+++9x73vMe99rXvrZkrjxWWWWV8vPggw92n/jEJ8oFFBi6tdde2915553ulFNOca973evcnDlz3OzZs6t4b3rTm9y///u/1+h6+tOfHmRk/ud//qfcKcIiPOkYN73AeEH+gD4kZtDW3/3ud91mm21WMvTXXXedGPZb3/qWe/e73132jf3228/9/ve/d/vuu29J80c+8pFav/u3f/s3N2/ePPf//t//Kxm6L3/5y+7FL35xuZituOKKnZdrJuI73/mOe+KJJzrPBzZxiyyyyALNeMEYBMnWc57znLG0wYDusOD27AFRuPHGG90b3/jGkjE688wz3WqrrVa922effdyf//znkjGDxRP+PO64446S8YJnb37zm2tpgmoDmK7/+I//cHPnzi0XRw9g5E477TT36KOP1uI897nPbaQzDsDd8g899JBbYoklxk3KtAf0D2CaoC6BcZcYL1iM//u//9vttNNOlVps7733LhehT37yk+6d73ynW3755cvnhx9+uPvTn/5UStOe97znlc9e+cpXlpuFL37xi+4zn/lMjyWcOcBjtEuANHPAeNtgQHcYVI0DTDj00ENLKQKoeTDT5QFSrg984ANRtfmxj33MrbDCCu773/8+O5nsuOOO7lWvelWrFgJxPTBxgHXWWadST4LqEvCDH/zAveQlLynVmyDJ23jjjd0RRxzRSAd2nkALMINbbbVVySSA9AVw0003ude85jVuqaWWKtP5z//8zzIc5EPVdeeee657xSte4ZZddlm35JJLlhKYP/7xj2Z6JXz729926623XknX1ltvXUqCKEA6edBBB5UqPcgf6H3hC1/ozjrrrCoM5OMliLDj9vl7e5vLLruslMR5VTOoh9/+9reXUspUgPTTwsACnZAPqCUxgPG///77S8bfAxgzYLg80wXYcMMN3Utf+lJ3zDHHJNH5qU99yi200EKVatPbRkF6n/70p92//Mu/lHUCecBGhOLYY48t6x7KutJKK5UbCFCHevziF78o04M69jjuuOPKZ1haDABV6xve8IaGTdoJJ5xQMpfQlzfZZBN36qmnmsv3+OOPl9JBaFPoG9Cnb7nlFtW+yJsAfOELX6j6IOQN9X7++ec38vD0QT3B589//nOWFmrj5U0YoF69JBj68Nve9rZS2kkZdJCCQh0/7WlPK8sB9dzGbgw2mzBWoF4gb1BbX3311Y1wkM9ee+3lVl999bIeYAzDxgLGHuCuu+5yH/7wh92zn/1st/TSS5cqcNgQXHrppVUa0K98v4Xy+THo7c44Gy/o/x/60IfcmmuuWea7wQYblG0CG0Rar5Z+cu+997oPfvCDZT4QBua1l73sZe6iiy5Kqr8BdQwSrwEm/PKXvywX2+222y5LjYE04pprrikXbZgcrYBJFqRoGDABS7tAWLBAgnLUUUeVqiaYjAGeuQAmCyYemJxBtQHlhIUdpCiwoGNce+21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1h//+Ed38cUX13b5jz32mNtxxx3d9ttv777whS+UzMSqq65aesSHex89YwQMWqzhMjh0BXphkQRGAhawU089tWRowGgc7JfA2z7cMYkBz2Cx3nnnncvfjz76aEmPBbDYgoqui3oF3H333aVtFNwesNtuu5VxPvKRj5RXFb3yla+MyhNs6H70ox+Var4jjjiirOPNNtusZJzA1uu8884rHeUCtttuOzaNBx54wL34xS8umep3vetdpfNOYFwPPPBA949//GMsdnpgLwVtDv0KGECgEcoHfQz6XywTD9f7Ql+A9oB22mijjco6gzwogLmDfgUM8X/913+VjN8xxxzjdtllF3fccceV9mzQdjAGoN+96U1vKjcdvn+ff/75Zf3B9VP/8i//UjJQQDtsRoDhhvHRBkD7Jz7xCXfQQQeVnu2BOdXad8CALMh02faAAQskll122WKfffYR3z/yyCPFyiuvXGy66abFgw8+WD0/6aST4HL64qCDDqqe7bnnnuWz//qv/2qks9NOOxVrr712Eo0QD9I97rjjqmfz5s0rVltttWKLLbaonn3rW98qw1199dU1+ldaaaWSNo+zzjqrDGf5u/HGG6t4L37xi8u/HPXq04M8fvSjH1XPHn744WLVVVctXve611XPfvCDHzRoweWAT4+DDz64fPbPf/6zFhbKv9RSS7F1i+vmk5/8ZBnuuuuuq4WDNl144YWLm2++OVh2iQYKyDfUJ+69995iueWWK/bee+/a81tvvbWsY/rcghNOOKGk79BDD62ePfbYY8ULX/jC8jnUt8dLX/rS4tnPfnbx0EMPVc+eeOKJYrvttivWX3/96hm0DcT9/Oc/X8vrgQceaOR/9tlnN9rd1xkF1/a0H55//vkNugcM6BKDqnHAgBaAnfq5555bGuhyuOCCC0r7IFCXgJrFA1Q9YEgNKkSK97znPdnbZPXVV6+dlltmmWXcW97yllLiceutt5bPQGIENOI7Jk877bTSrQE2iN98883La1ksfyCt66JePUAygmkDG6ytt966VJ2OA6BSBqnJ8ssvX9ab/9thhx3K66R+97vf9UoPtAGoAEGShOkBlxjbbLNNeZl5LE455ZTy6h3cTyG997///bVwd911V3koAfoVqI593iC9BOkbSGGpup0C1IseIGmFuCCBhP7BqZ0HDJgOGFSNAwa0ANgTgYoF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5MlIdMyqYBJF2rUTSBFGqJzKBn+y4dGxdk4Nq6WbAosYntajVcRvVd3WymRQ23L2MhW9Bvsn/52WR1J5cc/a9utQ/+C+03CeZs2OjPYZre1C7SzFpWE41SSXlpZOCNH1r7kLCMRrfB99VjQIJ44b5Y6x46J5ILQuB4Myn5E6UK1b5eqcMl5mL/WW/MV+RdqCugxRTVz6cFg7u2gfTrK5HsPJxmjG621ve5vba6+93Je+9CX3hz/8ofz74he/6N7xjneU7wYwMPi9scBk34X8ouC8Q0xYI73UQZRw1JcuNNhGhV10EIMjpUHjV4vpyEZBtBEgR9SDNglCWZNs8chixUFbnCwLvZYuhkSzVhbOjok+1+Ja7JFoHhyzghmoEBMjMU6UCaMMWagcFjsUrY497aF2SNpYCH6aOreZCc0LgU0i+ztQJ603sxLaMBkJjG0ovKVPxvj+on0d90W8Ucm9vk0lzDutMY7DGrEisscff7z43Oc+V6y++urF1NRU+Qff4Rm2+1rQ0LeoUhKf5hK3J6k5+rYNC4jSo/KgKhBLWINq16Le4tRSmrqqMxhs0TQVmqQ6w89S07bEo/lJ9czRmQKp3WLiWPpCm/SsaXDfQ2Gzwqres74L2U21QLY6mATVXEp5hbRb1cscW+TY8Tap63crsoHIXIQecsghZcE/8IEPVM8efPDB4r3vfW9pO7bUUksVu+66a3HrrbfW4t10003Fv//7vxdLLLFE8fSnP7008H/00UdrYc4666xiiy22KBZbbLFivfXWK37wgx808v/GN75RrL322sWsWbOKrbfeujj33HN7abiYSa8rnbh54rLkNVq8O2UY2hhZBuwuzAwUZzSfebKLXcTHjbZ9OVT32vtUJqpNHWoMqMQEckxp33SH0pXysPyOzSM1DNfe0qKs1hWaq9r0o7aIZix7RluGvm3euUHTnFaMVy6cd955xTOe8Yxis802qzFe7373u4s111yzOOOMM4oLLrigeP7zn19st9121XuQsG266abFDjvsUFx88cXFKaecUhr/H3jggVWYG264oVhyySWL/fbbr7jqqquKr3/968XCCy9cnHrqqVWYo48+umTKvv/97xdXXnllsffeexfLLbdccdttt5nLwDVcq8lx0k7BjZseQbokTZTl79TTVAk0NfJOAaZjDIa3seiS8TMtmsY0uHQsUjsr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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_reads.plot_reads(\n", + " mod_file_name=extract_file,\n", + " regions=ctcf_target_regions,\n", + " motifs=['A,0', 'CG,0'],\n", + " window_size=1000,\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " regions_5to3prime=False, # if set to True, regions on the reverse (-) strand will be flipped so everything is 5 prime to 3 prime\n", + ")\n", + "plt.title(\"CTCF target data, 100 known binding locations\\nsort_by=['shuffle'] i.e. default\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot Browser" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The read browser plot lets you see modification locations and probabilities in the same visualization. While the `pileup` operation and downstream plots by definition requires a threshold, but plot browser requires raw probabilities. Read extraction can be run with *either* binary modification calls *or* raw probabilities. Run the cell below to re-extract the single reads but with raw base modification probabilities for plot browser." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "from dimelo import plot_read_browser" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This is expected to fail. Read the error message below:\n", + "\n", + "\n", + "A threshold has been applied to this .h5 single read data. plot_read_browser must be used with an .h5 file extracted using thresh=None.\n" + ] + } + ], + "source": [ + "try:\n", + " plot_read_browser.plot_read_browser(\n", + " mod_file_name=extract_file,\n", + " region='chr1:114357437-114359753', # you can only browser a single region, not a bed file or list of regions.\n", + " # The only cap on region size is your computer's memory and your patience\n", + " motifs=['CG,0','A,0'],\n", + " thresh=190,\n", + " single_strand=False,\n", + " sort_by=\"collapse\",#you can sort by anything that the loading function accepts, but the special \"collapse\" option doesn't allow other sorting\n", + " hover=False, #turning off hover will remove the hoverover read names and other info \n", + " )\n", + "except Exception as e:\n", + " print('This is expected to fail. Read the error message below:\\n\\n')\n", + " print(e)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No specified number of cores requested. 10 available on machine, allocating all.\n", + "No valid base modification threshold provided. Raw probabilities will be saved.\n" + ] + } + ], + "source": [ + "extract_file_no_thresh, _ = ensure_extract_output(\n", + " input_file=ctcf_bam_file_updated,\n", + " output_name='ctcf_demo_extract_no_thresh',\n", + " ref_genome=ref_genome_file,\n", + " output_directory=output_dir,\n", + " regions=[ctcf_target_regions,ctcf_off_target_regions],\n", + " motifs=['A,0','CG,0'],\n", + " # thresh=None -> raw probabilities in read vectors\n", + " window_size=1000,\n", + " # cores=1, # uncomment to reduce memory usage\n", + " # cleanup=False,\n", + " # log=True,\n", + " quiet=PARSE_QUIET,\n", + " modkit_executable=MODKIT_BINARY,\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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ERCB3EYgJ5VHCnpOqi3lVQREQAREQAREQAREQAREQAREIJAIZGhepKBnKE4lJgIiIAIiIAIiIAIi2OdBUAAAAkVJREFUIAIiIAIiIALnRyC5gn5+6SiWCIiACIiACIiACIiACIiACIiACIhAOghkuYKejrwqqgiIgAiIgAiIgAiIgAiIgAiIgAjkWgK5TUHPtRWlgomACIiACIiACIiACIiACIiACORuAlLQ01S/CiwCIiACIiACIiACIiACIiACIiACmUNACnrmcD2/VBVLBERABERABERABERABERABEQgzxKQgp6Hql5FFQEREAEREAEREAEREAEREAERCFwCUtADt25yWs6UXxEQAREQAREQAREQAREQAREQgXQQkIKeDniKmpUEdC8REAEREAEREAEREAEREAERyN0EpKDn7vpV6VJLQOFEQAREQAREQAREQAREQAREIJsJSEHP5grQ7fMGAZVSBERABERABERABERABERABPwRkILuj5D8RSDwCSiHIiACIiACIiACIiACIiACuYCAFPRcUIkqgghkLgGlLgIiIAIiIAIiIAIiIAIikBUEpKBnBWXdQwREIGUC8hEBERABERABERABERABEbAEpKBbDDqIgAjkVgIqlwiIgAiIgAiIgAiIgAjkFAJS0HNKTSmfIiACgUhAeRIBERABERABERABERCBDCMgBT3DUCohERABEchoAkpPBERABERABERABEQgLxGQgp6XaltlFQEREAFPAjoXAREQAREQAREQAREIKAJS0AOqOpQZERABEcg9BFQSERABERABERABERCBtBGQgp42XgotAiIgAiIQGASUCxEQAREQAREQARHIdQT+HwAA//9U08DdAAAABklEQVQDAEJ1OfxvymLuAAAAAElFTkSuQmCC", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "from plotly.io import to_image\n", + "fig = plot_read_browser.plot_read_browser(\n", + " mod_file_name=extract_file_no_thresh,\n", + " region='chr1:114357437-114359753', # you can only browser a single region, not a bed file or list of regions.\n", + " # The only cap on region size is your computer's memory and your patience\n", + " motifs=['CG,0','A,0'],\n", + " thresh=190,\n", + " single_strand=False,\n", + " sort_by=\"collapse\",#you can sort by anything that the loading function accepts, but the special \"collapse\" option doesn't allow other sorting\n", + " hover=False, #turning off hover will remove the hoverover read names and other info\n", + ")\n", + "# plot browser is made using plotly, so you can use the returned \"fig\" object to make updates using plotly commands\n", + "fig.update_layout(\n", + " width=1000,# these values are in pixels\n", + " height=300, \n", + " title={\n", + " 'text': 'DiMeLo-seq CTCF and CpG methylation',\n", + " 'x': 0.5,\n", + " 'xanchor': 'center',\n", + " 'y': 0.75, # Adjust the vertical position of the title\n", + " 'yanchor': 'top',\n", + " 'pad': {'b': 10} \n", + " },\n", + " xaxis_title='chr1 coordinate, t2t v1.0',\n", + " yaxis_title='reads'\n", + ")\n", + "# Comment out these lines to display an interactive plot with e.g. zoom and pan\n", + "png_bytes = to_image(fig, format=\"png\", width=1000, height=300)\n", + "display(Image(png_bytes))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When using the read browser, one can optionally request plotting of a random subset of the reads using the `subset_parameters` argument:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA+gAAAEsCAYAAABQRZlvAAAQAElEQVR4AeydB2AURRfH/5dCAgmkQELvvXfBhiiKigVBwN6wF1ARuwgWVJDPgoKIolgQEBQRlKIgNkQQ6UjvLdQ0AunfvLns5S655O5yJXfJP7nZ2Zl582bmt3O7+2Zm94Jy+UcCJEACJEACJEACJEACJEACJEACJFDqBILg1T8qJwESIAESIAESIAESIAESIAESIAEScIZAYBvozrSQMiRAAiRAAiRAAiRAAiRAAiRAAiQQAARooBdzkJhEAiRAAiRAAiRAAiRAAiRAAiRAAr4iQAPdV6QLl8MYEiABEiABEiABEiABEiABEiABErAQoIFuQVHWdtgeEiABEiABEiABEiABEiABEiCBQCJAAz2QjpY/1ZV1IQESIAESIAESIAESIAESIAES8CgBGugexUllniJAPSRAAiRAAiRAAiRAAiRAAiRQ3gjQQC9vR5ztFQJ0JEACJEACJEACJEACJEACJOB3BGig+90hYYUCnwBbQAIkQAIkQAIkQAIkQAIkQAKuE6CB7joz5iCB0iXA0kmABEiABEiABEiABEiABMokARroZfKwslEkUHICzEkCJEACJEACJEACJEACJFA6BGiglw53lkoC5ZUA200CJEACJEACJEACJEACJFAEARroRYBhNAmQQCASYJ1JgARIgARIgARIgARIIHAJ0EAP3GPHmpMACfiaAMsjARIgARIgARIgARIgAS8SoIHuRbhUTQIkQAKuEKAsCZAACZAACZAACZBA+SZAA718H3+2ngRIoPwQYEtJgARIgARIgARIgAT8nAANdD8/QKweCZAACQQGAdaSBEiABEiABEiABEjAXQI00N0lyPwkQAIkQALeJ8ASSIAESIAESIAESKAcEKCBXg4OMptIAiRAAiRQPAGmkgAJkAAJkAAJkIA/EKCB7g9HgXUgARIgARIoywTYNhIgARIgARIgARJwigANdKcwUYgESIAESIAE/JUA60UCJEACJEACJFBWCNBALytHku0gARIgARIgAW8QoE4SIAESIAESIAGfEaCB7jPULIgESIAESIAESKAgAYZJgARIgARIgATyCdBAz2fBPRIgARIgARIggbJFgK0hARIgARIggYAiQAM9oA4XK0sCJEACJEACJOA/BFgTEiABEiABEvAs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rA4JkAAJkEDZIMBWkAAJkAAJkAAJkICrBGigu0qM8iRAAiRAAiRQ+gRYAxIgARIgARIggTJIgAZ6GTyobBIJkAAJkAAJuEeAuUmABEiABEiABEqDAA300qDOMkmABEiABEigPBNg20mABEiABEiABOwSoIFuFwsjSYAESIAESIAEApUA600CJEACJEACgUqABnqgHjnWmwRIgARIgARIoDQIsEwSIAESIAES8BoBGuheQ0vFJEACJEACJEACJOAqAcqTAAmQAAmUZwI00Mvz0WfbSYAESIAESIAEyhcBtpYESIAESMCvCdBA9+vDw8qRAAmQAAmQAAmQQOAQYE1JgARIgATcI0AD3T1+zE0CJEACJEACJEACJOAbAiyFBEiABMo8ARroZf4Qs4EkQAIkQAIkQAIkQAKOCVCCBEiABEqfAA300j8GrAEJkAAJkAAJkAAJkEBZJ8D2kQAJkIATBGigOwGJIiRAAiRAAiRAAiRAAiTgzwRYNxIggbJBgAZ62TiObAUJkAAJkAAJkAAJkAAJeIsA9ZIACfiIAA10H4FmMSRAAiRAAiRAAiRAAiRAAvYIMI4ESMAgQAPdIEGfBEiABEiABEiABEiABEig7BFgi0gggAjQQA+gg8WqkgAJkAAJkAAJkAAJkAAJ+BcB1oYEPEmABronaVIXCZAACZAACZAACZAACZAACXiOADWVMwI00MvZAWdzSYAESIAESIAESIAESIAESMBMgFt/I0AD3d+OCOtDAiRAAiRAAiRAAiRAAiRAAmWBANvgMgEa6C4jYwYSIAESIAESIAESIAESIAESIIHSJlAWy6eBXhaPKttEAiRAAiRAAiRAAiRAAiRAAiTgDoFSyUsDvVSws1ASIAESIAESIAESIAESIAESIIHyS8B+y2mg2+fCWBIgARIgARIgARIgARIgARIgARLwKQGPGeg+rTULIwESIAESIAESIAESIAESIAESIIEyRiBQDPQyhp3NIQESIAESIAESIAESIAESIAESIAFbAjTQNQ9uSIAESIAESIAESIAESIAESIAESKB0CdBA9wV/lkECJEACJEACJEACJEACJEACJEACDgjQQHcAKBCSWUcSIAESIAESIAESIAESIAESIIHAJ0ADPfCPobdbQP0kQAIkQAIkQAIkQAIkQAIkQAI+IEAD3QeQWURxBJhGAiRAAiRAAiRAAiRAAiRAAiQgBGigCwW6skuALSMBEiABEiABEiABEiABEiCBACFAAz1ADhSr6Z8EWCsSIAESIAESIAESIAESIAES8BQBGuieIkk9JOB5AtRIAiRAAiRAAiRAAiRAAiRQjgjQQC9HB5tNJQFbAgyRAAmQAAmQAAmQAAmQAAn4EwEa6P50NFgXEihLBNgWEiABEiABEiABEiABEiABlwjQQHcJF4VJgAT8hQDrQQIkQAIkQAIkQAIkQAJljQAN9LJ2RNkeEiABTxCgDhIgARIgARIgARIgARLwOQEa6D5HzgJJgARIgARIgARIgARIgARIgARIoDABGuiFmTCGBEiABAKbAGtPAiRAAiRAAiRAAiQQkARooFsdtqzsbOTk5FrF5O+mpKbhVFJKfgT3SIAESKCcEmCzSYAESIAESIAESKCsEZg6cyGWLV8LsQlLs2000PPonzmbgf6DR+DHJSvyYsxe2pmzGPL8u+h+9UO4oO8Q3PTQKzh+MsmcyC0JkAAJkICnCVAfCZAACZAACZAACficwN6DCXj4uXfQ+8bh+GzWIiQln/Z5HaRAGuiKwrhJM9Hlivuwc+8hFbL9fDVnCbbtOoBfZr+DFfMnIjgoCO9+/I2tEEMkQAIkQAIBQoDVJAESIAESIAHfEnBlRjYjIxMJx04hN9f+ql7f1tx3pbna7tNpZ3H0eKJdTrIi+sixk5AJWFdaMHLYurfQuQAAEABJREFUHZjxwYs4t3NrjJ0wHedd+zBeeuszbN253xU1bsvSQFcI77npKiyZ9Raqx8WokO1n4S8rMeDqixBfLRqVIyvhtgGX4dsff7PbGWxzMkQCJEACJFDuCLDBJEACJEACJGBFYN/Bo2jf624cOnLcKrbwrhjkEz+bi46978UlAx9Hj35DsW7zzsKCZSzG1XaLIf/06A9xTp8HcPGAx9Br0DBs2LLbQuXPVRtxUf+h6DVwmJ6AHf3uF0U+wmzJZLXTtmUjjH7mHvz+3XsY/sAN+PWvteh/9wjc+dgbWPL7vz5Z/k4DXR2Q6KhI1IiLRWhICAr+7T2QgHq1q1ui69aK1/vJqWna98vNvlnAr1cDf90BnPyncBWT/wN+HwAs6ARseadwujdiss8AO6cA614A9kwDcrNdLyU3Czi+Ajgw1+yXRIfrpXo3x4m/ga3vArumAmcOFl/W3q/VMb3LzPDIT8Cm180ucX3hfKJr1UPAkkuBLW8VTi8Yc3gR8Pe9wL9PAokbbFNPq5Oe6Fp2FbDjI9u0gqEjS4BVj6g6Pg8kbVJuozpe3wMJvwCZyQWlGSYBuwQ2qC54ySXAOecAF18MvPZavtipU8BDqmt37w489ZTqVpn5abJ35AgwZw7w1VfAJtUFJc5whw8D334L/PADsHgxkKVOKUaaK/6HHwL16gFduwK/qK7tSt6CstK2Dh2ADh2A19VXumB6wXCJwnLuXP+iOh/0Ut/xYUBWUe9TyQFO71Pf281mHypcogKZqUgCR38FNrwMbH0vj3GRkq4l5GSo43oayD6r8hUx6ybn4EMLgN3qGnxcXXuUJD8kQALeJSCPxl55i7pYOVHM2k07MOHTOfjiveew9qePcd0VF+Lxke+7ZFw6UYzfibja7p9+X43lygif/dFLWPvzFFx0bgcMf2ki0jMy9WPI9z05DtdfdRFWLZiEOZ+8ClkNPXfRHy63OzgoCCEhwTD+Nm3dg6EjxuPi6x/DtG9/NqK94tNALwarjOjIM+jhYRUsUmEVQvV+WppcBIGjR4/6l0s4hNyt7wNnjwLKEM/Y8nGh+p3Z9CFwYiWQrkby1E3CscO7C8l4ul3J+/5UN33qbjlbDWycXI3E/atcLjPpyBYgbT8gNyLKT0rY6rIOT7fLHX3HEg4iN0HdrGWlQozz9IO/F9me44d3ADLwkpmkOG5G7iFloKcfU8fwGLL2/1go3+ltM83HODNRDYh8hRP7VheSsdRd9ZmcXcqaST8BpO5Exs6ZNrJpW6cDp9YCGaeAXZ/i+KEiuCccRs6er5XcSaVnNzL2fKfq+p86XumqnseRdnSjjV5L+f72HWJ9Sv04TZmSCTGmIyMBkwn4WV0H16w5oes1a1YKli4FEhOB778HFi5M1PFGf1q1Kh3H1anttLJVVq7MxZEj+efo5cvTEREBdcEFZOXgf/+l2OQ1dBTnH1QzISNG5ODAAWD1auCVVzJc1mHo37PnGGbMgB4okMGC6eqrJvqNdE/5iTvVaMShHwE5fxz9Dcnbv7db56TjB8wyMvipZJOOH7Ar56l6lTc9xxIOIPegGgwVQ/n0XqTvX+wRvidOqA6fkwn9p45dWmqSXb1pR9aoa80hIEfdvyRtxIkjO+3KlbfjwvbmnyPLEgv9ffCDzbsvD8H0iSOcqsnSP9bg3C6t0altM4SGhuC2Ab31UvetO9XAqVMaAlPI1XZPn7ME/a68EC2b1keoMqCH3t0fBw4fw47dB7E+b8XBXTdeiUoVw9GsUR3cPvBy/KyMemfpbNy6GyPGfqKXt7/x/ldo3awBPh73JP7+4QPIoMCF3dphyvQfnFVXIrmgEuUqJ5lMJpM+uDIiYzTZ2K9UKdyI8jO/iJHzImuZC5NcrItMZ4JfEBBrAspS8VplXNFdlGyube0KBG0TGSKB0ieQk+N6HeSrqC4Nrmd0MkdmZlHfLycV+LUYK0cCJEAC/kNg3bp1+Pzzz0vsli1bhoSEBBtXsHXyiGz1uNiC0XbDh4+eQMO6NSxpklcC8py1+MW5Z16bDHmn1r6DCcWJlSjNXU6Sv7iCXW13ZmYWgtTstqEzJqqy3hU9oaHmidQgqwu1rH7erwbXtZCDjTxvfsP9L0Eecb73lquxeMY4vDf6UT1wEhRk0oMCrz17r56Zd6DKrWQa6A7w1a9THfusOvv+Q2pmWuWpEllJbYH4+Hj/ctVrw9T8ESA8HqjSEhVa3FOofhVb3g1UPQcIqwY0H4pqtVsWkvF0u6rUOx+Iag0EK26xnRFdt6vLZUbVaAFUqgsEVdB+VPXmLuvwdLvc0Rcnx6r6RUCImiasWBthtS8ssj3VajUF6g0AQqMUx1Yw1bpMHb847ULq9imUL6LZDeZjHBoNNLgZVet1LiRjqXv1WghqdJPSVRWIbIwKjW+wka3UXKXFdAAqxACN7kK1Ws1s0m30NBik5NSFKLIhKjS8TtW1pTpeYZC+Vim+jf18/vYdYn1K/TjdfXcoatYEUlOhZ7ovvRTo2LGqrtfAgZUhy9+jo4FrrwWuuCJaxxv9sGvXMFRTpzaZKT/nHBNq1Ii3pHfvHgaZWZfZarl2t25d2ZJm5Hfk16kTj5dfDkKdOkDnzsCIERVc1mGU0aBBHG64AXpGPyQEuEl91STOSPeUH91YnS9q9YE+f8T3QJWm19qtc1Q11Sg5x5iCtayEPVUHn+jx8+9uXPU6MNW+XLGtAkTUR1jd3naPQ7yL7ahaVXX4oFB9TwJ17CpFRtnVW6lGR6BiLXVOVhMMUW1QtUZju3Kulk/5/HMMWfgPC/MXovitGI7jx69T53TgpZfgkj9+/Hq88847GDZsmI0rvsTiU5NTTiM8TN0zWYlVUrPAqWlnrGLs77Zv1Rgz5/6CK295Wr+FXJ7Dlhel2Zd2LVY4rftpPLDxZWCDAuWCv17lmzt3LmQww9pZ18DVdl/Zqxs+mjYf8pNoi5atxJsTZ1jUtWvVCLHRlfHoiPcgaV/PW4ZZ836xpDvaia8WjTeeuw+/fzcej907ALVrVLObJapyhN14T0UGeUpRIOvJys6GjMZIGzLVnZuxL+HLe3ZVB3aZfktg6ukz+GL2T+jfpwdMJpMk+6erNxC4aD5w7mdAbJfCdYxuC1w4G7jyX6DFY4XTvRETXBForAYG2r8KNLgFUDcRcPXPFAJU6w7U6Wv2S6LD1TK9LV+1G9D8UaDRnYAy0ostrr4yfs/9FBCGNS4DWj9rdtHtCmcTXV0nAr1+Vsd4WOH0gjE11U1jt4+ATm8C0j+s0yMaAqKr5w9Ak3utUwrv1+ilZN9XdRwNPSCjbgJRR1lR1S8GQqsUlmcMCdgh0LYt9DL2lSuhn/F+7rl8oZgYYKLq2itWAGPHqm6VZ5cYEjVqAP36ATffrL4erY1Ys1+7NtC/P3DVVUDv3tCGsTnFte399wP79gGrVkE/I+9ablvp558H1q4F1q4FnlVfadtUD4VMClI7dWPVa4n6jr+lGl65CMVBQEQ99d1tZfahwkVIlsdoj7Q5Xg3Ktn1RnfeH5DH2iFbogesQdcMYHK4UmpSz85FzcK0rgYbqGlxNXXvsiDCKBMobgaSk9ti793Z1Tr/dJV/y9VcXlGnTpsHaucOvijL60jMybFTIo7aRldQ9tE1s4cBN1/XCb3PGQ2Z3E46dwn1PjsPVtz+DGXOXQuyXwjlci2lfMwm3d9yL2zvtM/ud83wjXNDPS5d8+9QF848//oC1sy7d1XbfMfByjHj8diz/ZyO++eE3pJ1N1+pqxleFGM5fvv8CainD+stvfsa/G7YhOzsHdWvHaxlHm6jKkYiMrIhwq8ebJY+8k0yWvZ85a3t8JM0bjldfRfWpVz5Eh8vu0c8vvDBmit7fve+wSlE3ef0uRaP6tfRbArtd9aA25IcMVnd4OpUbEiABEiABEiABEnCbABWQAAmUAgGTGs8KDgaClEXkii/5PF1dMTD37D9iUWssbZdZXUtkMTsVwyug7+Xn6+ek5bn32OgqeOXtzyH2y5gJ02HYNsWoKCbJBAQpUCYFSnwZwBXfCBf0Lekm9OjRAy+88IKNg9Wfq+02mUy4se8lmPzmcO1qxsfqR5LFXhO19etUx6tP361ftveiMuR37j2E7p3UwLMkOnB/KaN/yw41Al9ArlLFMP0rXvvzVlIXSPZ4UFH2uM6AU/jWqIewadlUG9ewXk3IX0SlcHzwxuNYPm8Cfv32Xcz8cKT+yTVJoyMBEiABEiABEiAB/yfAGpIACdgjoGw9uGqci7zks6evYFxmVjYyMs0vcZR9cYbM1K8X4rYhrxlBXHJBR8jS9H83bIfIfT57EarHxaB543oWGUc7MlssOmQJ+JqN27X45T3Pwez5v6oZ9Wcx/OUPdJzLG93gIECvXnXBhwmO/i5x0O6CnGTp/rETiTibnoHf/16Pj6b9gKF390dFNUAhZZ04laz5HTl2EqPf/VIveb/+qh6SVCKXlZ2Nv1Zv0nljoytr39sbRdjbRZQN/bJkolpsVNloDFtBAiRAAiRAAiRAAp4iQD0kEKAETMp+dGXmXIxzkZd8zjT5vGsexhU3P6VF+9z6NC4ZkP9o6bHjibCere3QugkeuP1aZbSPRodL79bPlP9v5ENqAEFVUmsoepOYlIrPZi2ClCHL27ftOoDnht6iJxhlIvL378brWeXTeb9CVbSmolJUHcQ4NynT0SVf5StKZV68o3YX5HQ2PR09r38MnS+/D8+9/hGefOhG/cb7PHX4YvZiza/XwGE4cSoJsz96Wc+wG+n2/AuvG4LWPe/E0j/X4P1P5uh9CYtr3+tuPPvaR5DHnn1lCyrK9qrJOBIgARIgARIgARIgARIofQKsAQl4i4AY3OLE6HbFF1ln6rRqwSSbFbq/f/eeJZsYlpJuRJhMJshjtKsXTcZPM8bpn/Xq2KapkVysP2LsFIydMB1NGtbWy74XTBuLW/pfpp/JlozyTLX8NJmsCpawy04b5spsdMk4V/Ky1N1BYSZT8e0uyElenLdk1lv6mXvhOeianjYl3HfrNVj41Vis/XkKJo15Qq9CsBGwExh2/yA8/+htaFy/Fs7v2kbvS1jcqOF34vvPXsNbox62k9M7UULOO5qplQRIgARIgARIgARIgAT8mwBrV44JKNsQrhrnIi/5vIVNjGl5yVlQkOPZZ6MOvS7ojNkfv4wJrz2mDUwjrywDP3jkOHJzcw3REvqqLqa8Z9DFF8NbfG24q/giwyqfkyW60u4acbGoGmP/5cPyvLj8tFpoiNTLucJl8OLmfr0w+tl78eyQWyD7hht4dU9tuDunyTNSNNA9w5FaSKAEBHKAA3OB9S+a/dzsEujwUJb040DKDuDsEaWwBCfxM4dVG74HDi0E0k8oHfJRekTv6b1AximJKNpJvtXDFIuRwPG/i5ZjCgmQgOsEstOAo78D+78DUncVnT8zScnMAXZ+rM4HW/PlTu9RcVOA3dMA+a4bKYIvHGkAABAASURBVGkHgK3jgc1jlPw2I9Z5X84LB+cBe6YDcp5wPiclrQlkpQJb3gaW3wLsm2WdYrsv5/f96viKk33bVM+GcjOBzESzy83yrO6A08YK+zMBMbSDlDUkRrcrvuTzp3Yt+WM1li1fU6hKBw4dQ+8bh+PI0ZNw608abBjj4hsviDOM9KLCTjyD7la9PJBZnjGXt+XLIEbbFg1hvIfMA6pLrEJ1yRLnZUZFIDk5GXRkUJI+cPrQavNN1dHftJ924LdS6UupSco4T90NbUSf3o8zSYdcrkf2PmWcn1IXhhN/I2vfPJ3/THKC+WY+MxlQN/KpySd0fCFWJ5VxLzf5csLPOq1vMFNOuV6HQnr53bTPm1zKHZf0g+occ+wPIPk/QBlnyUmn7DLI2KO+x4cXACdWInf7ZCQnndRy2ds/NRv4R35C1o7PdJx837L+e0/p+1YNzC1AzobXLfGS5ozL3DsfSPgVOLkaObumqfxJyvF64gw7a5mzW6eqwc0RgAz4rrgLKcd22OWYvVcdK3WOhnKyb63D0/vZZ9RArZzPlctKO263Pp4us9zq4zndbv9St+hOfcTudNU4F3nJ51QBpSwkL7uWKiSnqoFa2SmxMwFijBsz5U77Kh/8+++Pvzeg65UPYN/Boxg2aoLN8+ete95pE05KUfepPmgODXQ3IZ86dQp0ZFCSPpBz6Ceb3pdz5LdS6UvZaUdt6pF79qRL9Ug5sRfBGTLzblYTnH5Y589NV7Mn5ii9lRs2e5wy9/0AhFfXMsYm89garcOePOP4fWMfcK0PBJ/eaXy1gJwMnD2x1e73Kzh5vUXOlJWCs0fXamMv+MxeS3xw2h5L3uDkDZb4oLTdSE3YbElz5hgFnza/YViUBGWeRNqxLS7ld6aM8iADWYUgEPNc1q7pdjkGnz2YJwHIvrfYJKkBoGBT/oow2fdWWdTr2rmgJLwCNY+lszvYkVlzcWJ0u+KLrAPVPkmeOXcpPp2xADv3HsLq9dv0voTFTf5yHh578X39FvMmDWq7Vx+ZRBGjXGbKXfF1PveK9nbuenWq4/7brtHP61/T+zw888jNRTpZhu/t+oh+GuhCwQ1Xv3590JFBSfpA5XYPABVizb1P+ZHth5RKX4qq0QIIqmCuhxodrRTXzKV61G7YFqjW3ZwfJphq9NT5K1VtBPNoKwBTKKKqN9HxBVlV7XA3kJ0OGMsgK0Qjtlkfu7IF8zLM7x77gOM+EFK7l/oOhkD/RdRDfKPudr9fwQ1vAEx5cjEdEd/0EtRu3Amo2RvmP/X9rnudJa+p8Z0qOkg59ak3ALWa2tdb1DEKqnOVypiXv2pX1GjczaK7qDyML3y8w88ZB4TmPYsZ2RgxXZ+xyxHVeyreJrNT+95iWbdufSAk/6eITKGV7dbHW+VTb+E+4sdMvNY3VEd36mNSX4ngYEAMbvENZ4QL+ka65HOqAC8LfaKM83GTZmLvgQT89c8myL7h5OfH6ivj880XH0RwcJCbNTEBQQoUlNO+0mfXL5hucrNc72dvVK8mht59PaKjInHxeR31G+FvG9Dbrh9WIdT7FVIlKLpqyw8JkIDvCSijHD1mA90/hfYl7PtaACZ1GohpD0S3AWLVzXhQGFz+q3k50OJxoNWTylg/15xdjP6oVkDlpkCUGgSQEVdzSoGtOnlf8BXQ9EGl41GgzQsqXcWpLT8kQAIeIFBFff9aPqa+Y/cDDW4pWmFsF6Dz20D70UpWfR8NyQY3q/i3gK7vA7WuMGKBeoOAi74DLpwFNHskP97ZvWrdVFkvq+/8c0B9NTjgbD7K2RKIbgv03Qtc/jfQZx0QVMQNZI3LzKzbKN6yb6vFsyEZMKhYExAn+57VTm0k4CQBx2JB6hZInBje9vyQEPWVUjIF000mx7p9IbFo+pv6LfF3DLwc8rbxTcum6rD48ob4sSMeQPdO6l7M7cpIgxUIwyg3KUNc7uuCFCDxLeG8eCMs95hul+1dBfsOJmDT1j1OuezsHO9WJk+7Ip23R48ESKAUCKivYGRDVa7y1bZUP8EV3StebsLs6QgOd05vleZApbrOyVKKBEjANQKmUFhW7BSXUwbowuIKS8gAYkhE4Xj53tuTLyxpP0bOD6LbfipjnSUgRnlUa8fSMrMtzrGkByTkuibOA6qoggS8SECMb8OJ4W3si19UWIx5eLFOrqp+6uGbIG8bdzWf0/JiaGvjXBnq4osBLj4chZ0uodQE35w4A4PuH+WUS00745N68szpE8wshARIgARIgARIgARIgARIwJ8IiKEtToxx8YuaMS+YLoa7t9vhSL88d/7OR7NxKikFS/9cg89nLSrSpWdkOlLnIF0Z4nqmvMCMuRjqOj5Y5VdmpX5MSnwjrPZVij9/nnzoJsyaPMopF1nJzcksJ0H4PzUnG0IxEiABEiABEiABEiABEiABEnCWgBjaYnwbvhjpEjZ82RdXMF3CzpbhLTn5CbWPps1Hckoa5v/0F8ZMmF6kk99DL1AP14LSYDHGjZl08XVYGeIyk15Uusywu1aSz6Xr1Y5Hq2YNnHLBbj/L71zzaKA7x4lSJEACJEACJEACJEACJEACZYiAYYgXN3MuBnrBdMlX2hguOKctVi34EGJgvjXqIcuz5/L8eUEXVdnOI0pw5U+ZjIZRbsyYW8LB0C8YtYRFVuLEVw7+/efblQjOsfB/as61g1IkQAIkQAIkQAIkQAIkQAIk4DQBmfgVY1ucGOL2fIkTZ50u+ZwuxEuCMptbqWIYTCZZfu6lQixqVRkyY17IOFempBjmRrzhS5yWV/ksOjy440FVPl2J4GS9FVUnJSlGAiRAAiTgNoGzZ4FTp4AzvnnPiNv1LaggIwNYs8bsZN9IT0sDjh4F0tONGPu+M3K5ucDu3cCnnwI//wxIHmtthw8Df/8NnDxpHet4PycHWLwYGDkS+Ocfx/JFSSQnAzNnAgsXmuvx33/AL7+Yw1JXqf/x40BCApCVZatFwsJv7lxgzx7bNCO0fz/w6qvA//4HfPKJmeuffwKzZwNJSWYpKWPTJmDePODECXMct54nIMfziy+Al18GNmwANm40Hxs5Likpni+PGh0TkO/Ql18CTzwBbN5sK5+dDezdC+zaBWS68MitfJ+2bTOfb+T8bKs1cEKJieY+mpiYX2dpW37I//fkGpKaCsixtK6tnPOfegqYMgU4fdo6xb19sW0N41sMcHHOhCWfeyW7n/vDL+bh4efeccqlnVE3H+4UKQ0WgzvImBkXXzkjrH2rsMhq5/8Gur2VCAVXIBhh91ciOHcQaKA7x6lIqUx1BaDLVBdCOvYD9gFHfSA5OUsbmxUqAGIspqZmB9x354svcvGFMljEffVVjq5/YmIWxFiUG+P164GUFPt94dSpLIgxKwaoGDppaflyWequW24kxR08CDz3HCCcxMidM0eMdLPs7t1Z+OAD4IcfgHfegRrsMMc7Yi/pkydn46WXgCVLgMceA9auzdL1lzRX3C235OLJJ811EAP5xReBESPMhtvzz+diz55cbTQnJhqGQn4df/ghVxv3f/0FTJoExc22DomJmbjxRiAxEZrz+PHAVVcB998PjBoF3HILdJ0XLszWBsqECcA99+TzcaUdlM0/LkWxeOihXIwbB3z7LXDrrWYnAyVyXAYPztXHoqi8jHfMtySMnnoqBy+8AHzzDXDllcChQ/nlrF2bq41zORetXGn+rjhTxvLl2XpQ7PPPgaefhhpAzdfpTH5/kElIyNLnFhnMkwHE48cztZEr1xoxdjMzbc81/lDngnVITs7WA70ySCIDsUb64sVZGDTI/D18/XXg3nsdf/eKvHEvkCB2p7F8XQxzcc6EJV8BVT4PSh2C1MYZ537llKEts+J6htx4UZyYkcpZXgyn9gulq3zuF+5VDcHBQbC3EiEp+bR+AZ8XCneoUkg6FKJA0QR27twJuvLJYJcaoqfbpW6G6JztB4cOnUZYWP75JDNTbiYDh9/WrXuUgZ1/sd2+3Vz/ffvUlHJ+s9Tsd6LdfrFvn5oWsZLbteukRS4pKV/HsmVAQ/n1QSvZdeuOadnly22nLX/77ZSOd+YYLFiQC3UvY9E6bVqa03kN/f/8sxfr1pkQEwNERZlVWc/k79ghRkE+Ixlw2LXrkC5n+/Y92LYtP01y//WXbR1mzjyOiAhg3z5JNTvr1RZ79gC//XYAixblz4ZI+uLFCboMo5703f9e/fvvXqxfb7L0GblxNx8R83b7dhNWr95L7rvcZ+1Kf5XVKuYjAH1sPvggSR+DnTsPqAFQE4w/WeGzdetBneZI/y+/5C9pklVOP/101Kl8jvT6Mv3ff/PPocLg4MH8Nkk4Le2M37fp5Mn885qcO/fuNZ/XZs1KhcxsSzvE/fOPCRs37im2PSLnjJPvtTjRbzgjXNC3Tpc0Z/R7U+a+W6/Be6MfdcpVqujkT94WVWFtnAcDQcqZlPkovhjjNr5Kk7B1uuwXpdMP4+V3zid8Ogddr3wA5137MC7oO0Tvj5/yDdLOOFgi6MH2KMJuaGNWtGjRgq6cMmjevDnoyMCVPlCvXpTNEvDw8JCA6kNt2jRFq1b5J/7mzYN1/Rs1is2PVHvNm8fp+IJsGjaMVqn5nxYtqlvkqlbN19GzJ5SRny8ne1261NKyPXooy1gi8lyvXvE6vmBZ9sK9e4dAbvrysuKOO6o4ndfQd+65TdChA9TMPSzLzWPzq46mTU165t8oQwYEmjevr8tp1aqp8qGNCuT9XXihbR1uvbUmZOl0vXp5AsqrWFFt8j4NGgC9ejVEnz7Kis+Lk/Srr66jdPP7aBwnT/jdujVB+/aw9BmZicxDrr1mzYDu3ZuQu4+vhZdcEqT5y0a+z0OHVtPHoEWLhqhUSWLNTgZD27RpoNMc9YdLL400Z1LbcGXHXHVVbafyOdLry/ROnaxORKod9etXUdv8T5Uqlf2+TXFx+ec1OXc2aWI+r910U7ReDWC0pmtXoGNHOZ8Wfc4zZB35Uo4Y3mJwF/SNmfSC8RKWfI50ezs9KztbGY1n1Tkq19tFKf0mQIxvMcpNyhAXPyhExavvoxFv+DbpJiUTOJ+v5/2CiZ/NRed2TfH4fQPxzCM3o0v75pDHCV4Y87HPGqKo+qwslwtiBhIgARIoSwTk5lGczOzIzYAYVoHWPlnme9ttgDhZii31j1T3tq1bA/XrA+3awWaVgKQbroq6X2zZEqhbF2jTBpCbHyNNfLnhEVe7NvDaa4Bwql4d6NcPCA0VCXPeBx80L/t+7DFAyjanON5KvpEjxcAF3nnHXAfHuQpLfPkl8Oab5jpcc435+eRXXgFk2a08Oy4cqlYFoqOBRo1s8/fpA71U89xzgQcegLphtk2X2fOvvwaio6EZDx1qXkr/4YfAqFHAtGlm+csuMz+j/vDDwMfqnqEgS7MUt+4SkEcIhg8H+vcH5LiLGzAAkOMyZYq72pm/JATGjDE/TnL99cCCBUDVsmmvAAAQAElEQVRcXL6WDh3M3zn5DooRl59S/F63bubv7+23A6JfDLDic/hfqpwrb7gBOP98QPzoaEDaIdca8eXc6n+1tq2RnP/i46FXKNWsmZ92ySWAnBfle/jss8Dkyflp7u4JF4ORK77kc7dsd/P/8fcGPbu77+BRDBs1Aa173lmkS0o57V5xusFBgCkYEOO8oF/QODfSYVLygfORn61r2bQ+PnhjGO65+SrcNqC32n8cTzwwCIuWrcKBw8d80hhF2ifl+GMhrBMJkAAJ+JyAzM7I8uhANM4FljwX3rGjzF7AZqZYBh7kxkpmrUSuKOeMnEldz2WJ+113AZdeCptZMdErN25yQ209cy3xjpzcfPXuDf0cepcujqSLTpeBBrkBvuIKQOohgw4XXwxIWNon9a9WDZAb5oKGs4SFX9++QIMG9suQAQwx9uUlWIMHA8JVbrrFMIyKMueRMmRQRAYIZDDAHMutpwnI8ZTBKHnPQNu20ANLcmzkuFSu7OnSqM8ZAvIdkoFCeYmi9YoeySuGqBjnMjBmDOpJvCMn3ydZESHnGzk/O5L31/ToaHMfjY7Or6G0LT/k/3tyDZGBVzmW1rWVc+3YscDdd0M/BmSd5s6+8JGy5Prgii/53CnXE3nr1amO+2+7BvLismt6n6dne2XG154LD6vgZpHqwixGuEmZjvZ8MdrtxfsDKBdaHhcbDXlpnMmk2muV7+pLz9Oho8cTte/tjaLs7SLKq362mwRIgARIgARIgARIgARIwF8JiGEuzhXjXOTFlXabGtWriaF3X4/oqEhcfF5HPdsrM772XFiFUPeqK4a5GOHGzLizvuRxr2Sf5r7soi5Y/OsqyOMD1gUn561AqFc73jraa/s00L2G1suKqZ4ESIAESIAESIAESIAESKDEBGSi1FXjXOQlX4kL9VJGeev4zLlL8ebEGRgzYTpk/8Qp25cHlrxoNaMsRrkY6i75Kl/JC/VJTlnWPnTEeIj7Z90W7D2QgLseG6PDEifuyVc+0HVx+2V7WovjDQ10x4zKpQQbTQIkQAIkQAIkQAIkQAJlmYAY2jIbLka3K77k8ycu+w4m6LeOv/z255j69UJ8PmsRZL9Hv6H4/e8N7ldVGuyycS5mpv8b6DJbnpmZDXEm1cYe3dsjMqKiDkucuBrxVSHxgC9eyIcAW3fgfveiBv8gwFqQAAmQAAmQAAmQAAmQQKkSELvTVeNc5CVfqVa8QOGvjf9Sx3w1cQTW/vQx1i2ZghmTRqJrhxb6BXJnzmbo9JJvTID1zLmYkE6FVT7499+Dt/fVL4L74I3HHfqcQffvY+nV2mVlAQkJwLZtwK+/AvPmAT/+aP5Zn+IKlnzp6Wpsx2pwJzUVWL4c+OYb4JlngJtvBhYtMms5dgxYsQIQ3xzj+a38XNCRI+a6Z2d7Xr99jYwtDwSkP8n35NQp2z5f1tq+axfw1VfA+vWB3c7Tp4HvvgOmTwfknODOcTpxApgxA/j7b8D46Sv5uSU5B4rvjm7rvGfPAsuWmd/ebc1f+p6cm+WN6omJ1jkAKX+DmqyQNw4bb16fO9e582xyMrBwofl8v39/vt6kJGDWLED07N5tLiM/lXskQAIkUDQBOSdt2gT89hv0vWXRkuUzRWbNxYnR7Yovsv5EbNuuA7i5Xy+0b9UYoaEhCFENatuiIR68o6/+Kbbtu6wuKiWpuJpZhnbB0L7xQjjDSC8qLIY8+OcqAVl74GoeynuRgNxs7twJiNu8GTh8GJg5E/oG/ckngTNn7BcuP0ckxnBaGpCYmC/z3XfAH38A8gZa+bmY+fOBgQPNN5zvvgtIWHxvGOlyE330KKDOEZAb55Mnof382gXoHqvtFwQ2bgR27ADke7J3r19UyeOVOHAAkJ/tEqNt3DjzYJvHC/GRwg8+AMS4FONcjPTExJIVLDeb110HvPwyIG95F0NYNIkxnZkJiC/nUYlz14kBLoa4nI/nzMnnL2255Rbg6acBeYu61MkoSwZERfbbb4GVK80/eSRvAl+7FpBBCkOuoC9Gv7wdfM0aQAx8GZT57z+z1J13Alu2AHv2AN9/D6xebY7nlgRIgAQcEZBzmAxky72YXDNlINBRnvKUbjJB36eKwS2MnPUlnz9x6tC6CVJOFzYSmjeqq6sZ5om3uJuUcS4Gt0u+SZcfKJuMjEy889Fs3HD/S+hz69OFXEqqMrR80Bga6G5C3qLumjzpDh5M1L/9KzPf8lM+cjI1qig3nj/+eEjdqG0p5E6eVNNThqDyd6mRsuXL9+nfGVZV1Ea79clkwQI1Ta/kjM/SpccL6XS3XQkJZ9UonlGC2T906ITHy3G3nv6Wn/Up3L8LMtm0aSfkO2LuVVAzshllsl8tWpRmM6i1aNHZgGznypW79XnNOF7iL11asnPBN9/sgwz+iQ5xs2en4cCBI7JrcSdOJLrNaeXKPTbliPK//87QeufOVVPrEqGcDDrMmbNHx0s/XbkyHXKuPXgQaNdOCVh9/vnnpEVOZK3dokX79ey73BwaWVatSsWSJbsgA69yPTDiN206W6Qea53cd3wuISMyKut9YNeuDH1OMs4fa9aklIvzh9FeR76cr+W864pxLvKSz5Fub6fvP3QU/23fq935Xdti3uLl+HPVRh024n9YsgKyLLth3RruVUc3WJmNMlMuRrqzvs7nXtG+zD1lxo+Ql8bVrF5VvyyuW6dWuOSCTjh2IklzDJHfefRBhRRpH5RShoto0qQJPOmqV4/Uvy0svwEpo5xKvYWe/H5y797xdsuLigq3yMlOvXo1cc45tfTNfYsWQHS07bLI3r1tR7R69Ii2q9edtsXFVYDMaEl9DBcfH+XxctypYznMWyb4N29eHxER+c9yxMeHlIl2FeyPvXqF6UE24/tz2WWhAdnOTp3q6vOa0Q7xe/Qo2bmgb99aqFZNNJhd//7hqFHDKkJFR0dXdptTp051ULWqUmb16do1ROu95poKltgGDWQWvY6Ol+PXpUuoNrRr14Z+LMEiqHY6dKhikWuiTu7W7pJLakJu+qxn/zt1qoiLLqoHmYGX64FSoT8tW1YoUo+1Tu579vpMnuQZiH2gYcMQfU7SJw+1adeuUrk4f6imOvWR825xxrmkiSso5w9259gJ0zHg3pHavfjmJ7q99z05ToeNeHk2Pe3MWZxJz9DpJd8ou8GuUa5m1e3GBwFGfMkL9XnOZX+uxd039cFrz96ry77rhisw/IEb8NTDNyLh2Ek18ajaq1O8u1H0vFtAWdcuIymedBUqhKBxY2jXqhVQsyZwww3mZ8fffBOoXDlE3bAXdhUrBqs06Bu56GhYZK67DrjgAvNy0IcfBq6+GvpZxjvuCMajj5rD4tesWVhniBolcsfFxQUhPh6QpZtKFWJjgfBwz5fjTh2Z19PHw3f62rY1qZsMQL4nDRsGWfp8WTqmDRoEQ5Y9X3EFMHy4fJeDA7adDz4INGwIZUwDN90EZWSXrK/Is3WyhFwe2/n0U+DGG83HXgYwQ0PlHAN9AfVEP5Bl7BddZD4f9+sn/M1lPfxwEGT5+5gx5seEpE5GeRdcEASR7d8fapAU+PNP6BnwDh2AqKii2yznxldeATp2BNq2hX5fSJs25uM9dSogA60NGgDXXgt07Wquh1Em/aK5kg3ZlPc+0LNnEGJizPdialxQ7ZvPK2WdC5z8E0Pb2viW+1XrcEHj3EgXGSeL8JrY8AdvxIwPXnTKRVQKd7MeQSq/ckEhZt9Y5m4Y4ZZwgXQBrHIEyufYyUTUq11dzZaH6SofSjih/W4dW+JkYgq27zqgw97eKNLeLoL6XSUgX/7q1YFmzaBmT6CfcezTB+qkWrwmyRem+pP1d0Fm4s87D7j+euCNN6CfZb/8crOeuDige3dAfHOM57eVK0PfkMvFQU5yni+BGssVAavGSn+S74n0Les+byVSJnYbNTIba7JcOpDbGREBXHcdtHFeo4Z7h0Zmtm+8EejWDTBukoSNnAPFd097fm4x+nv2BG69FXq5uqFb+p4Y7mLAR0fny8ueyIiBPWgQcP/9wKhRQN++zp1nZRm7DMbI+b5uXdFmdlFR0O8OET0yyCFlmFO4JQESIIHiCcj5onVroEcPQK6ZxUuXv1ThI+d0wzkbFrnSplW/TnW0bdnIKScvjXOrvtJgbYybAPHFIBcfJjgMu1WwbzPHRlfB0RPml+Sc26U1fvr1H12Bg4ePaz/IuOnQIe9taKB7jy01kwAJBBgBVpcESIAESIAESKD8EBB7S5wY6OLLQK/4jsIi42+Ujp9Mwu9/b8CiZasKucwsd39KSUxG5UwFZ8gdhVUefwNVTH0a1aupGK7XEn17n48Zc5fi2juewz3D30SzRnXQvLHV6LmW8s4msKh5hwG1kgAJkIAvCLAMEiABEiABEiABPyIgE8OGMW7tFxUvhrnI+VETdFXWb96Ji/o/igee/h+GjZpQyMlz6FqwpBsBIrPmJmU6ysy5ddiIN3zrdJlhL2mZpZDvxWF34P3Rj+qSr+l9Hl5+cjBkpcLDd/XDpDFP6HhfbBRlXxTDMkiABEiABLxLgNpJgARIgARIgARcISB2pmF0G74Y4AVn0iXOOl32XSnH27IfT/8BLZvWx1cTR+ii5nzyKpbPm4BeF3bCJed3RFTlCB1f8o0JEONcjHAEq301c26EC/o26SofAucvMqIiqsZUgfF3/VU98J4y2B+6oy+qx8UY0V73aaB7HTELIAESIIEyQIBNIAESIAESIIEyRsCk7EfD+DaMcjG+xRnxhm+dLvn8CcWO3QfRv08PtG7eQFcrOztbG+V33XAllv65BkePm5+r1okl2UiDLcvbxXxUThvreb420pXRLj/BJvvaV4Z8gM2gC5oVqzdj2KiJ6Df4Bb28Xfbl5+skzVdOUfVVUSyHBEjAPQI5wJlDSkWuck58ctIBcU6IapGsFOUp3TlngYxTat/6o+IhzjquwP7Zo0BmcoFIF4LpJ4Bcd5+RcqE8ivoVAVaGBEigDBI4c7BsnNdzs9TBcXANVBK++6j7gUw3DS7fVdavSxJDXJxhhFv7si/OXrrE+VPDsrNVn1AVkpfB1akZh215bxuPjTbPBh84rO7RVHrJP8pkFMNbL19X+9pXBrj2VViMdXvpElfyQn2eU4zzu58Yi0XLViKuarRe3i778vN1H02b77P6KKI+K4sFkQAJlJRA+nFgzXPApjeA9SMBMaKL05WyA0hYZnayX5ys3HgkLAGO/gbs/hzY8Brw3/+A/d+ac2UrQz8zVRnfyuVkmOMKbre8Dfx6LbD0CmDPVwVTHYd3f2kue9sHahBC3dA5zkEJEnCFAGVJgAR8SUAGW9e9oK4nLwH/DgPS9vuydM+WJYPPZw6rNqhrU7YawPasdte1yQD63tnAwR+AA9+r/GbDTO3wUwICMjFsPTMuhrczYclXguK8lqV2jWrYvG2P1n9e1zaYOPU7LP3jX3z4hfQRoFmjcPdCUgAAEABJREFUujqt5BsToI1tZToWmkkvOHNuHVb5EDh/YyZ8hdjoyli9aDImvzlcL29f+/MUDLy6J975aDZOJclklvfboyh7v5CyXMLevXtBRwbe7gOJu34BstPMX6WMRJzYvrTYfpedus8sq7bZKXuKlT1+YLPSnXfTITchyLvYn1qHA3u2qEltZaArPfLJSj9TWNeencjdb74AQOXN2j27sEwx35Mju9cA6cdEPZCbidMH/3Ypv7fZF6V/z5492ENHBroP7CEHcmAfsOoDCdvVAHF63oxd9hkk7frZ7/nYO9cfPqiupZaVaLlISz5W6tenlMNr9bVSXzSzUnB07/pSr5M9dqUdp/k4sTEp+1GMcnsz5RInzl665HNCvc9EBigDslb1qrq8B2/vC3kp3JAXxmPuoj8x/IEbIM9W68SSbnSDldlozJiLL8+aiy+Gu/iyrF18S1jNsOt8JS3U9/nSMzL1owLhYRUshYeGBOPW6y/T4d37Dmvf2xtF2ttFlG39sbGxoCMDb/eBCtGNbL5IFWMbFNvvEFwxXz4koljZilWq28gagdygMERVrYWcXHX1gvnPpK5ShdpaNR45leqbBdTWVKlWseUVzB8RW0/lClLO/AmpFO9S/oL6fBWuWrUq6MoWg2rVqsEvHevlk+PC77Pnvs+RVRuaT+h527Do+n5/vrR37YisEp3XArMXHBpe6ten0ErVzJXJ21aKrlPqdbLHrrTj8vA49MR+NIzwkBBA3ebACBf0rdMln0PlPhTo06sbHrrzOl1ifLVo/PLNO5g1eRRWzJ+Iu268Use7tzEBQQoQggCTMrzFD8rzLWF76SYE0t/5XdtgU95KBOt6R0SY76urxUZZR3ttX1H2mu5yobhy5cqgIwNv94FKNdoDdfsBMcpvcDMqxbUstt8Fx7YFKtXRTvaLq19ElDLQo9sB4TWA+J5A3PmqnA4wNbxFlVEFwRUigKBQ5SogOCxCxVUu5ILbPgPUvhqoNwjBbZ4slF5c+ZWjqgJ1rgIqN1Vln4ewGl1cy8/vIHl5qA9ERkaiPDq22Xzciz1PeaiPlZcyIqqqQeXG96prSSegbn+E17ogQM9TVYCwOCC4EhBaBWGRVUu9HeHVWiquHYFKdYG4CxAZVa3U6+SP/dpZIyQoCMUa5cHB9tMln7Nl+FJu++4D+GX5Gvy6fB1CQ0Pcnzk3Km8KUnvKWYxxBUaMdCNc0DeMd5FROf35s2z5Wkz79mftKlUMx1//bML4Kd/osBH//iffQtJqxMX6pCmKtE/KYSEkQALuEJDnfapfDDS+G6jWXWkyKVfMJyQSiGptdrJfjKhOkgt9bGcgWhn2tdRIa70BQER9nQQ5KQeHA8FhKlzEKaNKC6DNC0DLYfn5lLTTn8gm0AZ+1W5AkCrL6YwUJAES8HMCrF55JVC1K9D0AaDmFeq8XiFwKcj1L0wNJIdGqTYUcQ1UKT77yP1AVCsgvkfJrrc+q2hgFGQyAcHK1hSD2xVf8vlTC1NPn8Gtj4zGdXe9gEeeexdDR4zX+7cNeQ37DiZ4oKomQIxuk/oOiC+Gt/hGuKBvSTd5oGzvqvhu4R94bfyX2n381Q+6sA+/mKfDRrzIyGMDZ9IzdLq3N4qyt4ugfhIgARIgARIgARLwBgHqJAESIIGSExBD21XjXOQlX8lL9XzOdz+ejTUbt+OJBwbhy/ef18vbn3zoRhxKOK4N9qxsN38lRzdYmY0mNZohxrezfgD8zNo7Lz+CTcumOuWiKkd4/uDZ0ahI24llFAmQAAmQAAmQAAmUdwJsPwmQQJkmIHanKzPnYpyLvOTzJzALf1mJy3p0weAb+6Bjm6Zo1awB7hx0BYbdNwg79x7Crr2H3ayumgkXo1xmyl3yVT43Sy6t7CdOJePQkeNwe3CjBA2gge4A2pLf/0XrnncWcvKWPwdZmUwCJEACJEACJEACRRJgAgmQQOkSEINbnBjdrvgiW7o1ty29eZN6qBEfaxupQhec01ZtgbNn83+RR0e4utGGuTIbXTLOlbzMtrtaVinLz1nwOy68bgh69BuKy24cjva97sZLb32GpJTTPquZkPNZYYFYUC5y9UsBfvxyDKxdhdCQQGwO60wCJEACJEACJFA+CLCVJEACDgjITLirxrnISz4Hqn2aPPDqnvjmh98KGZEyey4VadygtnhuODUTbm2ci+HtVFjlc6NUX2ed/9NfeGHMFAivx+4dgJefHIyrenXH19//gmdGT0Zubq5PqkQD3QnM4WGhqF+nuo0zmQKrwznRTIqQAAmQAAmQAAmQgJMEKEYCgU9AbudlNlyMbld8yVfarR82aiJa563yHTZqAuQlZudd87AlTtJuH/qarmZ2To72S7yRButZ9GBAfOMFcYaRXlQ4AJ5Bh9XfjLlL9SMCU995BvfecjWuv6oHxo54AKOG34nfVqzD4YQTVtLe26WB7gTbk4kpeO71j/Tyhh+WrCiVZxGcqCZFSIAE/JSAXBe//hpo2RKIigIaNgR++81+ZU+fBhITAfHtS/h/bEYGsHs3sHkzcNjBY2/SzjNnAGFkDEynpgKjRgFdugDt2gHPP5/fZpFJTASOHgWSkvLjd+0CfvwRWLoUOHYsP97Yk3zvvgvceSfw8MNmnVKuke6K//33wMCBwP33A2vWAFlZ5uMl9c7MdKzpn3+AOXPMfaCkdXBcCiU8TUD60Al1byZ9+tQpT2v3vD75Prz3HvDBB+a+5vkSvKyR6knABwRMJsBV41zkJZ8PqldsETKz++SDN8IZF1YhtFhdjhNNgEkZ58bMudO+yofA+dt74AguuaBjoQpfcI66GVGxB48cV1vvf2igO2BcPS4Wd914JRrWq6kln3plEsa8/5Xel81RdZdId1TdLNOxH7APFNUH1qxJxLx5wMGDgHGT/+KLmYW+N8ePJ0GMW5ER/9ixpEIyRZXhT/EHDqRBjFV5aexxdS07dOiE3XYcO3ZKtzc8HDDlXcPT0zOwdm0mFi+G1iEcvvkGWLXKrOPYsRSkp5s5nj0LpTcRCQlHsXFjDsTYFcNpw4YMFW/bH1evPoXly4HoaCBU3afIwMAvv6QUknPE8dCho5gyJUcPAmzdCkydmqEGUzItAwxnzuQUq3PHjlPYuRO63WLobdyYWqy8o/ow3fY4e5NHQkIypM/JYFJaGlS/S/TbY3fgwDH89x8Qkvc03qZNwJEjvmPlzePgKd3UU7b7A5z8k1lzcWJ0u+KLrJNFeE2s14WdcOcNVzjl3DbQZdZcjHKZKXfF1/m8hsDjils3b4DvFvyhrum2S9nnLf5Tl1W/Tg3te3sT5O0CAl1/2xYNMfyBG/Qyh5HD7sArTw3GV3OWoDTe6BfoLFl/EiABEvAGARnQKIlemfl2NZ+UZQwmuJqX8iRAAuWGABsaIATkfG5tnMu+ODHA7fkSJ07y+VsT5QXW8oKzN9RE4shxn+KjafOxXw0qe6aeahRdG+dqFl37yoS06xdMV/k8UwGfaLn3lmv0W+8v6j8Uz7w2GeMmzUS/wS/g3Y+/Qf8+PRBfTY3y+6Amiq4PSilDRcRVjdGtycoy/55gfHw86MiAfYB9oLg+0KFDNK65BqhdG3qmuGpV4OWXQwudO6pVi0KFCmYZ8ePiogrJFFeOv6TVqVMJkZGA3MRUqwbUqlXVbjvi4mJ0e2VWUgxfqL+wsAro0CEUvXtD6xAO118PdO1aVeuIi6uMsDBojjLzHh8fjRo14tGmTRAqVgRi1Cm6bdsKWtaaR+fOMTj3XCAxEZBl6BERwGWXVS4kZ53H3n7t2vEYPDgIcXFA8+bAnXdWQEREKORmTm7YKlYMKlZnkyYxaNwYut01a0LVO7JYeXt1YFzpnG+qV68C6XNyrCtVAqpXj/bbY1enTpx+pMYYhGrdGvp7wr5TOn3H89zZDkdM4eSffJ/FyfXKni+rUOzFS5yTRfhE7PjJJFx7x3P6BWdfzF6MH5f8jXc+mo0rbn4Ki5at8kAdTEqHMhsNo9wUnBcOMfuWcF68ETapPEoiUD6d2zXDF+89B3krvvyS16czFiA7OwfPPHIzXnz8dp81I7Co+QxLfkEyW756/TacOZuBI8dOYvKX89CtY0uEq5vIfCnukQAJkEDRBOTCP2gQ9JJTeW5ans/u0cO+vBiO0dFQRp/99ECIFaNanrNv1QoQI7S4Okt7xbCWmx0xcEVWjPtRowB5Vnv9emD0aIk1O5GJjoYyjKCf5zfHAo0aAX36AJdcAm08G/GGL/keewyYOhWYMAFap5RrpLviX3stMGsW8OGHQMeO0MuIpR1Sb1k+70hXly5Av36A9IGS1sFRGUz3PAHpQzK4Jn1aBoI8X4JnNcr3YcgQ4MEHzX3Ns9qprUwTKGeNk2u04eR7buyLX1RY4v0Jk8zwnkxMwWfvPot/F3+EVQsmYd7nr6PneR0wbNQEbce4VV8xtLVxrgx18cUAFx9OhN0q2LeZ//pnE46dSMTH457UDDf+8im+/+w13DagN0J9+AteQb5tduCVduToCcgbELtccR96DRwGeb3+y08NDryGsMYkQAIkQAIkQAIkQAIkUMoE/Kl4GRwWJ8a4+EXNmBdMF1l/asefqzbgpusuQZf2zWE8b96oXk0MGdxfV3Pn3oPaL/lGGeLy7LmpwIy5GOo63pg5L5gu+Upeqq9zzpq/DN/88JulWJOpdOpPA91yCOzvDLt/EFYvmoyFX43Fn3Pfx5fvP486NePsCzOWBEiABEiABEiABEiABEigtAi4VK7YX2J8G74Y3hI2fNkXVzBdwi4V5GXhqMoRSE5JK1SKvbhCQs5ESIPFGDdm0sXXYWWYy0x6UelivDuj309kmjSsg70HEkq9NjTQnTgEspy9bq14REdFOiFNERIgARIgARIgARIgARIgAX8nYBjixc2ci4Genw5IHrFH/altF3ZrB5n9FScGZlLKaSz9cw3+N2kmYqMro3njem5WV5mMJnHKIBejWxvnRljFycy63XSTm+X6Nvuga3pCHhX4/e/1vi24QGmKbIEYBkmABEiABEiABEiABEiABEigjBMQQ1sMbnFiiNvzJU6cdbrk8wqaEip98I7rcH7XNhg1bir63Po0zrvmYQx5/l0cSjiOd18ZitAQZUSXULc5mzK0xSgvZJwrU1IMcyPe8CVOy6t8ZgUBsZU34KedOYsHnn4LrXveWcjJwIcvGqKo+qIYlkECJEACJEACJEACJEACJEAC/kNADG3D+BYDXJwzYZHxn1YAFcMrYPKbw/WjuCOH3YEnH7xRGeZDsGj6m+jUtqmlqiXeEVBicOvl7Mp8lH1xRlj7ahBA+1bpYqiXuFDfZ7zy4m6anfCz52RVtS9qpQj6ophyUEZmIrBvNrB9EpC8pXQbnLQJ2PSaqssHwNkjpVuX8lx6xinVJ2aZj0Pyfx4gkQvk5phdZhJwao3ZZSYX1p2dBhyYB2ybAJz8p3B6wZicdNVXjgHpJzsCb2UAABAASURBVJX+LNvU9OPAhlHAnzcBh360TSsYOrUWWPMMsPlNIO1AwdSiw9JP/3sbWDUE2PkxkHW6aFmm+D2BbduAmTPNbvt216sreeQt5x06QL+Z/Ycf8nVkZACT1Gl28GBg+vT8eMkzcSLw6afmciVN3pZvSBxRp8I16iuzfDnw66/qFKlOk8ZPuxky9EmABEiABMoXAbE7Q0IAMbgN50xY8vkTqRfGTMF9T45DxzZNMejai3HnDVfg0gs7o1LFcA9V0wSYxGxUTpaz65lyta99BVD7QYD2rcMmFRc4n14XdtLshJ89Z7yAz9stCvJ2AWVdf3JyMsRl7J4DHJwPHF+B3K0TkZx0UsdLmm9dEnLWvwocXgzsnYnMLZNLqR5mLr5tu3+VmbHra9UnlJF8/C/VJyao45CkXMnrqI1zKCNdudyTq4HTe7TLOrGmkN6zB5YB+78BTqwCtr6PlJMHCslYH5uc0wnKKE4FlOGfmZpgI5u+WVk9G1Wf2qcGG36/HsknD9qk2+j5VxnnB78Hdn2KzA1ji5SzziP7WcIq4WfVnt2q3t/h7L5FTueV/HQl71eeZnf0aAqWLFHjQmqs56Rysp+QkOLS8Xz44SysXAmEq/sK0fHKK8CePWYd33+fhhdfBOar0+2jjwIrVqRq3RMn5uCPP6DzrVLdfrX6inz2WS6SkpKRkJCKDRuA48eBlBTg7FngwAFg3740ndfTDKjPf/ojjwWPBftA+ewDztofhlEuM+eGkzjZL+hLnDiJN/mZ3WkymZCRWWCCxVkIzshp49xqhlxmysUYt/HtpKt6OaPeORnfSJ1KSsGMuUsxdsJ07b6etwy+WtputJAGukGihP6pU6cgLjhpnUWDKSsZZxPW6HhJ86VLO7waQemHLXUJTlxdKvXwZZv9tazgFDVFl3ckpE+ccaNPpKcriyJPF9Rst0kZ0kYwOCup0DE2nVDWjSGg/MyEFYVkDG6nU04hyJStpMyfYGTayAbtV4a5OQnIyUTG7u9s0i16Dv+DoDP7DEkEn3K+7wWnygoDq6vdsT/tlmGURd983vFHDlu3ZsJ6Zlr2t22z7VOO6r16dTBiYixdSe/8/LNZxxw1Fqoj8jbffJODzZtPKyPcfDkToz4vCampJmzZkoZDh+zftOzfH8R+lncNc3RMmO6/3zkeGx4b9oHCfcC4DjjyxX40jO6CvjGTXjBewpLPkW5fpl98fkesWrvFi4akukczjHFZ2g51zQ0KUU0UXxnmOpzn26SrfEoqID6qkvKCvQv6DsErb3+Oz2Yt0u6l/03FpYOewL8btikJ33wUVd8UVFZLqV+/PsQFN7oZ0Es+AMR2RnyzXjpe0nzpajS/BKh1paqE+qgvSFDTe0ulHr5ss7+WFdzoFuT3iS6o3rRniY9FXFw8gLyTXFAYUKUFjD9TlaaF9IY1Vf1R5EQooh5iW15fSMbgVrN2fSAk/xcKgsKr2siGdhkLBFcUTUD8RajW+WGbdIse6Xt1+5nl1HchqNXjduUMeWvfVLcvYNQ3KBThLQY7nddaD/fN56PS5HDhhbGIjobFSI+KAiTOlTqNHGmCLEnPzhs3ql0buPvuWIiOV1+tpPVLR6tVCxg5sgq6d6+Jiy6SGOD0acC4ceraFejWrQY6dIhGpUrQ8UaazIB06hSudYpeuvpkUZ8M+D1gHygrfQBO/sk1Qa4HYnS74ousk0X4RCxINUSWsz/3+keY9u3PhVxGRqZ79VD6IUa4si3s+gWNd0NO8rhXsk9zj373C12e/Kz22p8+xrolU/Rz/fXrVMejI95DdnaOTi/JxpU8Qa4IU7YYAlXPAc55H+iojJnmQ4oR9EFSq6eBC2YAF80Fal/tgwJZhF0CVbupPjFR9YlxQPOhdkVcipTlRXLCE1elJfRAjAzGyH5BRZWbqbJVf+wwGmj3sko1KVfMJzwOiKgLRNYHQivbCkoZ1ycAV28Gei2xTSsYaq/K6/UTcPmfQL0BBVOLDte8AjhvqmKlvj8XfA1EtS5alil+T+BmNT40cCAwQHUB2Xe1wkPV10WeYx+tutPUqcA330Ab11B/DRsC//0HLFsGyDPlkXljS/ffD7z7LvDOO4AsiX/uOeCGG1QG9ZH7ivPPhzLkgV69gE6dgEsvhTbaVTI/JEACJEAC5ZSAXB9cNc5FXvL5E7LvFv4Befv4suVr8dr4Lwu5M+kZblZX3UeKES73ovZ8McTtxfsbKAcUduw5iJv79dLP8oeGhiBEHWx5rv/+267RP7+276C6H3agwxPJQa4rYY4iCQSFA+Ey01mkhO8SwmvAelbUdwWzJBsC3uwTolucTYFWAVMoULG2VYSDXVOIEghSzs4nWE0/itFvJ6lQlJQZUsDILyRkJ0LyRLUC1Ay6nVRGBRiBODXmI66k1Y6NBXr3BlrkLxaxqFLXS7RSXaXgdV/Kk5nycHUqlvyWDHk7YsxLXpErmDdPhB4JkAAJkEA5IhCkbnvEybXBFd/friHvvPwINi2bWqSLqhzh3lEVw1yMcJkkcsWHyb1yfZy7Q+umSDl9plCpLZuqCSwVGxysOozyvf3xTSmutIKyJEACJEACJEACJEACJEACJOBlAmJou2qci7zk83LVSqQ+NzcXiUmp2pVIQZGZlKEtxrkY6i75Kl+ROv0v4YqLz8G8xcvx56qN+G/7Xotb+MtK/Ub8VGW8S/yO3Qe9WvlyZ6B7lSaVkwAJkAAJkAAJkAAJkAAJBAQBMbRdmTkX41zkxflTA+XZ6Pc++Rbn9HkQ5/d9RLuuVz6AD7+Yh3R3nz+Xhgool41zMTPFiYLAcPMW/6krKj9ZN+DekTDc25Nn6UcIBt43Ssfd99Q4LeetTWBR8xYFz+mlJhIgARIgARIgARIgARIggQAgIHanYXS74ks+f2rejLlLMOnz79GxTRM8ft9APPXwTXp//JRv8Pr4aR6oqpoJt545N5a5G0Z7kWGVzwOl+0rF8AdvxIwPXnToJr7+uFerRAPdq3g9rZz6SIAESIAESIAESIAESIAEPEFAZsLFuWKci7y/GehTpv+INs0bYvKbw3HPzVfhjoGX6/27b+qDWfOXuf/za9oQV2ajYaQHBQM6Ls8vKhxgz6DL29rbtmwER65Fk3rw5p8i7U311B1QBLxZ2dxs4NRa4OivQHaaN0squ7qF29kEICfd/9qYk2E+vqfWAMlbgIxTJa7jgQPARx8BX3+tVCWXWI3fZ0xNBQ4dAs6eNVc1QyFcuxbYvBnIyjLHBdpW2rB0KTB/PpBW4Gsu7ZymBvFffRXYsaPolm3bZn5r+8ECj3clJgIbNgD79gHHjwPTpwMrVqivQ94vnqSkAFtU1zt5smjdrqacUt34/feBJ58EPvtMdXEVdkaHHNfVq4EPPgDk7fKyL/mk3lu32tdjsJszB9izB8jNlRz5TtJ37oT++TlJE55LlgDiZD9f0od7uZnqYKiDkLAMyM7ryC4Vrw7eGXWgU1XDcpQul/JS2GUCcrySNgLiZD9dfZFOrATEd1mZBzNIXTLVF9wfr23SzPRjwK5PgWO/q5Dqs2pb6h85hntnAGcPl3pVAr0CJjXB66pxLvKSz5/aHhcbjXO7tC5Upb6Xn6/jjhx19+KoQIlxbsyUO+2rfLoG3LhCgAa6K7TsyGZmZoLOMYOcPTOB/8apO/OPkLvxDTJzsd9knVU3L4nKOjmt7txPrUVmeopfMczdrSyvA8qySP5PWR/KSD+8EFmnj7hcx4SETDz8MPD338DixcDzzytbP8Nx/wq07+CJE1n6J8LE4Fq1Cjh9OlMZgLmYPRv46itg3rwcl9n5A4MxY3IwfDjw4otQfq5NG954w2ywLlwI3Hmnut/dVfi4rluXhXvugf65tJtuAnbvNsucOJGJdevUvehZaAO1Tx9g5Ejg9tuFVzZSUjLx7bfQ/WbePODAgSybskvCJkP1O9G/bBlQsSIgx+qdd1T3PmWuU1E6t2/P1gMJb78NyM/E/fAD0Lcv8Ndf2ZBBBzG0ZRAqISHbpo5vvZWD7dsBGbj5XdkBW7bYpv/0E/DPP8Cvaoxz06ZsvPxyrv45OflJuddfL53+krNtMrBZHdit7yB30xs27SmKj3V8zgl1rhDj/vgK5B752eX81rq4X3y/FD45e9WXZN9sNco1Gzm71QjXtvfVKOGPgPKzUg+UDv8M9aVOVde1s8oIPr0PWX52bcvMUCONP/UA1jwJ/NYP2ds/KR1OVvcMWQfVyWC5OvltHgss64vMM4mlXifpX/7m7Nyy240SQ1tmxMXodsUXWbsKSyny8ou7YvGvq5CVrSbErOqQmqa+Yypcs3pVtXXjI6DEKJeZcld8mWV3o9jympUGuptHfqe6a6PbqW5ei3dZCcriymNtOr0L+7atdJjHRa4+17dr1y74yiUf35tHz+wlJviubEdt3LNrK0ypu4HQaFW5/JHSlCObXOYzd+5JyE9hKUX6c+YM8Oefh1zWs8uHx6ZkZaXq9hmbDRsSsXdvPrutW3MCss1//JE/uyQDD6tX79Xt+O+/g3plgNFe8adPT9Fp1vx++MF2ycR33yVqmfXrUyA/nyb5/lNjQImJsmd2336boQY7bGcGNmxI1fmsdbu6v2TJITVwAlS1uqeRmes//zxZrO49e8zLH8SYNtfQvN25MxfWN3SHD2cV0JNraaPk+O+/dEv6hg2HtOEu8eK2bMlUgxH5/WXNmlyLrKvtdEc+57gaMZAKiVMDiHu3r3WpHlnJyjCTvMqZMk5i/+7NLuV3p+7lMW+unKcVa/1J26c9Y5O8789SYX/yqG09Uk8dLpV6FNUfEjbMAmQGPQ/U2V3flnr9zuz6Lq82ysvNwrHNc0q9TkXxK814Rcepj5yXizPOJU1cQTltrzpVgm+EzpxJx94DCbjrsTEYOmK8xY0YM0VX4IUxH+u4UeOm6rDrG3XNsWucBwN244Py4k2uF8UcMgRCCu4QaNGiBegcM6hQ89x8zJGN0bj1+QHGrXAbmzdvDl+52BqN8/mpvbg6vivbURubNm8DVG6kprqTVM3y1+XG1GnvMp8BA6rbGCJilPXsWd9lPY7qXNrpTZrIYIbClffp3DkOderkBZTXsmVIQLa5R48QVXvz55xzTOjevYluR7t2DdCqlTne2N5xR6xOsz4WffvGGsnaHzgwTst07hxrWTLfsiUQE6OT9WbgwIro2rW63jc2HTtG63zWul3dv+KK+oiIAE6cMLSa/Z49qxeru1GjMIgh36WLWd7YNmsWgpwcIwR1vMNs9DRpEqwHBAyJNm0qWdI7daoP64Gr1q3D0amTIQl07hxskXW1ne7Ih8SfY6mEKaYdmrTq6lI9KkSr84ahIawqGjVr71J+d+peHvMGV8m/jgRFNDDIaz+20UWlwr567fw6SUWi4+qWSj2K6g91Ot4EhMVL1bSLaDqw1OtXudkAXRe9MYWgVvtBpV6noviVZrzm48RGDG1r4zt6D3zSAAAQAElEQVQkBLAOFzTOjXTJ54R6n4kEBQehR/f2iIyoiMzMbIurXTNOx1viSvwMXZBqi3JBCpCYj8Zyd8M4t4QLpJtUHpWTH9cIkJprvChdUgL1BgIthwNN7gVaP11SLWU3n6OWhUQC0W0BuamK6aCuHuGOcvg2vcHNQN3rgCotgZiOQM0r1E1NNZfrEBsLTJgAdOsG9O4NyPPK/nYRdLlRdjLExAAdFabGjaGMSyA0FBg8GBig7rtuViivuspOpgCIkme1x40DXn4ZePNN2wo/8wzw4IPAFaprTJ0KZaDapkuodWvg44+BRx+FfsbcGLSoUgVo3x4IDwdq1ABk2fhLLwGffw7ccAP0EvT+/c395pprzDKizx0n/U709+wJyEoOOVaPPw4bQ9mefpFr1w4Q2eefB+RYzp0LnKNs2dq1gQoVoNtercDX47HHgGbNoPVfeCHQtKmt9ssuA8Tov+giqMFN6OXtI0YA4p4urVNqk3uAVurANleVb1WCSsSog1pdAa7WHajey7bBDHmegJyj66mTjLgG6ovT7BGgVh/V8ZQfrr5Yni/RsUa5qY9UgwXhcUBEPSC4ouM8vpQIqgBc9ivQUZ3QeswBGt3hy9Ltl1X9YuA8dfJr9RTQU51cQiLsyzHWKQJyrg8OVl0vzzkbFiPeqQJ8JPTg7X3xwRuPO3Sjn1Hn7ZLUScBoY9wEiC/fXfHlJXDiFxkuSWHME0QEJOATAvLFjVGGZby6uwyu5JMiy1whwi28ujoxhrncNK9nkJsYOb4xHYEqLYAKMSUuUoyye9U4zqBBSlWVEqvx+4wyI1qrFrTRKZUVw61DB+iZZhmhl7hAc9KGSy4Brr4aNsu1pR3hyri+5RbghReAJk0kxr4TI/X66wExZq0loqOBtmqMqp66hxfjVp5R767sOuMmqXJls+EqgzzW+dzZj1Hd+BFlu8hgwx3qvlzCzuiT49q5s3lA4sMPZYbbnEvq3bw5bFYAmFPUV0bZAcKuXz+gQQNA7oVg9SdsxfiXAQpJE569lE0rTvatRH23awoFtHGtjOzg8BKUq25BKqpRi0g1UhWkdJVAA7O4QECOV1QbQJzsh1UDqqqRI/FdUONxUalLaLR/XtuksWFxQKO7gDg1ciYzhxJX2k6OYf0b1QWkZmnXJODLl2uIODHSxZfrr/iOwnIeDvjGu9QAdb6W/m8KUbnUvtzXOxVWsiqHv3+ys3Pw1z+bMGLsJ7jzsTfQ59antbttyGt468Ov8d9220dNvd2ewKDmbQrUTwIk4A4B5iUBEiABEiABEiCBgCMghrZhjFv7RcUbxrukB1xj3amwNFiMcpMyHfWMuQkwwgV963QoOfj/37sfz8Y9w9/EsuVrUDE8DK2aNUCLJvXVfgVM/24pBtw7Ep/NWuSzhijKPiuLBZEACZBACQgwCwmQAAmQAAmQAAl4noBJ2Y+G0W34YqgXnEmXOOt02fd8bfxZowIlxrkY4wgGZCbdCBf0bdL939Rct3knpkz/Ea8+fTd+mzNePyYw7sUH8daohzD5zeH4a/4E3H1TH4ydMB2pp8/AF39BviiEZZAACZCA3xJgxUiABEiABEiABMolATHQDePbMMrF+BZnxBu+dbrkK1fApMFilOtl7WI+KqeN9TxfG+nG8ncVp+WUIR8AM+jbdu1HbHRl9LvyQphMaiCiwIENUR3grhuv1LE79x7Svrc3QtDbZVA/CZAACZRbAmw4CZAACZAACZCAfxIQQ1ycssFQ0Jc4cQXjJWzHjvPPBnqsVspkNCmnl68bvjLAjbAY6/bSAwBUjbhYnExMwfGT8mtE9oHt2X9EJ8jyd73j5Y0i7OUSqN55ApmqY+z6DDi0AMjJdD6fy5I5wIm/gb0zgIwCvyHksi4XMqTtBxJ+gfVvirqQ2yyadkDx+QEQ3xzjv9vcbNXepcCuqYrzKfv1zE5Tx2IlkLJdpavjorZFfnJVnzhzGJB+UqSQkZALJG8BDi8CMmx/I9qQsPGzz6p6qD6RtBmQ38rd/y1w2vh9WlWv4yuAPV+pY+egv0jddn9hLlf6sJS972vgyGJg3ywgcZ0qVtVNbW0+OemAlCFO9m0SGSiGAJNIgARIgARIgARKSEDsR+uZcTG+nQlLvhIW6ZVsB48cxz/rtuKn3/7RbuWaLUhKPu3BstTMshjgema84Ex5cWGVz4O18Iaqdq0a6xn0QfePwiczfsTvf6/Hvxu2K7cNv/61DhM/m4v7nvwfLjm/I5o2rO2NKhTSSQO9EBJ3IsTwMFwJ9Cy7Flj1IPD79cCGl0qgwMksu78E/rpLlfEy8Gs/lUnqrDxvflJ3ARtHA3tnAutGAGePul6aGI7/PALs+AgQ39+N9M1jgd/6A6sfA5ZeZr+92yYCYgzvnKKM2CX2ZYzYE6uB1B3KyF0PnDloxNr3jy0H1r8IiN7VqvxsB8/MbJ8EfWy2jgdW3AWI/9ft0Eb6HjWQs/xWs75lV6nBowz7ZUqstPefIcAfN6jj/Dx0+ze+AqxV+/+9Cfx9rzLefxJJW7fjY2C7YiFu2we2aQyVIgEWTQIkQAIkQAJll4AY2mKU25splzhx9tIlzh+obN25H7c+Mhq9bxyOOx59HY+9+L52dz3+Bs679mEMHTEeh486MVHjqDECSoxzY8ZcfHnWXHwx3MW3ly5pjnSXcnpU5Qh8Nv45dGjdFP+b9DUeePot3DZktHKv4aFn38aET+eg90Vd8Ooz99hdAu+N6tNAd5Pqli1bIC49Xc1AWunavXu3jpc0Z9zOdYuBk6ssGjJ2f+NSfmfKMGTO7lSzo0ZJ6cexZ833XivLKPPEDjWTbJSp/GPbFrlc5okt36mc+Z9jW+a7rMOojy/8jJ3T8yubsgO71i20qe+u/9SMteJvCGUkrLJJt67jnh0bAJlBzxM+m7i/SFnJl7zbythXs+OHNhbNavt//wJpebPlmbYn8WObv8XpHVbcVX33r/nabtnbN/yp+vDqvBoCWfvmwTx7Hwx1RoPxl7yjcH/LPrXRSEbuqbXY9t96u2VI2+jM55wywSHv/Mm28JiyD7APsA+wD3iyD1huKhzsiN1pGOHGzLkRLuhbp0s+B6q9npycmoahL4xHcsppvPLUYMz44EV8P3U0vvv0VXz5/vN4bugtWKNmgu9/chxyctydjFMz4UHGTLm6rzOMcfFlebv4dtNVPq+TcL+ARvVq6pfC/f3DB5g1eRSmvPUUPldG+7zPX8eqBZMwWhnnYsi7X5JzGmigO8epSKkmTZpAXGhoBRuZunXr6nhJc8bVb30JcmO6WHSE1O/nUn5nyjBkKtQzv+hAF1ahKuq07eO1sowyoxtepIszNjGNe7lcZlSTq43s2o9pcoXLOoz6+MIPaahmkXVN1aZyE9RrfalNfes17QxY/f5sSFxnm3TrOtZp0BKw+p3gClVqFykr+SLq9VSF5n2CwxHfomhWDZu2Q27Fumbh0Bizn7eNaXYtKja6Ni+kvLCqqNnuertlN2zZTfXhTkrI/Amqo2bblTyQrQYX8i8MEQ2vKpQ/KLq1OZNso9ujUdNWhWSkXXTm8w05OMeBnMiJfYB9gH2gfPYBuZ1wxgUpS0hcQWPcUdgfDPQ//t6AA4eP4csJL6B/nx5o27IRGjeojaYN66Bjm6a4pf9l+HDsE5AXm+0/VIKVq9YA9Uy4gmUxxoNVqlXYiDd8Y0YdJiUXOJ/IiIr6J9a6d2qFzu2aQQz3ShXDfd4ARdbnZZapAkPUcJq4IPl2604oHdEEiXPVmS5WM45dPwAu/AZB7V8qkQ5nygxqdi9w7qdA2xeBnt+pckKVC/GqC45qCrR5HqivjNb2ryAkopbr5UUrHV3eB5qo+is/pEoD13XkHS9nOLkrE9TmGaDHt0Dnd4BLfrJbVzR7CKjbH2h8N4JqXWZXxqgHYjsDkU2A6HYIiqhbrGxwjQuBdi9rvVJ+SFjlYuVNzR6EPjbNHwW6q77RfChw7ucIiWqEoEY3A+d9adbX80eEhFaEUaeCvumiOUCX94ALZiKo4+uQvow2I4AOo4GWTwLdPkJwnSsK5Tc1Vce0qWKhnKn5Q4XSC5bDcAgZ+fC7XER/4zHgMWAfYB9gH/DTPuCssSGGtiNj3F665HO2DG/JpWdkQIzHKpGViiwivlqMTjuZmKz9km+UfSNGtxjq4usZ82DACBf0LekqHwL7Lys7G0+9MglrNsr7onzTFhrovuHsXCmhUUCjO4BaaobbarbUucyuSKnDXrUbUP9GQM2gu5LTLdlKdYHqFwNhcSVXU6mO4qNmZsUvuRbf5JRRxOqXqGN6J1Ahxn6ZweqkWvUcoLIafJCTmX0pc6wpFKhYE5B+Yo4pZqtOiFVaADUvV2XHFiOXl6Rm2SF9IqoVENnQPGgQUS8vMQio1h1ooAx1PSOeF23Pk7o1vM1crvThCqrseoOAGr2BegPV4EJ7lUvVTW1tPkFh5jKkHNm3SWSABMojAbaZBEiABEjA2wRM6pZE5tjsGeHFxUuat+vmSH/LpvWRduYspn69EJmZWYXE086k6xeciRHfsK66fywk4UKEgJL7VLm3dcUXWReK8UfR3Jxc/LBkhX7Wf8C9IzFv8XKkZ2R6tarqztur+qmcBEiABEiABEjA3wiwPiRAAiRAAhC701XjXOQlX2nja9GkHgbf2AdvTpyBDpfdgxvufwn3DH8Tgx8fAzEku155P2bOXYrXn7sX0VGRblZXjWSIcS4z5S75Kp+bJZd29tDQEMz55FU8N/RWVI+LwTOvTdYvk/NmvWige5MudZMACZAACZBAOSTAJpMACZBAIBCQmXBxYnS74vuDgS58n3hgEOZ+OhoP33kd4qtF4+xZ8y/tyLPTjwzuhwXTxuDSCzuLqHtOG+bKbHTJOFfyCHwDXcA1a1QHt/S/FBNeewxrf/oY99x8lUR7zQk5rymnYhIgARIgARIgARLwMAGqIwESIAGPEBBD21XjXOQln0cq4AElTRrWxkPKQH9v9KOQt7d/8vbTGDviATx4e1/Uq13dAyWICmVoWxvnsnTdqbDKJ9nLkJMZ9Xg1GOLNJtFA9yZd6iYBEiABEiABEggwAqwuCZBAeSEghrYrM+dinIu8OH9nJD+ttmvfYc9UU0DpWfRgQHx5UZz4hpFeVFgMefj/30fT5uvHA46fTNKVlbfe3/nYG7B2i5at0mm+2NBA9wVlp8vIBZL/A3Kznc4RUILSrpz0gKqy9yubA5zep4pRvtr67UeOW668EEP10TMHVTWVr7Y2nzPqIpB5SrVnt+rDee2RY568VYnZkVex/JCAUwR03ytKUvWt7DP5idLn8kO+2dPl5/V535TIUgKZAOtOAiTgNwRMaoLXMLpd8SWf3zSiiIpkZ2fjmtuf1Ybnr3+tQ3a2O9cpBUqMcTG4XfJVviLq5y/R8sK3yV/OhzwWUC02SldLXrC3au0WvV8xPAxHj5/CF7MX67AvNjTQfUHZkppILAAAEABJREFUmTKyUoEFnYGfLwbmt1ZGzh5ncgWOzNmjwAk18nTyXyBlR+DU25s1zUwE/roHWP2Y2Zewx8pTRgusnRuKxcA++htwaCHw7zBgw0vAmieBTKuf7Nj4MvDTecBv1wMrHwT+uEEd75XAwnOAZVcBi3sA0sfdqAazlkMCMjCUdgA4cwQ4q1xBBNKnTqhzSuJGIFmdV9JPABnqe2XdNwvm8XQ4cT1w8h/g2F+qbDVA5Wn91EcCLhKgOAmQgPMEZCZcnCvGucgHgoFuCjLh8p5dsW7TTjz07Nvoc+vT+GX5GufhWEuaxGRUTmbKxUh31g8AUBu37NZvw7/xul7WLdb7rzw1GB+88ThGPHa7/pm11NNWEwJawjsbRdo7isuL1r1798IT7vjG6eom9JAZmzLUEjd+7hG9nqibJ3RkJO9XbRODUXnpx7Fv7+4y1b6SMDq5bQGgjrUiov1TW7/3GBMZNdV61Ub2S1I/c549yBUDSemBzJxnnZY9Vd9knNqxyFzfPTuQu2cGEBIJBFUwp2el4Oym95WceakQzh7G8U2zzfIe+s6Y67e31HTu2bMHe+i8yiA1MUH1p7zzRk4mDh/ca1NeyvHdKj1vRiA4TO3nfZTsgf37bGS9cawO7tuR38eRg7QTO71epjfaQZ17eNycP5eVWValfU1h+Xs9ej3Puxo49MR+tDbOZV+cGOH2fIkTJ/kcKi9lgRBV0bdGPYy/5k/AzA9H4oa+F+PkqZQS1soEaKM8OM8PKsIvmG4qYXm+y3Y4QQ3uq+JkBl15dj+NG9TW8bL0Xe94eaPoermEMq4+NjYWnnAV49vZkAqr1sYjej1RN0/oMIVUsrQvB6GIia1WptoXW4J+EF61pYWJ7ITFNPYYE9Fn7UpSP3OeqsgJyjt2odHWKhEWXd9c36rVkRPRGMjJsEkPim5lE65Yo5NZvgSszHXxzHfNU7qqVq0KOu8yCK1Q0aYPRVaJsWEeGm5eiqaFbPqfCVHRtrLOHKtq1arBFRcVWx3QS/2g/0LCK7uU35WyKOvasSkJL2f6CGW8+52vWrX09Hvq2kA9/nGt1idlJzZiiItTtizs+SEhsBsvsk6o9wuR4KAgtGneUP8k2/VX9ShhnUwqnzIbtZGufH3tU36QAiQz6pawMtCtwyYlo3L688cUZEKliuE2VaxeLQbPP3obYqIq63iZ7JKd3Ny8SQMJeNH5PzUvNt4TqitXrgxPuIha3YDObwO1rgLavYSKDa/2iF5P1M0TOkKjGwEVawFhcQiKblam2lZSPpVqqEGZpvcD1boDyq9Uq7vHuAQHh1i6t+yXtI6SLzi2DfSxi+kA1BsIxHQCGtyCSjU6W+ob3Ol1oHZfIFIdZ5Frch8qtH4EaP0cUPNyoMMbiKjeziIveuk8c+4oyxzDIqsCoVFAsDLUw6qp/lNFuXxu4VG1gUrKVYhR55ZYs5ys4gjNl3GFT2RkJFxzlYEqaqAtLE7Vox4qqPOca/ldLY/y3uTrSl+hbMm+Y6XOzUP3bGyH/x9/y02QEztinBvOZAKMffGLCku8E6pLVWT3vsOYOPU79Bo0DEkpeSsgS1ojUxCgjXMTtG8KNvvyM2oSX1y4pGX6KF+DOjX0Evcjx05aSoyOisTN/XohMkLdf6jY/3bI+6KAOrXiVcj7H0Xb+4WwBCcJ1L8J6D4FaKKMNrnJdDJbQIgFhQER9YHKTYAQdVMbEJX2diWDzMZrq6fMvpzcPFqkSWkTpzx3PnK8oloDVVoANS4Dmj4AxF8EWNc3ShnxHccCXSdqYxx1+wPBkUDjwSpuAlBvAPhHAq4TUN+R0CqAMs61kV5QgfRBGfiT84oY6SGVAGWcIyi0oKT3wlKufDfk/GbyYbneaxE1kwAJlJAAswUegaAgQJwY4+IXNWNeMF1k/bG1JxNTMGPuUtxw/0u4+vZnMUEZ6I3q10KFUHevTybVXAXLFJLnBxfhF0yXfErUjz/CJza6MsZOmAF7M+RpZ85i3Acz0LJpfVSJVPcZPmiLIu2DUlgECZAACZAACZAACZAACZBASQkwnxcImJT9KMa34YvhLWHDl31xBdMl7Gx1MjIykXDslF3jz1kdxcmdTc/AT7/9gyHPv4sLrxuCV97+HBu37sa9t1yNZd+8g4/HPYmK4RWKU+E4TRosg+ImZTrqGXPl67Ay1HVYgdRhFa/DypewLHd3rF1LeJuTLsTORti88NhtWLRsJW4b8pp+kZ78PN1/2/dizoLf9UDH3gMJeP7RW+3k9k6UoucdxWVNa0pqGk4llfTFCmWNBttDAiRAAiRAAiRAAiRQdgiUz5YYhnhxM+dioBdMF3vVETGZjZ342Vx07H0vLhn4OHr0G4p1m3cWmU1mv1v3vBMF3Yp/N9vNc/xkEl566zNllA/FYy++j227DuDhu/rhy/ef1/IXn98RcVWj9b77G2UyinFuGN3iW8LBgMysW8IiK3Him+Doz9ucHJUv6Zf3PAdvjngQW3fuxyPPvat/nm7AvSPxwpgpkozJbw5HxzZN9b4vNoqcL4oJ3DJkWYOMSHW/+iFc0HcIbnroFcgXInBbxJqTAAmQAAmQAAmQAAmQgA8J+GlRYmiLkS5ODHF7vsSJs06XfI6atHbTDkz4dA6+eO85rP3pY1x3xYV4fOT7yMmx/6IxMVRF56QxT+DHL8dYXPtWTSS6kNt38Ci+/v4X1IyP1Ub5wq/G4qE7+kKWbBcSdjtCGdpilMuMuPg2xriYk8pJfKF0lc9B2d7m5KB4S3KfXt0gKw6+mjgCrz17rzbYZ3/0EhZMG4vzu7axyPliR9H0RTGBW8ZXc5boEalfZr+DFfMnIjgoCO9+/E3gNog1JwESIAESIAESIAESIIEyRKCkTRFDW93aQ4xvwzkTFhlHZS79Yw3O7dIando2Q2hoCG4b0Fsvdd+60/zCsaLy16lZDfXrVLc4WYJtTzYmKhLV42Kwc+8hPPHSRLyvBgN27D5oT9T9OAFlUrPi1svXrcM63k66GPIOSvc2JwfF62RZ2TBv8XJkZWejfavG6Hv5+RCDXZ47P3r8FCRNXrqnhX2woYHuAPLCX1ZiwNUXIb5aNCpHVlJfrsvw7Y+/ee05EgfVYTIJkAAJkAAJkAAJkAAJkIAHCIjdaSxfF6NbnJ0wJN5wki75HBV/+OgJNKxbwyImtoQEjh5PFK9I99aHX+ul1Z/PWlTs29cb1quJn2e+pWbon0fP8zri81mL0feu53HTgy9r3ckpadr3zEbNhJvEbFROlrPrmXK1r/0QVYTat5uu8qnU4j7e5lRc2ZImKxqeemUSPlO8IyqFS5SNqxIZgZff/hzjJs20ifdmQNH0pvrA1y0vBahXu7qlIXXzXq+fnOrJTm9RX3Z3jv0BLL0M+LEDsHNKydqZsh1Y9wKw8n7g4PyS6fCnXNlngRV3AXMbmv2s1KJrl5ms2n0vsKAT8O8TQNoBIGkzkLobyMkonC8zCUjeZnayX1jCfkzSRmD1o8BftyvG8+zL2Is9sRJY/yKwcTSQqHTYkykubt3zwO8DgL/vVv1jcnGSTCMB/yJwNkH1+U1Ayg5AvtP+VTvv1ubwYuCnC9R5qSOw5yvvlkXt7hM4o2bWjiwBxMm++xqL1pCwDPjrDrM7+nvRcjpFlvsaTkf412breGDxhcBydV1MVd9zX9Ru46vAT6rMVQ8Bco6xLjPjJLD5TWDlA8CuT61TuF8CAhs3rse0aV/gq6/ynTPhDRvW4ZtvvsFNN91k46yrkJxyGuFhYdZRqFQxHKlpZ2ziAHMwrEKo/mmvdq0aIza6Mj6aNh93Pvo65OVpZonC26Agk5qhb4oXH78df8x9D+++MgRNGtbWgg88/T/c+dgbemJRZoZ1ZAk36zdsxBdffIUvpin35TSzb4S/KBC2Sl+3foNfcCqu2Zu27cGBw8fw7JBbECLLKAoIR1WJwPAHBmHZ8rVIPX2mQKp3gjTQi+Eqz4LIM+jhYRUsUvLlkUBamjKu1M7Ro0dB55hBxob/AafWAWcOIXfDKzh65IDL3NJ3fatugrcBGaeQu3cmjiYcclmHPx2rpK3fAPvn6PaIn7Tl6yLbk7xttvmmKjMROKmMYcUAudmAMupTTx4slC879RCQk66d7Dvb7vQd0xXj7UD6ceTu/FQxLqy7sK4EZO/7XueBuunL3Du3UH0K58nvMyf2rgKOqpu5sFhAlkud+AcnDm5ySUdx+pmWz5osPMvi+LEj6pymXG6W+i6expnE/eWq32aufw16oPD0XuSsG6XanqCcZxmzz3qOZ/ZJNZCkrhly3ZB9b7LN3v4xkLZfu6ztnxTZL06dOgXrv5SUlCJlvVnfonQfP7gF2D5JXadPqGvvP0j77zOv1+/knuXAvq8BGVxXkxupW2fYlJm6exGQuE6dc1L1fcHJ/Wts0otqS3mLt+5XRe23b98e7dq1U8nGAJHzvuQbNmwYxo8fb+OUMsunSuUIpGdkWMKyI3ZFZCXzb2tL2NrJb24//+htkLevD7t/ED4f/5x+zHZL3m9wW8va2xcb5dILO2P8K0O1sS5G+5kz6Rgx9hOczrNb7OVzFGdw0nTUJtdwKqPeL+hbpfsjJ1Vdm8/+g0d1uG0LNWGm9wpvZNBEYvcfMsvKvjcdDfRi6JpMJj3SlZ6RaZEy9ivZWQJhEeKOYwKKrWOh4iVMclNcvEi5SDWZ1JnQay11vDTJ/aK9WX/3a0cNJFA8AV98R4qvgX+k8nvsH8eBtSABEnCWgBiet99+O0rqLrroIsTFxdk467JrxlfFnv1qEDcv0ljabix1z4su0ouvFqPTzqTbGvk60sEmJqoybuh7CWZ+OFK/bM7e0m0HKizJjjg54ufPnKSR2dnZ2t6rUCFUgnadDLZIgkzeiu9tRwPdAWF5ScO+gwkWKWPkpEpkJR0XHx8POscMKrR9AohpD1SsBVPbEYivXhuucgtr2Beo3AyoEANT/RsQV6OeyzpcLdOb8lHNrwfq9tPtET+qxaAi21Ol2QCgRi8gNBqIPQcIiwFktjkkEhExdQrlC46sBQSFaSf7zrYjrMlNinFTpb8aTI3vQnz1WnCctzqC612r86BibYTW7+tEnvw+U7W+ak98TzUDfxJ6VUDVLqhau7VLOhzXMb88ypKFp/pAtbga6pxWXX0XQ4CQCFSMrluu+m1ou+eAqFZARH0EtX9Jtb26cuxfnupfntYTHNta9dNI7WTf0/qt9QU3vQeoVFe7kKaDi+wXMTExsP6rXLlykbLW+n21X612C6DpA0CFqkBsF1RqeYfX6xfb4Dyg3iAgNAqIuwCRzW+0KTOy4eVAtLqfUtd/uS+IrdvRJt1XbPy9HOt+VVr7l1zQEX+u2oh/N2xHZlY2Pp+9SL/UrXnjerpKU79eqH93WwfU5te/1unf4k5KOQ2ZaZeXUsuS+BZNzPJKpEQfsWXsLd0ukTIvZHLACd7m1LB+Tc27uJfArdu0UzIkbKcAABAASURBVLfceNRZB7y4CfKi7jKh+vKeXTFr3jLIqJc8d/DF7J/Qv08PmEymMtE+nzVCXWRwyU9An7VA47tLVmwVdaFs/ypwzodA7atLpsOfcgWHA90/BfruNvtysS2qfqFVVLs/Aq78F+j0P6BiHfONcWRDIKhC4VxyYa+iBjPEyX5hCfsxUW2Azu8C536uGF9jX8ZebFVlZLd7GWjzPBCtdNiTKS6u/WjgwtlAtymqf9xXnCTTSMC/CIRXV31eGT6VmwDynfav2nm3NjV7A5f9AVy5Bmhws3fLonb3CagBVDHotJN99zUWraG6GnQ99zN1LVEu/sKi5XSKSW0Np3b97dN8KND7d+A8dV2MbOKb2rV5QX23VJldJwJyjrEutUIs0OpJ4JxJQKO7rFO472cEOrRuggduv1YZ4aPR4dK7MXPuL/jfyIcgz41LVY8dT4T18vWMzEy8MOYTnHfNw+h65QNYsHQF3nt1KKIqR4h4mXWlywlo2rCOHjgZMfYTuy/lk4na19/7EvI2fnlhuC8OBA10B5Rv7nep/j3Biwc8hm5XPYjMzCwMGdzfQS4mkwAJkAAJkAAJkAAJkAAJlFcCJpNJ2wyrF03GTzPG4e8fPkDHNk0tOJ586EasWqAGWvJiLuvRBX/Nn4Cls97W7rc549G9c6u81LLrmUyly0me3R/99D1Ys3E7Lh30BN6cOAOz5i+D/NT2yHGf4spbnsbJxBS8OOx2uPxXwgw00B2Ai6gUjg/eeBzL503Ar9++q5/liK8W7SAXk0mABEiABEiABEiABEiABMo7AXnZdK0a1Swz58XxkKXo1eNi9IyuySQrS4qTLltppclJfq/+2ymvQF4UJ0vqR42bitHvfoHZ839Frws7YdH0N/VMu6+IO2ug+6o+fluOLC+pFhvlt/VjxUiABEiABEiABEiABEiABEiABFwn0LxxXXzy9tNYt2QKFkwbg59n/g8bln6q34pfp2ac6wrdyOEnBrobLWBWEiABEiABEiABEiABEiABEiABEnCTgKxiqFe7OmpWr+rUqgc3i7ObvXwY6HabzkgSIAESIAESIAESIAESIAESIAES8B8CNNA9cCyoggRIgARIgARIgARIgARIgARIgATcJUAD3V2C3s/PEkiABEiABEiABEiABEiABEiABMoBARro5eAgF99EppIACZAACZAACZAACZAACZAACfgDARro/nAUynId2DYSIAESIAESIAESIAESIAESIAGnCNBAt4MpNzcXWdnZdlLMUTk5ucjOzjEH3NhKOScTU7Dv4FGkZ2S6oan8Zi0vLc9S/VH6nb32Sj+SdHtprsZJPzxw+BikX7qaV+TTzpxFUvJp2aULcALF9SlP9rnU02ew/9BRnE47WyJix08m4Wx6RonyMlPgEJDzX3Yx190sdY70RGtET8KxUziccKJE13npx0ePJ0K+I56oD3WUHgHpC9LviqqBpBeV5kq8u9fdE6eSkZiU6kqRlCUBEvBzAjTQ7Ryg+T/9hd43DreTAn3RfemtqXj57c/spkvkr3+tQ+ued0J8FPG3fvNO9Og3FBdeNwRX3vIULug7BHMW/G6RXvL7v1qH6LF2ciIXOes4Y3/YqIk6vxhXRpy1v+LfzTr9koGPa92nklJ0WDZyg9v1ygd0fHEXJJEtR84vmnrmbAb6Dx6BH5essFuf4vqrZMhQgz+3DXkNA+4dKcEi3QtjpqBT73tx+U1P6n4peYyLvqM+JTe0Q55/Fxf1fwyX3vAEJO9/2/fqssTgl354w/0v6bCxkXSJv2f4m0YUfT8hIIOG7XvdjUNHjtutkaM+d1Dlk/PJWx9+bTe/RMpgTr/BL6DbVQ/iipufwjl9HsDod7+wGEWO+tyfqzbipodegei48LqhGDpiPMTYF93irr3jOX0+kz5muIlTv5MkjBj7iU77/e8NOmxspA+L7L8bthtR9P2AgBi7xV13HfVXacLbk2fpY56cmiZBu27m3KWQfi/XSDmPXXbjE9i4dbdFtrg+JefZp0d/qPvxxQMeQ69Bw7BhS35e0dla3RfwumvB6dc7jq67jvqc9Ae5Drpz3RVAxfU5Oc/KdVXuJc/v+wgGPz4GYqxLPnFjJkzXfV76neFufWS0JGHe4uU67Z2PZuuwsZn27U86/qNp840o+iRAAqVAgAa6FfR9BxO0cfLMa5OtYvN3Fy1bqY3q2fN/zY8ssLd1534Mf/mDArGFg7kq6tF7BuD3797D2p+n4PaBvSEGkhjgKgm56r9SxXD8+OUYG1chNASX9ehiEycy7Vo1RtWYypJVDyLIzqQxT9jItW/VRKItbvp3Sy37P/y8AnLDbIngjg8IOC5i3KSZ6HLFfdi591AhYUf9VTKYb2w/w78btkmwWFe3VjxmTR6l++OCaWOwZ/9hfD3vF51H9MhOUX3qf6qe0neXf/8+/po/AQ3q1sC7H9te+OVGd+WaLaJGu6lfL9Q+N/5FQIxeGTS0Vytn+lyKMoAefPoth+cTGQjsdUFnLPxqLNYv+QSTxgzDV3OWYN3mHbro4vqczFzd9+Q49OjeDr/NGY9fv30HO3YftPRXrUBtht59vc058OZ+l6rY/M/kL+dZArv2HcbSP9dYwtzxDwKLHFx3i+uvRgtkUPvjr34wgkX6cs2VfrhqwYfqPDYRTRrUxluTbAeZiupTP/2+GsvVoNHsj17S59CLzu2A4S9NhJwXrQvkddeahn/uF3fdlRo76nNy7nrpLfevu1KWuKL63EdfzkdMdCSWznobf859X69Ckmux5BEn9eh5Xgebc+C4kQ9KksWJIZ6UclqHM7OyMWX6j3qfGxIggdIlQAPdin+tGtXw2fhn8fyjt1nF5u9e2K09ZqmL79WXnZsfabV37EQiHnzmLYwafifkQm+VVGi3vTKoB1x9EWKjKyM0JBjV42L1flBQ/iEJDwtF/TrVbZzJZEJkREWbuKTkVMiM/G0DLrcpp07NajZyFcMrWNJv6X8pPp2xQJ/Qs7NzIDeqt/S/zJLOHf8gcM9NV2HJrLdQPS6mUIUc9VeoHHJTunnbHgy7f5AKFf+5/7Zr0KpZA90fa8ZX1cLRVSK1b2yK6lOHEk4grmo0QtUAUkhwMDq1bYptuw4Y2bR/i+pzH39lHpWXkX+ZhR14dU+dxo3/EHj35SGYPnGE3Qo56nNZ2dl48pUP1PFvhst7nmNXhxEp57FHBveDDAwFBwehVvWqOqlKZIT2jY29PpeRkaWTa6jzpslk0ufbDq2bYPe+Izre2MRVjbI5B0ZH5ffnfldeqAeu1mw0z5Z/MXsxbuE5EP725+i6W1x/lbasWrsFr42fhnEv2homklbQXdP7PFzYrZ3qT2GoElkJVSpHIDqqMqz/iupT09XgkvSplk3rQ67pQ+/uD1k9JANHRv5b1DmQ112Dhv/6xV13pdaO+tzHajDIk9dde31OVoLMmr8MN113qb4/iFbnNrmGz130p2WSRupaWfVj6/tIOWdKvDjpq53aNsOsecskiJ9+/QdxsdHo2KapDnNDAiRQegTyrcHSq4PflCyGhZy8YtSJzl6lKlUMg6RHVKpYKFmWQz3y3Lvof2UPXNWre6H0oiJWr9+GkeM+xaTP5+KZIbfoC7shezIxBc+9/hFkJPaHJSuKfC7+rcmzcGPfS1CvdryRVfuyvFRm5T+ftQjGCKlOUJvzurTRN67f/PArfl2xDmEVQnHxeR1UCj/+REAuutLnQkNCClXLUX9drC62cuw/UDOTldWgTiEFdiJkWd6kz7/H7Y++jo7KyO5ToC9b9ynrPjX4xivx3cI/9DLjX5avgYzKP3THdTYl3HRdL8iy5E1b92DaNz/htgG9EV8t2kaGgdInIMdEBgzt1cRRnxs7YQbEeH7+0VvtZbcbJ0aM9KshL4zHg7f3RZOGtW3kJK3geUzOxXff1EevOpLZLjk//v73etzcr5dN3lnzf9UyE6d+B5n9t06MUYaX6JCbaRlc/fr7X3Dr9Zdai3DfDwjIsZZzoL3rrlSvuP6690ACHnr2Hbzz8iNo2rCOiDvlvl/8Jx578X2IkXXfrVfb5CmqT2VmZiHIaoBd+pdkPHz0hHja8bqrMfj9prjrrlS+uD7njeuuvT4XpAYmpS7WfU4GUCVO7h3FF7dyzX/6PvLNiTMg95sSZ+3uufkqfPjFPKSdSVfX7Xm4t0B/t5blPgmQgO8I0ED3AGtZqvn8Gx+jds04PHSnrVHiSL28iEZeKCMX90SrZ8LlBvkuZfQ0rFdTq3jqlUkY8/5Xet968/vfGyAzBPfdeo0lWoxtuVGVZe+xaoZejKU7lcElxpchZDKZIHkkbaK6eb3/9mthCjIZyfQDnMCGLbshfXLiG8P0oJKzzcnOydUz37IqIzklDSmnz+isjvpU8yb19IBPkCkIT73yIWSZs8xo6sx5m9iYKmqG8lLIErzP1KDRrdcXu2IjLxe9QCEw/bsl+PWvtXj7pUcgKymcrbf0FXmeU/wjx05CzoWS11GfO69rG73qaPe+w6rPTYLMBhk3qJL/8p5d9RJ4iZOl69ffM7KQkX6zmtFctnwtXnzzU8hqDjnvSl66wCeQlHwa8hjE4/cNxPmqr7jSol17D+tnebOzcyDnQSNvcX3qyl7dlIEzH1NnLsSiZSshBpGRz/BNJl53DRZl0ff0dVcYFdXnZAVSj+7t1QTPJ/r9RbIibeJn30kWi2vdrAFkVYc8crb/8FHcPvQ13TctAmpHVozUjI/FEy9N1Eb6xed1VLH8kAAJlDaBoNKuQFkoX94iLBfkypEVMe6DGRg7Ybo60Z2FPL+7aNmqYpsoy+U/eONxNcI/RC/Dk7cZS4a2LRpi+AM34N5brsbIYXfglacG6+czs7KzJVm7bHXz8NaHMyGzQNZLoOXE/fyjt+m8srT58/HPaaNry459Op+xueSCjpCl+CcTk9H7oq5GNP0yQOC7Bb9DlsUtWLJC98cfl/4NmU2SvimGUFFNrBheAW+Negg/fPEGQkKCMeHTOVrUUZ8aNnICrr7sPMhM1dJZb6Frh5b6BV5ZVv1VFN16fW/8rUb0ZSlpHTWgJXGl41iqpwmIYSJLKT/84nvd5zZt3Y3l/2zSRktxZbVsWl/3m/mqz8n5csEvf2vx4vqcrN64e9hYjBv5ECa89hgWTBuDI0dP4qX/fabzyubhu/rpGfmH7uirl+zL+VlevilphpOZ2f59euC3FetwxyDbR4QMGfqBSWDFv5v0EnO5psp57+PpP+iGyEux5AWVOlDE5rF7B+CL956D9I0nXppgkSquT90x8HKMePx21ec34psffkPa2XSdr2be40I6oDa87ioIZfTj6euuYCquz4194X5co667sqx9kRoUMiZhZGJG8sp1dsjg/noyZvwrQyFheR+DpBkuSE3MyH2mnANlibw8bmSk0ScBEig9AjTQPcA+MiIcj95zPWrXqAZZGiVO1EZGVFQGcJjk3SRfAAAPdklEQVTsOnRyYytCYuyLX9DFVY3RUVlZ2dqXzUJ1QpbnfGWmXcJFufhq5rxnCvwUkSxXfVEZ/y8NvwuhyhgrKj/jA4+AvBjmuisugPRFcRGVwhEeFqrDzlyATSYTGtWrCZnRtNd66z4lPyskL4Br0biuFpVn3mTQKO3MWcjspo7M28hjGHITe7/Vio+8pLLllcPWyGMOXdo3131M+pz0s/CwCpBneZ3BEVU5Qg8qyS8C2JO37nPrN+/SIs0bmftcvdrVMeDqi/D73+t1fMFNaGgI5NnKgudAkbv3lqv0e0eM1UoSRxf4BOQFb3JdjomK1H3S6IfRVSIgL1t1poXSJ04mpth9vKxgnzKZTPpRs8lvDoe4mmpWUgbAG9WvZVMUr7s2OMpUwNvX3YJ9Tq61skJk6jvP4L3Rj+rHiy45vyNMJpNdrjIgeTrNPHBkLXDFxedAjPOrLj3XOpr7JEACpUiABroVfHnjpSyvNIxgvW81A5itZqwlLlvFiYzsy/J2uQjLcnFrJ3F9LumuXzgjRaxau0X/zJXMYkpYRjGX/vGvfjZcZjQnfjZXGfPhkJsKSZe3GcvzQmfOZmgjSV7i1q1jS2VkmV/0JiOlslRYRldjoipLFov79a91ehmTzDKJkfTux99o3S2a1LPIGDvnd21jqaMRR99/CGSpvib9TGqUmZVlWf4r4eL6qyxbs+6PF3VvD1m+K3HSN6XPyc+/LFAz66JLfp5KnvUVgzpTDQKt27wTcxb8ga7tW0gyiutTYvzLbLisGJE+J/WVn4STQSe5wdUKrDbyvgR78VYi3HVAwJvJcvwzMjN1EbIvTgfUprg+d0PfS/RMjfQxcS2a1Ie8LFDiVVb92IN1n5OXs8l5Th7xkfOZnBPl/CgvLRL54vpco/rmR3/kJ4Gkv8kLk+TZTxkgkLzyvLn8SoAMMEn9ZfmnDCLJOVTSrZ0Y9/JIkHUc9/2HQHYR112jhnJ87fXXxg1q2/THQddcrLPcecOVkDQJDBs1EfIOA9kXJ497ybnvrBrMlhdZfjpzAaTPiFHtqE/JvYC8y0DyykDRR9N+gLwormJ4BVFt43jdtcHhd4GsYq67Utmi+pyj667kte5zjq67jvqc5D+ddlbfR8q5UFan3XfbtVKMdvLTgjv3HITUV85/0779GRec01anWW/E8B969/X6XUTW8dwnARIoPQI00K3Y79xzCB0uuwfyM2syiyP78nIiQ0ReqCZx8jNr8kIs2f9u4e9GcrF+atoZyLI6uXiLoJww5aVI513zMLpf/RCWLV+D914dChkRlfQjR0/o54XkJ7Z6DRym38r58lODJUm7bxf8rm54z+A2O8/xys3KC2M+geiW3yJesHSF1i0zVDozNwFDQJ7nln4mL9KSvij7YkRLAxz1V5EpysnNpPTHxORULWIymSDLka++/Vl0uPRu3PzQK+h9URfcecMVOt1Rn5Kl7RUqhOo+d961j0B+smrM8/dDbmy1ArUxmeyP6qskfvyIwHnXPIwrbn5K16jPrU/jkgGP6X3ZeLLPSd/4+Kv5uFjp79j7Xv0yt6cfvgmd2zWTolBcn5PVSvIoxl+rN0P6m5wjZUBo5BN36LyykRckSnwH1Z+fHv0hrHVLOrujUPB/5+i6K9e5ovqro9bt3ncIh44ct4iJUS7nvs6X34feNw5HcFAQrK+7xfWps+np6Hn9Y5C88nLXJx+6Ub8I06KcOwFDoLjrrjTCU33OZCr+uitlFdfnZDDpnD4P6OvuzLm/4Mv3n4c8Hin5xK1Q58dr73xeX9NvuP8lfU2/g4/yCBo6EvB7AjTQrQ6RvD1407KpsHZvPHefRWLQtRfbpImcPKNmEbDaWbVgEi46t70l5uLzOuq8zfOWAQ+6pifWLZmif79Sfkbr55lvoXvnVhZ5eXZ89aLJ+jeC5fct5cQrs5SGgMxCShmGQW/Ei39Zjy6Q36JeOuttrf+3OeNtdEu8dd0kj7junVrpOgYF0ZASHv7gxAiRfmbtjNlnR/3Vuv7Sd+X3eY24qCoR+ljLm9UlTmbBJX3lj5P087yrFnyI0c/cYxlRd9Sn5DliecZN8v8883+Q9yq0bdlIVEP6rdTfWGKqI/M28lLFj8c9mRei5w8EVi2YpPuGHDNxv3/3nqVarvQ56btyHjMy5/e5XjpK+seSr9/CH3Pf0+e5tT9Pwe0D858Dd9TnLu95jr4h/fHLN7SO0aq/yhJOUS6z4qJbzn0Lvxqrz7XWuuWdHtZ1kzzi5MV00maZ+ZcwXekTkHOXHBNrZ33dLa6/Wtfe6LvW56E5n7yKt0Y9bBGTPrRm8UdYNP1NFLzuOupTsjJJruXS5+Q7I9d4i2K1w+uughAgn7dGPWRzDpS+Z1x3of6c7XPSd+W6qrJYPtZ9ztF111Gf69appT53Sn2+/+w1FPx5tJkfjsSK+RP1Nf2fhZP1NV0eO5LKyPPoBesm8eLkflOeS5d9OhIggdIhQAO9dLjrUmUGSV7uJjeV9oxiOZHKbwTL85w6gwsbQ7foN5locLuArlyLyg2D3BTITxsVBOFMn5L8YogVzMswCdgjYDKZII/oyHnO3nswnOlzcVWjLQNJuoy8jclkQtWYKhDdogf8IwEnCMhKIBlUtHfdNZmK71NyLZc+50QxFCEBCwG5bhZ13TWZiu5zcl6T85sMDlmUFdiRSRzRbe9RiwKiDJIACfgRARrofnQwWBUSIAESIAH/JsDakQAJkAAJkAAJkIA3CdBA9yZd6iYBEiABEiAB5wlQkgRIgARIgARIoJwToIFezjsAm08CJEACJFBeCLCdJEACJEACJEAC/k6ABrq/HyHWjwRIgARIgAQCgQDrSAIkQAIkQAIk4DYBGuhuI6QCEiABEiABEiABbxOgfhIgARIgARIoDwRooJeHo8w2kgAJkAAJkAAJFEeAaSRAAiRAAiTgFwRooPvFYWAlSIAESIAESIAEyi4BtowESIAESIAEnCNAA905TpQiARIgARIgARIgAf8kwFqRAAmQAAmUGQI00MvMoWRDSIAESIAESIAESMDzBKiRBEiABEjAdwRooPuONUsiARIgARIgARIgARKwJcAQCZAACZCAFQEa6FYwuEsCJEACJEACJEACJFCWCLAtJEACJBBYBGigB9bxYm1JgARIgARIgARIgAT8hQDrQQIkQAIeJkAD3cNAqY4ESIAESIAESIAESIAEPEGAOkiABMofARro5e+Ys8UkQAIkQAIkQAIkQAIkQAIkQAJ+SIAGuh8eFFaJBEiABEiABEiABEiABAKbAGtPAiRQEgI00EtCjXlIgARIgARIgARIgARIgARKjwBLJoEySoAGehk9sGwWCZAACZAACZAACZAACZBAyQgwFwmUFgEa6KVFnuWSAAmQAAmQAAmQAAmQAAmURwJsMwkUSYAGepFomEACJEACJEACJEACJEACJEACgUaA9Q1kAjTQA/nose4kQAIkQAIkQAIkQAIkQAIk4EsCLMurBGigexUvlZMACZAACZAACZAACZAACZAACThLoLzL0UAv7z2A7ScBEiABEiABEiABEiABEiCB8kHA71tJA93vDxErSAIkQAIkQAIkQAIkQAIkQAIk4P8E3K8hDXT3GVIDCZAACZAACZAACZAACZAACZAACbhNoFgD3W3tVEACJEACJEACJEACJEACJEACJEACJOAUgdI00J2qIIVIgARIgARIgARIgARIgARIgARIoDwQKMMGenk4fGwjCZAACZAACZAACZAACZAACZBAWSFAA72kR5L5SIAESIAESIAESIAESIAESIAESMCDBGigexCmJ1VRFwmQAAmQAAmQAAmQAAmQAAmQQPkiQAO9fB1vo7X0SYAESIAESIAESIAESIAESIAE/IwADXQ/OyBlozpsBQmQAAmQAAmQAAmQAAmQAAmQgKsEaKC7SozypU+ANSABEiABEiABEiABEiABEiCBMkiABnoZPKhsknsEmJsESIAESIAESIAESIAESIAESoMADfTSoM4yyzMBtp0ESIAESIAESIAESIAESIAE7BKggW4XCyNJIFAJsN4kQAIkQAIkQAIkQAIkQAKBSoAGeqAeOdabBEqDAMskARIgARIgARIgARIgARLwGgEa6F5DS8UkQAKuEqA8CZAACZAACZAACZAACZRnAjTQy/PRZ9tJoHwRYGtJgARIgARIgARIgARIwK8J0ED368PDypEACQQOAdaUBEiABEiABEiABEiABNwjQAPdPX7MTQIkQAK+IcBSSIAESIAESIAESIAEyjwBGuhl/hCzgSRAAiTgmAAlSIAESIAESIAESIAESp8ADfTSPwasAQmQAAmUdQJsHwmQAAmQAAmQAAmQgBMEaKA7AYkiJEACJEAC/kyAdSMBEiABEiABEiCBskGABnrZOI5sBQmQAAmQgLcIUC8JkAAJkAAJkAAJ+IgADXQfgWYxJEACJEACJGCPAONIgARIgARIgARIwCBAA90gQZ8ESIAESIAEyh4BtogESIAESIAESCCACNBAD6CDxaqSAAmQAAmQgH8RYG1IgARIgARIgAQ8SYAGuidpUhcJkAAJkAAJkIDnCFATCZAACZAACZQzAjTQy9kBZ3NJgARIgARIgATMBLglARIgARIgAX8jQAPd344I60MCJEACJEACJFAWCLANJEACJEACJOAyARroLiNjBhIgARIgARIgARIobQIsnwRIgARIoCwSoIFeFo8q20QCJEACJEACJEAC7hBgXhIgARIggVIhQAO9VLCzUBIgARIgARIgARIovwTYchIgARIgAfsEaKDb58JYEiABEiABEiABEiCBwCTAWpMACZBAwBKggR6wh44VJwESIAESIAESIAES8D0BlkgCJEAC3iNAA917bKmZBEiABEiABEiABEiABFwjQGkSIIFyTYAGerk+/Gw8CZAACZAACZAACZBAeSLAtpIACfg3ARro/n18WDsSIAESIAESIAESIAESCBQCrCcJkICbBGiguwmQ2UmABEiABEiABEiABEiABHxBgGWQQNknQAO97B9jtpAESIAESIAESIAESIAESMARAaaTgB8QoIHuBweBVSABEiABEiABEiABEiABEijbBNg6EnCGAA10ZyhRhgRIgARIgARIgARIgARIgAT8lwBrVkYI0EAvIweSzSABEiABEiABEiABEiABEiAB7xCgVl8RoIHuK9IshwRIgARIgARIgARIgARIgARIoDABxlgI0EC3oOAOCZAACZAACZAACZAACZAACZBAWSMQSO35PwAAAP//yy16nAAAAAZJREFUAwD0vL/6TRdEnwAAAABJRU5ErkJggg==", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig = plot_read_browser.plot_read_browser(\n", + " mod_file_name=extract_file_no_thresh,\n", + " region='chr1:114357437-114359753',\n", + " motifs=['CG,0','A,0'],\n", + " thresh=0.5,\n", + " single_strand=False,\n", + " sort_by=\"collapse\",\n", + " hover=False,\n", + " subset_parameters={\"frac\": 0.25}\n", + ")\n", + "# Comment out these lines to display an interactive plot with e.g. zoom and pan\n", + "png_bytes = to_image(fig, format=\"png\", width=1000, height=300)\n", + "display(Image(png_bytes))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot read depth profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The depth profile plotters allow you to visualize the depth of your data, both at a single locus and across many loci. This is always showing the read depth that is *usable for DiMeLo-seq*, that is to say read depth for the motifs in question where there is mod tag information available for the putatively modified nucleotide. Because not all motifs are reverse-complement-symmetrical, the depth is expressed on a per strand basis. You can use the same single_strand command as in enrichment plotters to only pull data from the strand specified for a region. Otherwise, data will be pulled from both strands, but the populated read depth will for each position only come from one strand, so you will still only get effectively (full read depth) / 2. " + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "from dimelo import plot_depth_profile" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e2a84e17ab154cf5864bacd00fd93286", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/1 [00:00" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYwAAADvCAYAAADhLuMKAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAUFVJREFUeJztnQncDdX7wI99aUEqW4iQJQmRJVspSVIUpSRpEUqb4ie8JEuUSvi37yrKUtpDWZOE7FpspWxJdpX5f77n7Vxz573LzL33vdv7fD+fed97586dmXtm5jznWc7z5LIsy1KCIAiCEIbc4TYQBEEQBBEYgiAIgmtEwxAEQRBcIQJDEARBcIUIDEEQBMEVIjAEQRAEV4jAEARBEFwhAkMQBEFwhQgMQRAEwRUiMISE8eWXX6pcuXLp/+lO8+bN9ZLs3HzzzerMM89UqQD3TkZGRkreexkZGfr4u3btUqmECIwk4JVXXtE3T6ClX79+iT69lGDSpEnqySefTPRpCIIfw4cPV9OnT1fpQt5En4BwnKFDh6oKFSr4Nck555wjTeRSYKxatUrdc8890l5CUgmMa665Rl111VUqHRCBkUS0bt1anX/++THf74EDB9QJJ5yg4g15LQ8fPqwKFSqkkgnOKX/+/Cp3blGw0/1aC7FFnpgUYvbs2apJkya68y9atKhq166dWrt2bUDb6Jo1a1Tnzp1VsWLF1IUXXqjef/99vf7777/3bfvee+/pde3bt/fbR7Vq1VSnTp18719++WV10UUXqdNPP10VKFBAVa9eXU2cODHL+WH7vuKKK9Snn36qBR+dx7PPPqs/++WXX/Qoi3NnP/fee686cuSIq9+9b98+rTmwf47P9y+55BL13Xff6c/xDXz44Ydq8+bNPlOescMbW/Xbb7+tHn74YVWmTBlVuHBh9ddff6k//vhDPfDAA6pmzZrqxBNPVCeffLIW2itWrPA7vtnH5MmT1aOPPqrOOOMMVbBgQXXxxRerH3/8Mcv5Pvfcc+qss87Sv79+/fpq3rx5yi0cp3fv3mrKlCm6ndlHw4YN1cqVK/XntGelSpX08fndmzZtyrIPvlu3bl393VNPPVXdeOON6tdff82yHaYSNFj2xf9p06a5Ps9Q1/rPP//U16ts2bL6enG+o0aNUseOHfPbx5gxY1SjRo1U8eLF9fc553fffTfLsbhPuF9OO+00ddJJJ6krr7xS309u8XLvLV68WF122WWqSJEi+j5p1qyZWrBgQcBnbN26dapjx476vuE39OnTRwtNA9swWHv11Vd99yU+Iju0Fet4njlmt27d1MGDB1WyIhpGErF3794sTjAeePjiiy90Z1axYkV9wx46dEiNGzdONW7cWHecTkfltddeqypXrqxVYkZ/CA1u2Llz56pzzz1Xb0NHxih7/vz5vu/t3LlTPwh0WgaEQ40aNfSDmjdvXvXBBx+onj176g6gV69efsddv369uv7669Udd9yhbrvtNnX22Wfrc6Vz3bJli7r77rtV6dKl1euvv64FoBt69OihOxLOiU509+7d+pwRlnXq1FEDBgzQbUfHMHbsWP0dBICdRx55RGsVCAg6C14jVOk0aStMgdu3b9edHp0En3GedkaOHKnbi31wvMcee0zdcMMNupMxvPjii/q30xHSaf7888+63U455RTdgbqB64KAN207YsQI3Tk/+OCDasKECbrt9+zZo49/yy23+LUj/jA6nXr16unv8Zueeuop3ektW7ZMd0zw2WefqQ4dOuj2ZDvalO8hDN0S6FrT2dF+CCjWlytXTi1cuFD1799f/fbbb35+Js6LtqENjx49qoU612LmzJmqTZs2vu1uvfVW9cYbb+gBEO3K77V/Hgov9x7reMYQXIMHD9bX2gyWuCb169f32x5hwXNH+3399dfq6aef1tfltdde059zHM6d791+++16HQMJ5z6499gHz/ELL7yghRoCNimhHoaQWF5++WVqkgRcDOedd551+umnW7t37/atW7FihZU7d27rpptu8q0bPHiw/t7111+f5Tg1atSwOnbs6Htfp04d69prr9Xbr127Vq+bOnWqfs++DQcPHsyyr1atWlkVK1b0W1e+fHn93U8++cRv/ZNPPqnXT5482bfuwIEDVqVKlfT6OXPmhGyfIkWKWL169Qq5TZs2bfTxnbBvjsG5On/H4cOHrX///ddv3caNG60CBQpYQ4cOzbKPatWqWUeOHPGtf+qpp/T6lStX6vdHjx7V14hrZd/uueee09s1a9bMCgfbcXzOw/Dss8/q9SVLlrT++usv3/r+/fvr9WZbc/xzzjnHOnTokG+7mTNn6u0GDRrkW8c5lipVyvrzzz996z777DO9XaB2dBLsWj/yyCPWCSecYG3YsMFvfb9+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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(4, 2)) # If you want a different sized plot, you can us matplotlib commands\n", + "plt.title(\"Forward strand mod read depth\")\n", + "plot_depth_profile.by_modification(\n", + " mod_file_name=pileup_file,\n", + " regions='chr1:114357437-114359753,+',\n", + " window_size=1000,\n", + " motifs=['A,0'],\n", + " single_strand=True, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " regions_5to3prime=False, # if set to True, regions on the reverse (-) strand will be flipped so everything is 5 prime to 3 prime\n", + " smooth_window=None,\n", + " palette = { # you can pass down kwargs to sns barplot if you want to\n", + " 'A,0 depth':'blue',\n", + " }\n", + ")\n", + "plt.figure(figsize=(4, 2)) # If you want a different sized plot, you can us matplotlib commands\n", + "plt.title(\"Reverse strand mod read depth\")\n", + "plot_depth_profile.by_modification(\n", + " mod_file_name=pileup_file,\n", + " regions='chr1:114357437-114359753,-',\n", + " window_size=1000,\n", + " motifs=['A,0'],\n", + " single_strand=True, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " regions_5to3prime=False, # if set to True, regions on the reverse (-) strand will be flipped so everything is 5 prime to 3 prime\n", + " smooth_window=None,\n", + " palette = { # you can pass down kwargs to sns barplot if you want to\n", + " 'A,0 depth':'red',\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can superimpose plots of mod fraction enrichment and read depth to get a better picture of what is going on, and how meaningful results are. For example, in the below plot, we see that adenines are somewhat depleted at CTCF sites, while CpG motifs are enriched. The adenine modification signal has much higher read depth because `A,0` motifs are much more prevalent than `CG,0` motifs. With this set of loci and read depth, the CpG signal flanking the CTCF sites is quite low depth - explaining the noisy trace." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8cf6434f7cd84e668099f45cad69f56e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/50 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax1 = plt.subplots(figsize=(8, 4)) # Create figure and primary axis\n", + "ax2 = ax1.twinx() # Create secondary y-axis sharing the same x-axis\n", + "plt.title(\"Motif read depth across peaks\")\n", + "plot_depth_profile.by_modification(\n", + " mod_file_name=pileup_file,\n", + " regions=ctcf_target_regions,\n", + " window_size=1000,\n", + " motifs=['A,0','CG,0'],\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " regions_5to3prime=False, # if set to True, regions on the reverse (-) strand will be flipped so everything is 5 prime to 3 prime\n", + " smooth_window=10,\n", + " palette = { # you can pass down kwargs to sns barplot if you want to\n", + " 'A,0 depth':'blue',\n", + " 'CG,0 depth':'orange',\n", + " },\n", + " alpha=0.4,\n", + " ax=ax1,\n", + ")\n", + "plot_enrichment_profile.by_modification(\n", + " mod_file_name=pileup_file,\n", + " regions=ctcf_target_regions,\n", + " window_size=1000,\n", + " motifs=['A,0','CG,0'],\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " regions_5to3prime=False, # if set to True, regions on the reverse (-) strand will be flipped so everything is 5 prime to 3 prime\n", + " smooth_window=50,\n", + " palette = { # you can pass down kwargs to sns barplot if you want to\n", + " 'A,0':'blue',\n", + " 'CG,0':'orange'\n", + " },\n", + " ax=ax2,\n", + ")\n", + "# Combine legends\n", + "lines1, labels1 = ax1.get_legend_handles_labels()\n", + "lines2, labels2 = ax2.get_legend_handles_labels()\n", + "ax1.legend(lines1 + lines2, labels1 + labels2, loc=\"upper left\")\n", + "ax1.set_ylabel(\"Read Depth\")\n", + "ax2.set_ylabel(\"Enrichment\")\n", + "ax1.set_xlabel(\"Relative Genomic Position\")\n", + "ax2.get_legend().remove()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plot depth histogram" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Depth histograms show the distribution of read depths across sites, either averaging each locus into a single value or providing readouts across all genomic coordinates." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "from dimelo import plot_depth_histogram" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "cc93553616114765a9c51a018150cbb6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/50 [00:00" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAHBCAYAAABzIlFzAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAANtRJREFUeJzt3Ql4jWf+//FvRKid1BZDaqsEJVQr9iJq6Yxa2qmlVcrwq2qrVUu1tU2rlKnqYnSM1jK/aXWlplqKoi2xlhJC7VFiL0EiEs7/+t6/Oed/EknIep5b3q/req7kPOec59znOTmej3v1c7lcLgEAALBQAV8XAAAAIKsIMgAAwFoEGQAAYC2CDAAAsBZBBgAAWIsgAwAArEWQAQAA1iLIAAAAaxWUW9y1a9fk2LFjUqJECfHz8/N1cQAAwE3Q+XovXLgglSpVkgIFCuTfIKMhpkqVKr4uBgAAyIIjR45I5cqV82+Q0ZoY94koWbKkr4sDAABuQlxcnKmIcF/H822QcTcnaYghyAAAYJcbdQuhsy8AALAWQQYAAFiLIAMAAKx1y/eRAQDcWq5evSpJSUm+LgayKSAgQPz9/bN7GIIMAMCeeUWOHz8u586d83VRkENKly4tFStWzNY8b9TIAACs4A4x5cuXl6JFizLJqeWhND4+Xk6ePGluBwUFZflYBBkAgBXNSe4Qc/vtt/u6OMgBRYoUMT81zOjnmtVmJjr7AgAcz90nRmticOso+t/PMzt9nggyAABrsGbercUvB9ZAJMgAAJAFVatWlenTp3PufIw+MgAAZMGmTZukWLFi+f7c9evXz/RfWrRokU/OBUEGAJBvXLlyRQoVKpQjxypXrlyOHAfZQ9MSAOCW1bp1a3n66aflueeek7Jly0qHDh3M/qioKOnUqZMUL15cKlSoIH369JHTp097nnfhwgV59NFHTY2LDg1+6623zLH0OOk1LcXExEiXLl3MMXWR4kceeUROnDjhuX/8+PHSoEED+de//mWeW6pUKenZs6d5LbfPP/9c6tWrZ0b06Oisdu3ayaVLl9J9fzt37pQ//elP5vV0leiWLVvK/v37zX3Xrl2Tv/71r1K5cmUpXLiwee2lS5d6nrt69WrTR8V7Xp5t27aZfYcOHTK3586da+Z6WbZsmdSuXdu8t44dO0psbKznPc2bN0+++uor8zzd9Lh5iSADALil6YVWa2HWrl0r77//vrlwt23bVho2bCibN282F3cNHBo83IYNG2Yev3jxYlm+fLn8+OOP8vPPP6f7GhoaNMScPXtW1qxZY55z4MAB6dGjR4rHacjQJpivv/7abPrYyZMnm/s0HPTq1Uv69+8v0dHRJhB0797dzLmSlqNHj0qrVq1MSPn+++9ly5Yt5rnJycnm/rffflvefPNN+dvf/ibbt283Ie7BBx+UvXv3Zur86XwvegwNYD/88IMJbMOHDzf36U89b+5wo1uzZs0kL9G0BACW0QuJd+2BE2ntR3BwsDjBnXfeKVOmTPHcfu2110yIef311z37PvzwQ6lSpYr8+uuvpgZGw89HH30kERER5v45c+ZIpUqV0n2NlStXyo4dO+TgwYPmOGr+/PlSt25d05fm3nvv9QQereXQ2hOlNUH63IkTJ5oQoCFEw8sdd9xh7tfamfTMmDHD1OosWLDATPevatWq5blfw8eoUaNMrY964403ZNWqVaYWSZ97s3RotAbAGjVqmNtaw6U1PUpraLT2KDEx0czQ6wsEGQCwLMSEhtaWhIR4cbIiRYrK7t3RjggzjRo1SnH7l19+MRd0vQinpjUmCQkJ5uLduHFjz34NDCEhIem+htagaIBxhxhVp04d0yyj97mDjDYpuUOM0tDknt02LCzMBCcNL1p70r59e3n44YelTJkyab6mNgO1bNnSE2K8xcXFybFjx6R58+Yp9uttff+ZnevFHWJSl9kJCDIAYBGtidEQE95/nJQMqipOFBd7SDZ8OMGU1QlBJvXIoosXL0rnzp1NDUVqepHet29frpUldejQPiVaS6N0Zlttklq3bp1899138u6778rLL78sGzZskGrVqqU7M25WFSjwf71LvJuu0pqYLq0yp9fc5QsEGQCwkIaYwOD0awiQvrvvvlu++OILUztSsOD1l8Hq1aubi7c2CbmD2Pnz502zk/ZJSYt2hD1y5IjZ3LUyu3btMv1xtGbmZmlI0FoT3caOHWuamBYuXGj67KRWv3590wSm4SN12NDOv9oUpv187rvvPs9+ve2uaXKPutImLXetj9byZJb2P9IlJHyFzr4AgHxlyJAhplOudqzVsKLNSToq54knnjAXZG366du3r4wYMcI0QenIoAEDBpgajPRmotXRRdokpCOdtFPwxo0b5fHHHzch4p577rmpcmnN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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_depth_histogram.by_modification(\n", + " mod_file_name=pileup_file,\n", + " regions=ctcf_target_regions,\n", + " window_size=1000,\n", + " motifs=['A,0'],#,'CG,0'],\n", + " one_depth_per_region=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1b2b4fd395cc4172afcc350552b0c69b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/50 [00:00" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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CgAAwAVCFAAAgAuEKAAAABcIUQAAAC4QogAAAFwgRAEAALhAiAIAAHCBEAUAAOACIQoAAMAFQhQAAIALhCgAAAAXCFEAAAAuEKIAAABcIEQBAAC4kNHNmwAAQGiLiYmRM2fOJHr77NmzS2RkpKQlhCgAABAvQBUvUVJiY04m+spEROaSA9H701SQIkQBAAA/WgKlAaph7wkSHpH3plfnfOwJWRjV3t5HiAIQD0XbANIaDVBZc+W73YeRalESBSQCRdsAgLgIUUAiULQNAIiLEAUkAUXbAAAH40QBAAC4QIgCAABwgRAFAADgAiEKAADABUIUAACAC4QoAAAAFwhRAAAAoRaioqKi5P7775ccOXJIvnz5pHnz5rJ7926/bS5cuCBdunSRPHny2AzSLVu2lGPHjvltc+jQIXnssccka9astp+ePXvKlStX/LZZunSp3HfffZI5c2YpXbq0TJo0KUXOEQAABKdUHaKWLVtmAWn16tWyYMECuXz5sjRq1EjOnj3r3ebVV1+V77//XqZPn27b//bbb9KiRQvv+qtXr1qAunTpkqxcuVI+//xzC0h9+/b1bhMdHW3bPPTQQ7J582bp3r27tG/fXubNm5fi5wwAAIJDqh6xfO7cuX6vNfxoSdKGDRukfv36EhsbK59++ql8+eWX8vDDD9s2EydOlHLlylnwqlmzpsyfP1927NghCxculPz580uVKlXknXfekTfeeEP69+8vmTJlknHjxkmJEiVk2LBhtg99/08//SQjRoyQxo0b35ZzBwAAqVuqLomKS0OTyp07tz1rmNLSqYYNG3q3KVu2rBQtWlRWrVplr/W5YsWKFqAcGoxOnTol27dv927juw9nG2cfCbl48aLtw/cBAADSjqAJUdeuXbNqtjp16sg999xjy44ePWolSZGRkX7bamDSdc42vgHKWe+su9E2GozOnz9/3fZaERER3keRIkUCeLYAACC1C5oQpW2jtm3bJlOnTpXUoHfv3lYy5jwOHz58uw8JAACkoFTdJsrRtWtXmTVrlixfvlwKFy7sXV6gQAFrMB4TE+NXGqW983Sds83atWv99uf03vPdJm6PPn2dM2dOCQ8PT/CYtBefPgAAQNqUqkOUx+ORl19+WWbMmGFDEGjjb19Vq1aVsLAwWbRokQ1toHQIBB3SoFatWvZan9977z05fvy4NUpX2tNPA1L58uW928yZM8dv37qNsw8gpeg/CM6cOZPo7XVYj7jV2QCAlJExtVfhac+7b7/91saKctowaRskLSHS53bt2kmPHj2ssbkGIw1dGn60Z57SIRE0LLVu3VoGDx5s++jTp4/t2ylJ6tSpk4wePVp69eolL774oixevFi++uormT179m09f6S9AFW8REmJjTmZ6PdEROaSA9H7CVIAcBuk6hA1duxYe37wwQf9luswBm3btrWfdRiC9OnTW0mU9pjTXnX//Oc/vdtmyJDBqgI7d+5s4SpbtmzSpk0bGThwoHcbLeHSwKRjTo0cOdKqDCdMmMDwBkhRWgKlAaph7wkSHpH3ptufjz0hC6Pa2/sojQKAlJfqq/NuJkuWLDJmzBh7XE+xYsXiVdfFpUFt06ZNro4TCCQNUFlz/b+qZwBA6hU0vfMAAABSE0IUAACAC4QoAAAAFwhRAAAALhCiAAAAXCBEAQAAuECIAgAAcIEQBQAA4AIhCgAAwAVCFAAAgAuEKAAAABcIUQAAAC4QogAAAFwgRAEAALhAiAIAAHCBEAUAAOACIQoAAMAFQhQAAIALhCgAAAAXCFEAAAAuEKIAAABcIEQBAAC4QIgCAABwgRAFAADgAiEKAADABUIUAACACxndvAlIjWJiYuTMmTOJ3j579uwSGRmZrMcEAAhdhCiETIAqXqKkxMacTPR7IiJzyYHo/QQpAIArhCiEBC2B0gDVsPcECY/Ie9Ptz8eekIVR7e19lEYBANwgRCGkaIDKmivf7T4MAEAaQMNyAAAAFwhRAAAALhCiAAAAXCBEAQAAuECIAgAAcIHeeQAAINUPkJwaB0kmRAEAgFQ/QHJqHCSZEAUAAFL1AMmpdZBkQhQAALgtwoN8gGQalgMAALhAiAIAAHCB6jykiFDohQEAgC9CFJJdqPTCAADAFyEKyS5UemEAAOCLNlFxjBkzRooXLy5ZsmSRGjVqyNq1a+NuglvshZGYR2LDFgAAtwshyse0adOkR48e0q9fP9m4caNUrlxZGjduLMePH79tNwgAAKROhCgfw4cPlw4dOsgLL7wg5cuXl3HjxknWrFnls88+u313CAAApEq0ifqfS5cuyYYNG6R3797ei5M+fXpp2LChrFq1SoKtd1tSe7Yl9/4BAAg1hKj/OXHihFy9elXy58/vd4H09a5du+JduIsXL9rDERsba8+nTp0K+E3SgFOxUmU5FRuT6PfkyBkhC+bPk5w5c950Wz3mvzRuLKf/dw6B3v/Ro0ft+fTxw3L5wrlE7f/CqT/sec+ePXL69OmAf0Zq239KfEaw7z8lPiO17T8lPiO17T8lPiPY958Sn5Ha9u/7Gbr/QH7XOvvyeDxJf7MH5tdff9Wr51m5cqXfFenZs6enevXq8a5Sv379bHseXAN+B/gd4HeA3wF+ByTor8Hhw4eTnIgoifqfvHnzSoYMGeTYsWN+IVNfFyhQIF741Go/bYTuuHbtmvz555+SJ08eSZcunSXbIkWKyOHDhxNVWhMKOGfuc6jid5vf7VDF73ZOK4HS0q1ChQol+foRov4nU6ZMUrVqVVm0aJE0b97cG4z0ddeuXeNduMyZM9vDV0JthDRApZUQ5eCc0wbuc9rAfU4b0vp9joiIcLUPQpQPLVlq06aNVKtWTapXry4ffvihnD171nrrAQAA+CJE+Xj66afl999/l759+1qjtypVqsjcuXPjNTYHAAAgRMWhVXcJVd8llVb16aCdcav8QhnnnDZwn9MG7nPawH2+Nem0dfkt7gMAACDNYcRyAAAAFwhRAAAALhCiAAAAXCBEJZMxY8ZI8eLFJUuWLFKjRg1Zu3athKr+/fvbAKO+j7Jly0ooWb58uTRt2tQGY9Pzmzlzpt96bVqovToLFiwo4eHhNueiTn8Qyufctm3bePe9SZMmEqyioqLk/vvvlxw5cki+fPlsvLjdu3f7bXPhwgXp0qWLDaqr80e2bNky3gC9oXbODz74YLz73KlTJwlWY8eOlUqVKnnHCKpVq5b88MMPIXuPE3POoXaPEzJo0CA7r+7duwf0XhOiksG0adNszCntnbdx40apXLmyNG7cWI4fPy6hqkKFCnLkyBHv46effpJQouOF6X3UcJyQwYMHy6hRo2TcuHGyZs0ayZYtm91z/SMN1XNWGpp87/uUKVMkWC1btsz+h7p69WpZsGCBXL58WRo1amTXwfHqq6/K999/L9OnT7ftf/vtN2nRooWE8jmrDh06+N1n/X0PVoULF7YvVJ1wfv369fLwww9Ls2bNZPv27SF5jxNzzqF2j+Nat26dfPzxxxYkfQXkXjN1XuDpXHtdunTxvr569aqnUKFCnqioqJC83DqPYOXKlT1phf7ZzJgxw/v62rVrngIFCniGDBniXRYTE+PJnDmzZ8qUKZ5QPGfVpk0bT7NmzTyh6vjx43bey5Yt897TsLAwz/Tp073b7Ny507ZZtWqVJxTPWT3wwAOebt26eUJZrly5PBMmTEgT9zjuOYf6PT59+rTnrrvu8ixYsMDvPAN1rymJCrBLly5Z2tfqHEf69Ont9apVqyRUadWVVvuULFlSWrVqJYcOHZK0Ijo62gZn9b3nOoWAVuOG8j1XS5cutWqgMmXKSOfOneWPP/7fLOuhIDY21p5z585tz/p3rSU1vvdZq62LFi0aMvc57jk7Jk+ebPOL3nPPPTZv6Llz5yQUXL16VaZOnWolb1rFlRbucdxzDvV73KVLF3nsscf87qkK1L1msM0AO3HihP2Sxh3lXF/v2rVLQpGGhUmTJtkXqRYDDxgwQOrVqyfbtm2zthahTgOUSuieO+tCkVbladF3iRIlZN++ffLmm2/KI488Yv8D0sm8g5nOm6ltJ+rUqWNfKkrvpc6xGXeOzFC5zwmds3ruueekWLFi9o+kLVu2yBtvvGHtpr755hsJVlu3brUAodXt2hZmxowZUr58edm8eXPI3uPrnXOo3mOlYVGb1Gh1XlyB+nsmROGW6RenQ+ucNVTpH+RXX30l7dq14wqHqGeeecb7c8WKFe3elypVykqnGjRoIMH+r1f9R0Cote1zc84dO3b0u8/aeULvrwZnvd/BSP/Bp4FJS96+/vprmzNV28SEsuudswapULzHhw8flm7dullbP+3glVyozgswLQ7Vf4XHbeGvrwsUKCBpgSb7u+++W/bu3StpgXNf0/I9V1qVq7//wX7fddqnWbNmyZIlS6xBrkPvpVbXx8TEhNx9vt45J0T/kaSC+T5rCUTp0qWlatWq1kNRO1CMHDkypO/x9c45VO/xhg0brDPXfffdJxkzZrSHhkbtAKQ/a4lTIO41ISoZflH1l3TRokV+xeT62rf+OZSdOXPG/gWj/5pJC7Q6S//ofO/5qVOnrJdeWrnn6r///a+1iQrW+67t5zVMaDXH4sWL7b760r/rsLAwv/usVR7a/i9Y7/PNzjkhWpqhgvU+J0T/H33x4sWQvMc3O+dQvccNGjSwKkw9F+dRrVo1a7Pr/ByQe50szeHTuKlTp1rPrEmTJnl27Njh6dixoycyMtJz9OhRTyh67bXXPEuXLvVER0d7VqxY4WnYsKEnb9681tMnlHp4bNq0yR76ZzN8+HD7+eDBg7Z+0KBBdo+//fZbz5YtW6zXWokSJTznz5/3hOI567rXX3/derHofV+4cKHnvvvus14wFy5c8ASjzp07eyIiIux3+ciRI97HuXPnvNt06tTJU7RoUc/ixYs969ev99SqVcsewepm57x3717PwIED7Vz1Puvvd8mSJT3169f3BKt//OMf1vtQz0f/VvV1unTpPPPnzw/Je3yzcw7Fe3w9cXshBuJeE6KSyUcffWQ3J1OmTDbkwerVqz2h6umnn/YULFjQzvXOO++01/qHGUqWLFliQSLuQ7v5O8McvP322578+fNbgG7QoIFn9+7dnlA9Z/2SbdSokeeOO+6wbsLFihXzdOjQIaj/oZDQuepj4sSJ3m00FL/00kvWPTxr1qyeJ554wkJHqJ7zoUOH7Ms0d+7c9ntdunRpT8+ePT2xsbGeYPXiiy/a76v+/0p/f/Vv1QlQoXiPb3bOoXiPExuiAnGv0+l/kqcwDQAAIHTRJgoAAMAFQhQAAIALhCgAAAAXCFEAAAAuEKIAAABcIEQBAAC4QIgCAABwgRAFAADgAiEKAJJZ//79pUqVKqnmOrdt21aaN29+uw8DCHqEKAAh68CBA5IuXTrvhKoAEEiEKABB4erVqzbzfHK4dOmSpCY6G9eVK1du92EAuAlCFICAe/DBB6Vr1672iIiIkLx588rbb79t4cBx8eJFef311+XOO++UbNmySY0aNWTp0qXe9ZMmTZLIyEj57rvvpHz58pI5c2Y5dOhQvM86efKktGrVSu644w4JDw+Xu+66SyZOnGjrSpQoYc/33nuvlUjpcflWZ7333ntSqFAhKVOmjC3/17/+JdWqVZMcOXJIgQIF5LnnnpPjx497P0uPT/ezaNEi2y5r1qxSu3Zt2b17t98xDRo0SPLnz2/7adeunVy4cOGG18vZ7w8//CBVq1a1c/3pp58sNEZFRdl56LlVrlxZvv76a79gqft31ut5jBw50m/fuk2PHj3sWubJk0d69erldx8A3IIkTVcMAImcLT179uw2Y/quXbs8//73v22W9PHjx3u3ad++vad27dqe5cuXe/bu3esZMmSIzSL/yy+/2PqJEyd6wsLCbJsVK1bYfs6ePRvvs7p06eKpUqWKZ926dZ7o6GjPggULPN99952tW7t2raYFz8KFC2129j/++MOWt2nTxo6vdevWnm3bttlDffrpp545c+Z49u3b51m1apWnVq1ankceecT7WUuWLLH91ahRw7N06VLP9u3bPfXq1bNjdEybNs3OY8KECXbMb731lidHjhyeypUrX/d6OfutVKmSZ/78+XY99FjfffddT9myZT1z5861Y9JrovvWz1aXLl3y9O3b1859//793uusx+D44IMPbJb6//znP54dO3Z42rVrZ8fTrFkzfpeBW0SIApAsIapcuXKea9eueZe98cYbtkwdPHjQkyFDBs+vv/7q974GDRp4evfubT9rYNBgsXnz5ht+VtOmTT0vvPBCgus0VOk+Nm3a5LdcQ1T+/Pk9Fy9evOG+NZzo+0+fPu0XdjSUOWbPnm3Lzp8/b681eL300kt++9HQlZgQNXPmTO+yCxcuWCBauXKl37Yagp599tnr7ktDZcuWLb2vCxYs6Bk8eLD39eXLlz2FCxcmRAEBQHUegGRRs2ZNq6Jy1KpVS/bs2WPVS1u3brXnu+++W7Jnz+59LFu2TPbt2+d9T6ZMmaRSpUo3/JzOnTvL1KlTrfebVlWtXLkyUcdXsWJF27+vDRs2SNOmTaVo0aJWFffAAw/Y8rjViL7HVLBgQXt2qv127txpVZO+9NwTQ6sIHXv37pVz587JX/7yF79r9MUXX/hdozFjxlgVoFZn6vrx48d7jzc2NlaOHDnidzwZM2b0+xwA7mW8hfcCgCtnzpyRDBkyWGjRZ18aBBzazsc3iCXkkUcekYMHD8qcOXNkwYIF0qBBA+nSpYsMHTr0hu/Tdli+zp49K40bN7bH5MmTLZRoGNHXcRueh4WFeX92ji8Qjd59j0mvkZo9e7a1G/OlbaaUhkdtVzZs2DALahr8hgwZImvWrLnlYwFwc4QoAMki7hf56tWrrdG3hiZt6K0lUVp6U69evVv+LA08bdq0sYfur2fPnhainJIm/ayb2bVrl/zxxx/WKLxIkSK2bP369Uk+lnLlytm5P//8837nnlS+jemdErG4VqxYYQ3bX3rpJe8y31IqbdSvJWV6PPXr17dl2utPw+t9992X5GMC4I8QBSBZ6Je/9gr7v//7P9m4caN89NFHVmKitBpPe9Rp0NBlGqp+//136/WmVWWPPfZYoj+nb9++Vp1VoUIF6/E3a9YsCzIqX758Vpo1d+5cKVy4sGTJksWCRUK0Ck9Dlx5np06dZNu2bfLOO+8k+by7detmvf+0yqxOnTpWqrV9+3YpWbJkkvajpUpayvTqq69aKVfdunWtek6DU86cOS0waijV6r158+ZZDz3tXbhu3Tpvr0TneDQY6rZly5aV4cOHS0xMTJLPC0B8tIkCkCw0IJ0/f16qV69u1Wv6Zd6xY0fveh2GQLd57bXXrGu+DjmgAUDDTFJo8Ondu7eFLy1t0ZIureZy2v+MGjVKPv74YxvKoFmzZjcszdJhFaZPn26lQBo8blYlmJCnn37ahnPQ9lka7rSqUdttuaEhTvelwxxoMGzSpIlV7zkhSQNqixYt7DO13ZOWpPmWSim9vq1bt7bQ5VT5PfHEE66OB4C/dNq6PM4yALglOh6TNvT+8MMPuZIAQhYlUQAAAC4QogAAAFygOg8AAMAFSqIAAABcIEQBuOVG5N27d0+Rq6gDW86cOVPSsuLFi9+0wX7//v2tYT+A5EWIApDqEAKuT4eB8B0qIqFgqeNL6ZhbAJIXg20CQBLpNDBx591LKTqe1c048+wBSF6URAFINJ1fTgfI1C9onU7EGYHcl44ariUhOt+bzgWng0AuXbrUu14HtIyMjLTSEx1FW0cR1/npDh8+7F0/YMAA+fnnn62URR+6zHHixAkbLDJr1qz2/u++++6m1V86aOWzzz5rx6PHpZP2+tIRvNu3b28BRUcDf/jhh+3z45aMTZgwwQa61GNOyM3OzTF27FgpVaqUBTEdaFRHGnfo0H36eTroqE77ooOEvvLKKwlW5+nPSq+HXifnddySPB3xfODAgTZqu+5T1+ko7o4DBw7Y+7/55ht56KGH7NpWrlxZVq1adcNrC6R5OtgmACRG586dPUWLFvUsXLjQs2XLFs/jjz/uyZEjh6dbt27ebdq3b++pXbu2Z/ny5Z69e/d6hgwZ4smcObPnl19+sfUTJ070hIWFeapVq+ZZuXKlZ/369Z7q1avbe9S5c+c8r732mqdChQqeI0eO2EOX/W9gYE/hwoU9X375pWfPnj2eV155xZM9e3bPH3/8cd1jLlasmB1jVFSUZ/fu3Z5Ro0Z5MmTI4Jk/f753m4YNG3qaNm3qWbdunR2nfn6ePHm8++3Xr58nW7ZsniZNmng2btzo+fnnnxP8rJudm/rmm29smzFjxtjxDBs2zI5n8eLFtn769OmenDlzeubMmeM5ePCgZ82aNZ7x48f7nc+IESPs5+PHj9s10c/V66SvneOtXLmy9z3Dhw+3fU6ZMsWza9cuT69evewYnHsSHR1t+ylbtqxn1qxZdlxPPvmkfdbly5f54wCugxAFIFFOnz7tyZQpk+err77yLtOQER4e7g1R+qWvgeDXX3/1e2+DBg08vXv3tp/1C1+/sFevXu1dv3PnTlumgSGhEOD9H5aIp0+fPt7XZ86csWU//PDDdY9bg4CGH19PP/2055FHHrGff/zxRwsYFy5c8NumVKlSno8//th7PBo6nJByPYk5Nw1UHTp08HvfU0895Xn00UftZw1Vd999t+fSpUvXPR8nRDnXZMaMGX7bxL1+hQoV8rz33nt+29x///2el156yS9ETZgwwbt++/bttkyPH0DCqM4DkCj79u2ztkBaPefInTu3VUc5tm7dKlevXrUJhp12OfpYtmyZvd+hc9rdf//93tc6Ma5Wg+3cufOmx6Fz5Dm0ek6r344fP37D9+iccXFfO5+l1XZnzpyRPHny+B1zdHS03zEXK1YsUe2RbnZu+qwTE/vS1876p556yuYc1AmLO3ToIDNmzJArV66IW6dOnZLffvvthp+Z0LXV6lp1s2sLpGU0LAcQMBpGdALgDRs22LOvQDV0DgsL83utbXm0zc+tHLMGBt92Ww4NP76BLSUUKVJEdu/eLQsXLpQFCxbYhMJDhgyxIBr33APNd/96XdWtXFsg1FESBSBRtCG0fsmuWbPGu+zkyZPyyy+/eF/fe++9VhKlpRelS5f2exQoUMC7nZasrF+/3vtaQ4M27i5Xrpy91gbXup9AWb16dbzXzmfdd999cvToUStBinvMefPmTfJn3ezc9HnFihV+79HX5cuX974ODw+Xpk2byqhRoyzcaQNvLeVLiN6TG10rLanTxuk3+0wASUdJFIBE0ZKkdu3aSc+ePa3qK1++fPLWW29J+vT//7/FtBqvVatW1oNPe+5pqPr9999tzCKtKnrssce8X/wvv/yyhQQNL127dpWaNWtK9erVbb32MtPqtM2bN1uPshw5clivMrc0MAwePFiaN29upTvTp0+X2bNn27qGDRta9Z6u0230HLT6S9drr7dq1aol6bNudm56/f72t7/ZtdHP/v77761XnJY8OT38NBRptan2kvv3v/9toUqrExOi10qvr1bP6TXKlStXvG30M/v162dBWHvmTZw40a7t5MmTXVxNAA5KogAkmlYr1atXz0pJNADUrVtXqlat6reNfkFriHrttdesvZSGEx0gUrvsOzQcvPHGG/Lcc8/Zl78GtGnTpnnXt2zZUpo0aWLd7bUd0pQpU27pLumxaOmQBpd3331Xhg8fbkMPONVWc+bMkfr168sLL7xgIeqZZ56RgwcPSv78+ZP8WTc7N70eI0eOlKFDh0qFChXk448/tmumI787VYiffPKJvVeDp4YrDVoaXBOiYVWDoVYD6vklRIdI6NGjh12HihUr2vAGOjSEDsMAwD0mIAaQorSkRaeJ0SqulKAlNfp5KTE1TUqfG4Dbi5IoAAAAFwhRAAAALlCdBwAA4AIlUQAAAC4QogAAAFwgRAEAALhAiAIAAHCBEAUAAOACIQoAAMAFQhQAAIALhCgAAAAXCFEAAACSdP8f+xS3anRl1GIAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_depth_histogram.by_modification(\n", + " mod_file_name=pileup_file,\n", + " regions=ctcf_target_regions,\n", + " window_size=1000,\n", + " motifs=['A,0'],#,'CG,0'],\n", + " one_depth_per_region=False,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Export" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For some use cases, such as loading pileup information into a genome browser, it can be desireable to create files with less information content than the full bedmethyls. The `export` module contains functions to do these sorts of exports. " + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pileup_file: dimelo/test/output/ctcf_demo_pileup_on_target/pileup.sorted.bed.gz\n", + "ref_genome_file: dimelo/test/output/chm13.draft_v1.0.fasta\n", + "bigwig_out_no_ref: dimelo/test/output/ctcf_demo_pileup_on_target/pileup.fractions.no_ref.bigwig\n", + "bigwig_out_with_ref: dimelo/test/output/ctcf_demo_pileup_on_target/pileup.fractions.with_ref.bigwig\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "\n", + "pileup_file = Path(pileup_file)\n", + "ref_genome_file = Path(ref_genome_file)\n", + "\n", + "if not pileup_file.exists():\n", + " raise FileNotFoundError(\n", + " f\"Expected pileup file not found: {pileup_file}. Run parsing cells above first.\"\n", + " )\n", + "\n", + "bigwig_out_no_ref = pileup_file.parent / 'pileup.fractions.no_ref.bigwig'\n", + "bigwig_out_with_ref = pileup_file.parent / 'pileup.fractions.with_ref.bigwig'\n", + "\n", + "for p in [bigwig_out_no_ref, bigwig_out_with_ref]:\n", + " p.unlink(missing_ok=True)\n", + "\n", + "print('pileup_file:', pileup_file)\n", + "print('ref_genome_file:', ref_genome_file)\n", + "print('bigwig_out_no_ref:', bigwig_out_no_ref)\n", + "print('bigwig_out_with_ref:', bigwig_out_with_ref)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d85fe1c0ce174e12a42636a6e5356622", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Step 1: Indexing contigs in pileup.sorted.bed.gz to set up bigwig header for pileup.fractions.no_ref.bigwig: …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "041a0993dd78416e8bc59891a1e6470b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr1.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ade839abde114d0a9020f772156a3048", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr12.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "31870c31134645e48e02feebba50bb51", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr14.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4c18dcda64604bee96b1ca2c8e62ea58", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr15.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "07b335ee5a184393baf57254a2d204f4", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr16.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5595c3201c8e4f4aa39b9c8d984536b4", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr17.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "fca642ab9bfd47678c9ed89ba7fac6d0", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr19.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5e1dc0dfdcc34568bddd9c94ff8fa8f8", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr2.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e92141a5266e4e2b90749880910ababc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr20.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "dd77f4cb094b4e9bb7f63090eb2b612e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr21.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6ceacd6482ff40b2a36e302b28f9c265", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr3.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "34332d129bf34f0e958f9df8a81bb862", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr5.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b22bf705ff974f60a60a5f37d267f549", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr6.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "45d8469786f8484ba2bbabc99e78c266", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr7.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "9b761bf45f1a473fbd69abee1b20dacb", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr8.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4d669163ff1c44df924d25d288bd351b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chr9.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4867073420c14455b3fa7bed8ec19b5b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Indexing chrX.: 0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d4d1f972f5f047cba0647a67ae110df8", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Step 2: Writing pileup.sorted.bed.gz contents to pileup.fractions.no_ref.bigwig: 0%| | 0/17 [00:00<…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "2abdf2fcdd5c40b8b8b456c6293ed45f", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Writing chr1.: 0%| | 0/24430 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sort_by = ['chromosome','region_start'] # you can sort reads also!\n", + "plot_reads.plot_reads(\n", + " mod_file_name=extract_file_no_thresh,\n", + " regions=ctcf_target_regions,\n", + " motifs=['A,0', 'CG,0'],\n", + " window_size=1000,\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " regions_5to3prime=True, # if set to True, regions on the reverse (-) strand will be flipped so everything is 5 prime to 3 prime\n", + " thresh=128,\n", + " sort_by=sort_by,\n", + " s=2, \n", + ")\n", + "plt.title(f'CTCF target data, 100 known binding locations\\nsort_by={sort_by}')\n", + "plt.xlabel('position relative to annotated CTCF site')\n", + "plt.ylabel('reads')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Manually Loading Processed Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load sum total modified/valid base read counts by genomic regions" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [], + "source": [ + "from dimelo import load_processed" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f00d737907dc48f590a9bbbf0924ce2d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/50 [00:00= this value (bp)\n", + " sort_by = ['chromosome','read_start','read_name']\n", + ")\n", + "# Print out the data from the first read\n", + "for idx,dataset in enumerate(datasets):\n", + " print(dataset)\n", + " for read_data in read_data_list:\n", + " print('\\t'+str(read_data[idx]))\n", + " break\n", + "# create a list of dicts for easy processing\n", + "list_of_dicts = [dict(zip(datasets,data)) for data in read_data_list]\n", + "# pull out a read by info about it, in this case the name and motif\n", + "# note that if your regions parameter for load_processed.read_vectors_from_hdf5 has multiple windows \n", + "# and a read is present in more than one of them, that read will be returned more than once, which is\n", + "# necessary and correct for e.g. the plot_reads functionality\n", + "print([read for read in list_of_dicts \n", + " if read['read_name']=='b35229d7-6592-48fa-ab21-53154531c984' \n", + " and read['motif']=='CG,0']\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "chromosome\n", + "\t chr14\n", + "mod_vector\n", + "\t [0. 0. 0. ... 0. 0. 0.]\n", + "motif\n", + "\t A,0\n", + "read_end\n", + "\t 44230138\n", + "read_name\n", + "\t b35229d7-6592-48fa-ab21-53154531c984\n", + "read_start\n", + "\t 43990221\n", + "strand\n", + "\t -\n", + "val_vector\n", + "\t [0 0 0 ... 0 0 0]\n", + "region_start\n", + "\t 44125008\n", + "region_end\n", + "\t 44125326\n", + "region_strand\n", + "\t .\n", + "read_length\n", + "\t 239917\n", + "CG,0_mod_fraction\n", + "\t 0.09485774253731344\n", + "A,0_mod_fraction\n", + "\t 0.020597014925373136\n", + "[{'chromosome': 'chr14', 'mod_vector': array([0., 0., 0., ..., 0., 0., 0.], shape=(239917,), dtype=float16), 'motif': 'CG,0', 'read_end': np.int32(44230138), 'read_name': 'b35229d7-6592-48fa-ab21-53154531c984', 'read_start': np.int32(43990221), 'strand': '-', 'val_vector': array([0, 0, 0, ..., 0, 0, 0], shape=(239917,)), 'region_start': 44125008, 'region_end': 44125326, 'region_strand': '.', 'read_length': np.int32(239917), 'CG,0_mod_fraction': np.float64(0.09485774253731344), 'A,0_mod_fraction': np.float64(0.020597014925373136)}]\n" + ] + } + ], + "source": [ + "read_data_list, datasets, _ = load_processed.read_vectors_from_hdf5(\n", + " file=extract_file_no_thresh, # raw modification probabilities\n", + " regions='chr14:44125008-44125326',\n", + " motifs=['CG,0','A,0'],\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " # Optional subsetting controls:\n", + " # random_sample_n_reads=2000,\n", + " # min_read_length_bp=5000,\n", + ")\n", + "# Print out the data from the first read\n", + "for idx,dataset in enumerate(datasets):\n", + " print(dataset)\n", + " for read_data in read_data_list:\n", + " print('\\t',read_data[idx])\n", + " break\n", + "# create a list of dicts for easy processing\n", + "list_of_dicts = [dict(zip(datasets,data)) for data in read_data_list]\n", + "# pull out a read by info about it, in this case the name and motif\n", + "# note that if your regions parameter for load_processed.read_vectors_from_hdf5 has multiple windows \n", + "# and a read is present in more than one of them, that read will be returned more than once, which is\n", + "# necessary and correct for e.g. the plot_reads functionality\n", + "print([read for read in list_of_dicts \n", + " if read['read_name']=='b35229d7-6592-48fa-ab21-53154531c984' \n", + " and read['motif']=='CG,0']\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load regions to a list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can run any load_processed loader function one-by-one through a set of regions, rather than aggregating to a single output. This is implemented by the `load_processed.regions_to_list`, which takes a function handle to e.g. `load_processed.pileup_counts_from_bedmethyl` and runs it with the specified parameters but running each region in the regions specifier separately." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[(124, 3608), (124, 3556), (134, 3430), (75, 3498), (74, 3478), (85, 2396), (89, 2164), (89, 2164), (213, 2986), (213, 2932), (132, 3404), (143, 3552), (143, 3552), (72, 2882), (70, 2600), (70, 2546), (236, 4720), (114, 4194), (113, 4142), (105, 3072), (105, 3072), (108, 3114), (108, 3094), (254, 3788), (254, 3788), (249, 3716), (249, 3716), (63, 2152), (63, 2142), (49, 2116), (49, 2098), (135, 3576), (135, 3550), (128, 3248), (128, 3248), (80, 3756), (231, 5658), (231, 5596), (228, 5312), (249, 5262), (249, 5194), (157, 3334), (128, 3812), (128, 3812), (109, 3032), (109, 2992), (101, 2726), (101, 2686), (175, 3912), (175, 3884), (173, 3816), (158, 3296), (158, 3246), (167, 3072), (167, 3072), (176, 2468), (176, 2432), (179, 2826), (145, 4100), (144, 4066), (86, 3012), (86, 3012), (101, 2318), (101, 1944), (100, 1916), (194, 4550), (194, 4108), (125, 2520), (125, 2496), (118, 2472), (79, 2372), (79, 2346), (84, 2356), (121, 2316), (121, 2316), (218, 2746), (218, 2746), (254, 2700), (253, 2680), (81, 2066), (92, 1990), (92, 1990), (174, 4172), (190, 4106), (190, 4106), (275, 4486), (150, 3814), (149, 3756), (66, 3150), (64, 3318), (64, 3318), (160, 4906), (160, 4906), (67, 3250), (31, 1134), (220, 3104), (220, 3054), (219, 3286), (166, 3916), (166, 3916)]\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "pileup_file = 'dimelo/test/output/ctcf_demo_pileup_on_target/pileup.sorted.bed.gz'\n", + "# Base input and output directories\n", + "test_data_dir = Path('./dimelo/test/data')\n", + "output_dir = Path('./dimelo/test/output')\n", + "\n", + "output_dir.mkdir(exist_ok=True)\n", + "\n", + "# Input files\n", + "ctcf_bam_file = test_data_dir / 'ctcf_demo.sorted.bam'\n", + "ctcf_guppy_bam_file = test_data_dir / 'winnowmap_guppy_merge_subset.updated.bam'\n", + "ctcf_target_regions = test_data_dir / 'ctcf_demo_peak.bed'\n", + "ctcf_off_target_regions = test_data_dir / 'ctcf_demo_not_peak.bed'\n", + "from dimelo import load_processed\n", + "print(load_processed.regions_to_list(\n", + " function_handle = load_processed.pileup_counts_from_bedmethyl,\n", + " regions=ctcf_target_regions,\n", + " bedmethyl_file = pileup_file,\n", + " motif = 'A,0',)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Mod code specification and adjustment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition to the modification context motif and mod quality threshold, the `dimelo` package can delineate different modification codes. For example, a cytosine called with Dorado on Nanopore R10 traces can have modification scores reported for both `m` (methylation) and `h` (hydroxymethylation). These have different biological relevance and may be important to distinguish.\n", + "\n", + "By default, the `dimelo` package only looks for methylation. However, the `motif` specifier can optionally include a third field, e.g. `CG,0,m` or `CG,0,h' to call out a specific mod code.\n", + "\n", + "Different base callers may use different mod codes however. For example, Guppy and Megalodon for R9 Nanopore data tend to us `Y` for adenine and `Z` for cytosine, while the standard is `a` and `m`.\n", + "\n", + "The `dimelo` package reports a warning if a specific mod code is not present, and the bundled `modkit` program offers a command line tool to make adjustments to bam files as desired." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "WARNING: no modified appropriately-coded values found for ['A', 'C'] in the first 100 reads. \n", + "Do you expect this file to contain these modifications? parse_bam is looking for ['A,0,a', 'CG,0,m'] but for ['A', 'C'] found only found [\"A+{'Y'}\", \"C+{'Z'}\"].\n", + "\n", + "Consider passing only the motifs and mod codes (e.g. m,h,a) that you expect to be present in your file. \n", + "You can use modkit adjust-mods --convert [OPTIONS] to update or consolidate mod codes.\n", + "See https://github.com/nanoporetech/modkit/blob/master/book/src/advanced_usage.md\n", + " \n", + "No specified number of cores requested. 10 available on machine, allocating all.\n", + "Detected multiple motif contexts with modkit 0.6.x; running per-motif pileups and merging outputs to avoid mixed-motif empty output behavior.\n", + "Modification threshold of 128.0 assumed to be for range 0-255. 128.0/255=0.5019607843137255 will be sent to modkit.\n" + ] + } + ], + "source": [ + "pileup_file_coded, pileup_regions = ensure_pileup_output(\n", + " input_file=ctcf_bam_file_updated,\n", + " output_name='ctcf_demo_pileup_on_target_mod_code_specified',\n", + " ref_genome=ref_genome_file,\n", + " output_directory=output_dir,\n", + " regions=[ctcf_target_regions,ctcf_off_target_regions],\n", + " motifs=['A,0,a','CG,0,m'],\n", + " thresh=128,\n", + " window_size=1000,\n", + " # cores=1,\n", + " # cleanup=False,\n", + " # log=True,\n", + " quiet=PARSE_QUIET,\n", + " modkit_executable=MODKIT_BINARY,\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`dimelo` lets you run even if the mod codes aren't found in the first 100 reads, but the warning is worth paying attention to: sometimes, as in this case, they actually aren't present at all!\n", + "\n", + "In that case, plotting will yield an empty plot. We clearly need to properly specify the mod codes if we want the right result." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5d57377d6bd74ce2ad60e11459029d71", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/50 [00:00" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAksAAAGwCAYAAAC5ACFFAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAQ7tJREFUeJzt3QeYE9X38PGzS++9KqAIUqQqHQQUpIkiogKCFBEUaQqCINL8gUhHEFFQARUEUUREpKOI9CKdBem9985u/s+57zt5kt3ssFkSdpN8P88TSGYmkzuZJHP23nPvDXM4HA4BAACAR+GeFwMAAIBgCQAA4C6oWQIAALBBsAQAAGCDYAkAAMAGwRIAAIANgiUAAAAbSe1WIm6ioqLk2LFjki5dOgkLC+NtAwAgAOhQk5cvX5bcuXNLeHjs9UcESz6ggVKePHl8sSsAAHCfHT58WB588MFY1xMs+YDWKFlvdvr06X2xSwAA4GeXLl0ylR3WdTw2BEs+YDW9aaBEsAQAQGC5WwoNCd4AAAA2CJYAAABsECwBAADYIGcJALwQGRkpt2/f5j0DAkCyZMkkSZIk97wfgiUAiON4LCdOnJALFy7wfgEBJGPGjJIzZ857GgeRYAkA4sAKlLJnzy6pU6dmAFogAP7AuXbtmpw6dco8zpUrV7z3RbAEAHFoerMCpSxZsvB+AQEiVapU5n8NmPT7G98mORK8AeAurBwlrVECEFis7+295BoSLAFAHDH3IxCa31uCJQAAABsESwAAADYIlgAgAPz555+mOcEaumDy5MmmS7SrCRMmmElBw8PDZfTo0dK/f38pVaqU38v20EMPmddLaH369JF27dolivOjZs+eLQUKFDBJxe+8844kNq1atZIXXnghTttWr179no/hwIED5j36999/xVeaNGkiI0aMEL9z4J5dvHjRoW+l/g8g+Fy/ft2xY8cO878nLVu2NL8Bb775Zox1b7/9tlmn29yLZcuWmf2cP3/ePL527Zrj5MmTzvX6+5MsWTLH2LFjHceOHXNcvXrVcfnyZceZM2ccvjJp0iRHhgwZYiw/deqUeb2EdPz4cUe6dOkcBw4c8PtrVatWzdGlSxe3ZTdv3jRliIqKci7Lnj274/3333ccPXrUcenSJUdio5/JBg0axGnbs2fP3vMx7N+/33yGN23a5PCVrVu3OjJlyuS4cOFCvL6/cb1+U7MEAD6gNTrTp0+X69evO5fduHFDpk2bJnnz5vVLl2jtCm05dOiQ6e3z7LPPmvFktAdQ2rRp78tQB9myZUvwnoJfffWVVKpUSfLly5cgr588eXK3gQ+vXLliuqvXrl1bcufOLenSpZNAljlz5kR5DMWKFZNHHnlEvv/+e7++DsESAPjA448/bgKmWbNmOZfpfQ2USpcu7bbtzZs3pXPnzibYSZkypVSpUkXWrVvnts28efPk0UcfNUHRU089ZZowXLk2w+n94sWLm/v58+c3F2zd3lMz3DfffCOPPfaYpEiRwgRVHTt2dK4bOXKk2U+aNGnMsbz99tvmom81M7Vu3VouXrxo9q833b+nZjgN3Bo0aGCCtfTp08srr7wiJ0+edK63yvXdd9+Z52bIkME0p1y+fNm5zU8//WTKosevAV/NmjXl6tWrsb7/Gqg+99xzMZqOOnXqZJqPMmXKJDly5JCJEyea/eix6MVfm8n++OMPt+f99ddfUq5cOed71LNnT7lz546z6UrXf/rpp873Qd9r12Y4vW8FFk8//bRZrsu8aRr7+OOPTXn1HH/00Ufm9bt3726ClgcffFAmTZrk9rytW7ea17LeL22OtM6dNVZY165dzf50fY8ePcygjXFVPVoznJ43LePrr79ujlU/59oM7Grt2rXms6+f8TJlysimTZti7Hfbtm1St25d81nR433ttdfkzJkzZp2+ZxqE/v33387thw4dar43rp8nPe96/v2JYAkAfEQvHK4XMQ1M9KIcnV6ofv75Z5kyZYps3LjRXLC1BuLcuXNm/eHDh+XFF180FwHN73jjjTfMBTs2jRs3lsWLFzsvUMePHzfBTnTjx4+XDh06mAupXlznzJljXtuiuU5jxoyR7du3m7ItXbrUlFVprY0GRBr86P719t5778V4jaioKBMo6bFoULFo0SLZt2+fKaOrvXv3mpyeuXPnmptu+8knn5h1uu+mTZua93Pnzp3moqnvR2wXd32tHTt2mAtydHocWbNmNe+LBk7t27eXl19+2RyPvve1atUyF2gd6VkdPXpU6tWrJ2XLlpXNmzeb9+zrr7+WgQMHmvUaJFWsWFHatm3rfB+iv9e674iICHNfz7Nuo8viSt/3Y8eOyfLly00A269fP6lfv74J+NasWSNvvfWWvPnmm3LkyBGzvQZ/+vnR9Rp0z5w503weXANhzevRoFo/kytWrDDv2S+//CL3YsSIEc4gSANrfW+t49ZATctctGhR2bBhgwmQo39eNLDUAE8DqvXr18v8+fNNEKTBtWuApudHg3R9Hc1L01pEDawsGtjq+dU/QvzGJ42GIY6cJSC4xSVnSXM/NHcnRYoUJm9GbylTpnScPn3arLNylq5cuWJyi6ZOnep8/q1btxy5c+d2DB061Dzu1auXo2jRom6vobkvrjlL0fOHNA9E12teiKVfv36OkiVLOh/ra/Tu3TvOxz1z5kxHlixZ7pqzlC9fPseoUaPM/YULFzqSJEniOHTokHP99u3bTdnWrl3rLFfq1KndcmC6d+/uKF++vLm/YcMGs31c84+sY3d9TSu3qEqVKs7Hd+7ccaRJk8bx2muvOZdpnpE+d9WqVebxBx984ChUqJBb7tG4ceMcadOmdURGRsaasxQ9p0z/18e63Bv6OdH303otpeV58sknYxzHDz/8YB5PmDDB5O3oZ8vy+++/O8LDwx0nTpwwj3PlyuX8fKnbt287HnzwwTjnLFWLdsxaxubNmzsf6/ulOVrjx483j7/88kvz2XH9zug615yl//3vf45atWq5vc7hw4fNNhEREc5csFKlSjleeeUV851o27ZtjLJt3rzZ9vPii5wlpjsBAB/m7mjOkP4Fr7Ugel9rNaLXqGhuUeXKld1mRte/jrUWRen/5cuXd3ue1mbcC82f0dqKGjVqxLqN1kYMHjxYdu3aJZcuXTJNP5p3pbUucc1J0rJrTYtrbYvWLmjzj67TGhurGcc1B0abu6w5vEqWLGnKqc1wWmOitT8vvfSSqTnxxMoT0+ae6EqUKOG8r73StAnKarJUVg2F9dpaRn2vXQcy1HOlNSVak+OP/LPotJlUa/lcy6i5OdGPw7XM+p5p86lrmbWWT2t69H3R2i3Xz1TSpElNrZA3TXF2762+X5qz5VomXe96TqJ/hrXmbtmyZaYJLjr9nmgztDbDTZ061exL89FGjRoV65QmVu2gP9AMBwA+pE1HGixp84/eTyysC0psNO9Gm030oqRNR9p0Mm7cOLPu1q1bPi+PBoiu9GKrF3crGNDmO80l0kBr7NixUqhQIdm/f7/HfVkB6fnz5+P0Oq7LrKDIeu3E4G5ltpYldJmT3WOZNAC1mppdb3v27JGqVas6t1u5cqX5X5sOraZqV9Yy/WPFXwiWAMCH6tSpY4ILrT3SWpHotOeO/rX8zz//OJfptpprooGBKlKkiMnBcLV69ep7KpfW4mhtzpIlSzyu1+BIL3Sah1KhQgXzV73WRLnScmuisB0tu+Zc6c2i+USan2IdX1zohVdrRwYMGGByVfS1Y8ux0fdUc6n0de6Vln/VqlVuNS56rvT908TquL4P95OWWWtpXBPgtcxaO6VBpibQa82d5jtZtNZQz7k/y7RlyxZTMxnbZ1g7RWh+nH4uNXfO9WbVkmkN07vvvmsS87VmrGXLljECMk0S13MTvRbXlwiWAMCHtFZEmyD0wu1phnO9CGgirPZs0oRW3U6ThbUJoU2bNmYbTeDVv651G21G0eEHtLbqXmmSrQZDmsSt+9cEZ621UXqB0qBNH2tCtvZU++KLL9yerxc1rQ3QgEt7LHlq9tBea9rM1axZM7N/DfpatGgh1apV85iA7Yle1LWnlSb9as867VV4+vRpcwH2RIMCfV1NXL5XmqisgZ4mg2tz5K+//moSrLUnmdU0pu+DllFr4/R9SOgaHn2vtblLAwkNHLRpS8uvidFWM2OXLl1MAr0m1etx6XG6DqDpa6+++qoJePWzrZ9x7d05fPhwt220s4HWCmkyv/6xoIHRggULTKcIDUb11rx5c/NHhy7TzhMagEUfhFJ7y2lTrT8RLAGAj2kth95ioxetRo0amYuZ/nX933//mYuElZOjeTHaFKYXNs1F0aBFg4d7pRdT7dH2+eefm7wYbXbToEnp62jPqyFDhpj8GM0T0fwlV9qjSwM57dmmTR7ajTs6vUBqgKHHok0pGsTocAYzZsyIczn1vdOeYNorTWu4PvzwQ3OB1C7msdEeg9p9/F4DlwceeMBc2DXI0/dEj1eDWC2DRXt1aSCsNWX6PmhAF1f6/vgi8HWl+WT6+dHAQ3PCNL9Lc74+++wz5zbdunUznzf9DGjukNaUNWzYUPwlbdq08ttvv5lel9rbrXfv3uaz5UrHn9IaMA2KNNjRIFt7v2l+mwamgwYNkoMHD8qXX35pttfaMR2eQM+F1qQprbnS74kGZf4Uplnefn2FEKCJkFrNqV0b7X4gAQQm/UHWfJmHH37YYxIxEp5eyrSZRptstKYiMdLPkAZ/WtNSsGDBhC5OUBg/frxpnl24cGG8vr9xvX5TswQACHhaY6O1DtbgkYmR1ljpGFcESr5NMreakv2JmiUfoGYJCG7ULCGYaTOiXfL9jh077suQCf7ii5olxlkCACCEae6Qdtm3Wx/qCJYAAAhhOkCl67Q3iImcJQAAABsESwAAADYIlgAAAGwQLAEAANggWAIAALBBsAQAAGCDYAkAEJJWrVpl5nh79tln4zSdSt++fc38ZKlSpTJz3lnz6iH4ESwBAELS119/LZ06dTKT9h47dsx2W500eMyYMWZS4zVr1kiaNGmkdu3aZnRoBD+CJQCAT2jty7Vbd+77LT7zwV+5ckVmzJgh7du3NzVLkydPtj2u0aNHm9nuGzRoICVKlJBvv/3WBFg6431s5s+fL1WqVJGMGTNKlixZpH79+rJ3716vy4qExwjeAACfuH47Uor2XXDf380dH9WW1Mm9u5z9+OOPUrhwYSlUqJA0b95c3nnnHenVq5eZkDc6nVfsxIkTpunNovOJlS9f3jTlNWnSxONrXL16Vbp27WqCKw3OtBmvYcOGZmqR8HDqKgIJwRIAICSb4DRIUnXq1DETqf71119SvXr1GNtqoKRy5MjhtlwfW+s8adSokdvjb775RrJly2Ympi1WrJiPjgT3A8ESAMAnUiVLYmp5EuJ1vRERESFr166VX375xTk3WuPGjU0A5SlYii9NANfaJM1xOnPmjERFRZnlhw4dIlgKMARLAACf0CYsb5vDEoIGRXfu3JHcuXO75SWlSJFCPvvsM9PE5ipnzpzm/5MnT5recBZ9XKpUqVhf57nnnpN8+fLJxIkTzWtpsKQ1Srdu3fLLccF/aDQFAIQMDZI0OXvEiBEmd8i6bd682QQ0P/zwQ4znPPzwwyZgWrJkiXPZpUuXTI1RxYoVPb7O2bNnTQ2WJoXXqFFDihQpIufPn/frscF/Ev+fAAAA+MjcuXNN0NKmTZsYNUiaY6S1Tm+99VaMGjNNAB84cKAULFjQBE99+vQxwdULL7zg8XUyZcpkesBNmDDB1EZp01vPnj05jwGKmiUAQMjQYEh7tUUPlKxgaf369bJlyxZ56KGHpH///s51PXr0MGMytWvXTsqWLWt6t+nQAClTpnRuo/lOrVq1Mve1t9v06dNlw4YNpunt3XfflWHDht2no4SvhTniM0AF3Gh1rH7xtDdF+vTpeXeAIKMDD2r3ca1RcL04Ijhdu3bN1Ar98ccfXiV8a37SgAEDnAETEv/3N67X74CrWRo3bpyJ+PWAdYwL7dFgZ+bMmWYsDd2+ePHiMm/evFi31apXrW7VwccAAKFp2bJl8vTTT3sVKG3fvt1cdFu0aOHXsiFhBFSwpKOt6gBf/fr1k40bN0rJkiXNcPOnTp3yuP3KlSuladOmpm1606ZNpm1Zb9u2bYuxrXYhXb16tVvvCABA6NERvX///XevnvPYY4+Z5jsGmwxOARUsjRw5Utq2bSutW7eWokWLmjl6UqdObQb68uTTTz81g411797d9ET43//+J48//rjpGurq6NGjpi166tSpkixZsvt0NAAAIBAETLCk41JoopzrcPMawetjHW7eE13uur3SmijX7XXci9dee80EVPqXQVzcvHnTtHO63gAAQHAKmGBJRz+NjIz0arh5XX637YcMGWJGb+3cuXOcyzJ48GDTNm3d8uTJ4/XxAACAwBAwwZI/aE2VNtXpbNOeJk+MjU62qJnz1u3w4cN+LScAAEg4ARMsZc2aVZIkSWKGl3elj62h6KPT5Xbb//333yY5PG/evKZ2SW8HDx6Ubt26mR53sdEh8bWLoesNAAAEp4AJlpInTy5PPPGE23Dzmm+kj2Mbbl6Xu26vFi1a5Nxec5W094LrkPfaG07zlxYsWODnIwIAAIEgoKY70WEDWrZsKWXKlJFy5cqZ8ZCuXr1qescpHd/igQceMDlFqkuXLlKtWjUzB5B2BdXRVHV0Vh1+XumgY3pzpb3htOapUKFCCXCEAAAgsQmoYKlx48Zy+vRp6du3r0nS1tmedbh5K4lb595xHeOiUqVKMm3aNDOR4QcffGDm9Jk9e7YZeh4AACAumO7EB5juBAhuTHcSfPQP7kGDBpnBJ3WsvezZs5s/wHXC3Bo1aphtdDDjTz75RJYvXy7nzp0zrQ46E8Sbb74p9evXj7VjkM4ipoMnT5w4US5cuCCVK1eW8ePHmz/Ycf+F5HQnAADciwMHDpgc2KVLl5rJbbdu3WpaKZ566inp0KGD2ebXX3+VChUqmAlzp0yZIjt37jTbNGzY0LRW6MU1NkOHDpUxY8aYgZPXrFkjadKkMWP86UUbgYmaJR+gZgkIbtQsxZHOy377mtx3yVKLeDH8S7169UznnoiICBPIuNKaIM1d1Ulxq1atKrNmzYq19shTzZIu145C2qv6vffeM8s0sNJ0ER2mpkmTJh73p/PQaa2V9vrW4Ew7NQ0cOFBeffVV6dixo/z0009mH2PHjpW6devG+VghPqlZCqicJQBAIqaB0scJML/mB8dEkrsHPbHR5jStIdImuOiBksqYMaOZK/Ts2bPSo0ePWPcTWxOcXpS1ic919gi9GOvE7zp7RGzBktIgSV9TJ4jXuVDbt29vyqK1WZp3O2rUKNOLW/Nzdaov3D80wwEAQsZ///1nan8KFy4c6za7d+82/7v2il63bp2kTZvWeZs7d67H51ozRHgz24RFJ4fXJj7NbdLBj7UWRMcY1DlRdZl2btIgTmvFcH9RswQA8F1zmNbyJMTrxpEGSvFRokQJMxaf0sDlzp078drP3V7Dos1xOrSNNs1ZrABMB1PG/UWwBADwDW2aimNzWELRQEeb0Hbt2mW7jdKcJk3ytmZuKFCgwF33b80QobNF5MqVy7lcH2tvOzuaK+VKy+m6zGr60wGZcX/RDAcACBmZM2c2PdPGjRtnBjWOThO8a9WqZbbTida9pUnEGjC5zh6hScTaKy622SaQ+BEsAQBCigZKkZGRZiaIn3/+Wfbs2WOGBtDu/hrQaE7SV199ZcZg0tkfdPqrffv2mVwhHRbAaibzRGt/dKwm7ck2Z84cMyyBzi6hPeReeOGF+3yk8BWCJQBASMmfP79s3LjRjKukXfx1VodnnnnG1Abp4JFKe6CtXLnS9DrTYEeTvZ9++mkzNpNOnaWDUlp04vX+/fs7H2uPtk6dOkm7du2kbNmyZqwm7YHn2m1dhwpo1arVfT5yxBfjLPkA4ywBwY1xlhCba9eumUTsP/74wwRAcaXjOA0YMICA6T5gBG8AABLQsmXLTI2TN4HS9u3bzdhLWmOFwEBvOAAA4klzmvTmjccee4yxkgIMOUsAAAA2CJYAwM8DGgII7O8twRIA3IU1MKAm8wIILNb3Nvqgn94gZwkA7kLH1NEJVq1pJrQ7eWwTqQJIPDVKGijp91a/v7GNjRUXBEsAEAfWNBbMywUEFg2UrO9vfBEsAUAcaE2SzvWVPXt2uX37Nu8ZEAC06e1eapQsBEsA4AX94fXFjy+AwEGCNwAAgA2CJQAAABsESwAAADYIlgAAAGwQLAEAANggWAIAALBBsAQAAGCDYAkAAMAGwRIAAIANgiUAAAAbBEsAAAA2CJYAAABsECwBAADYIFgCAACwQbAEAABgg2AJAADABsESAACADYIlAAAAGwRLAAAANgiWAAAAbBAsAQAA2CBYAgAAsEGwBAAAYINgCQAAwAbBEgAAgA2CJQAAABsESwAAADYIlgAAAGwQLAEAAPgyWJoyZYr8/vvvzsc9evSQjBkzSqVKleTgwYPe7g4AACC4gqWPP/5YUqVKZe6vWrVKxo0bJ0OHDpWsWbPKu+++648yAgAAJJik3j7h8OHDUqBAAXN/9uzZ0qhRI2nXrp1UrlxZqlev7o8yAgAABE7NUtq0aeXs2bPm/sKFC+WZZ54x91OmTCnXr1/3fQkBAAACqWZJg6M33nhDSpcuLbt375Z69eqZ5du3b5eHHnrIH2UEAAAInJolzVGqWLGinD59Wn7++WfJkiWLWb5hwwZp2rSpP8oIAACQYMIcDocj4V4+OFy6dEkyZMggFy9elPTp0yd0cQAAgA+v3/EaZ+nvv/+W5s2bm+ECjh49apZ99913smLFivjsDgAAINHyOljSprfatWub4QM2btwoN2/eNMs1KtNhBfxNmwE1N0oTysuXLy9r16613X7mzJlSuHBhs33x4sVl3rx5znW3b9+W999/3yxPkyaN5M6dW1q0aCHHjh3z+3EAAIAgDZYGDhwoX3zxhUycOFGSJUvmXK5DB2jw5E8zZsyQrl27Sr9+/cxrlSxZ0gRup06d8rj9ypUrTR5VmzZtZNOmTfLCCy+Y27Zt28z6a9eumf306dPH/D9r1iyJiIiQ559/3q/HAQAAgjhnKXXq1LJjxw5Tu5MuXTrZvHmz5M+fX/bt2ydFixaVGzdu+K2wWpNUtmxZ+eyzz8zjqKgoyZMnj3Tq1El69uwZY/vGjRvL1atXZe7cuc5lFSpUkFKlSpmAz5N169ZJuXLlzGjkefPmjVO5yFkCACDw+C1nKWfOnPLff//FWK75Sho0+cutW7dMj7uaNWs6l4WHh5vHOpK4J7rcdXulNVGxba/0DQsLCzNTuMRGmx71DXa9AQCA4OR1sNS2bVvp0qWLrFmzxgQVmt8zdepUee+996R9+/b+KaWInDlzRiIjIyVHjhxuy/XxiRMnPD5Hl3uzvdaKaQ6TNt3ZRZiDBw82kah109otAAAQnLwelFKbu7T5q0aNGibnp2rVqpIiRQoTLGlzWKDSZO9XXnlFtFVy/Pjxttv26tXL5E5ZtGaJgAkAgODkdbCktUm9e/eW7t27m+a4K1eumFwlnQbFn3Si3iRJksjJkyfdlutjbRr0RJfHZXsrUNI8paVLl951rCQNDvUGAACCX7zGWVLJkyc3QZJ2y1+8eLHs3LnTtyXz8HpPPPGELFmyxLlMa7j0sY4o7okud91eLVq0yG17K1Das2ePOQ5rRHIAAIB4BUsaWFi90XTiXO2dpstKlChhxmDyJ2360iELpkyZYoIzzZHS3m6tW7c263WMJG0is2hu1fz582XEiBGya9cu6d+/v6xfv146duzoDJReeukls0zzrjQnSvOZ9KYJ5QAAAF4HS8uXL5cnn3zS3P/ll19M7c6FCxdkzJgxZgwmf9KhAIYPHy59+/Y13f///fdfEwxZSdyHDh2S48ePO7fXEcanTZsmEyZMMGMy/fTTTzJ79mwpVqyYWa+jj8+ZM0eOHDli9pcrVy7nTcdoAgAA8HqcJR25e/fu3SahWWtydNTrTz75xAQq2iynOUyhhnGWAAAIPH4bZ0mDJB2nSJu/tFanVq1aZvn58+fNlCIAAAAh3RvunXfekWbNmpneb/ny5ZPq1as7m+d0jjUAAICQDpbefvttM+2INrs988wzZhRtpaN3+ztnCQAAINHnLCEmcpYAAAje67fXNUtKe49pLzKtXYrexX7kyJHx2SUAAECi5HWwpIM8Pv/886bZTccu0m74Bw4cMNOEPP744/4pJQAAQALxujecDvqo88Bt3brV9H7TgSgPHz4s1apVk5dfftk/pQQAAAiUYElHztbxlVTSpEnNKN7aM+6jjz6SIUOG+KOMAAAAgRMspUmTxpmnpCNd792717nuzJkzvi0dAABAoOUsVahQQVasWCFFihSRevXqSbdu3UyT3KxZs8w6AACAkA6WtLebNaXJgAEDzP0ZM2ZIwYIF6QkHAACCDuMs+QDjLAEAEHj8Os6SWr9+vUn2VjqB7hNPPBHfXQEAACRaSeMzIGXTpk3ln3/+kYwZM5plFy5ckEqVKsn06dPlwQcf9Ec5AQAAAqM33BtvvCG3b982tUrnzp0zN70fFRVl1gEAAIR0zlKqVKlk5cqVUrp0abflGzZskCeffFKuXbsmoYacJQAAgvf67XXNUp48eUzNUnSRkZGSO3du70sKAACQiHkdLA0bNkw6depkErwter9Lly4yfPhwX5cPAAAg8TfDZcqUScLCwpyPr169Knfu3DHTnSjrvo7urTlMoYZmOAAAQnzogNGjR/uybAAAAAEjTsFSy5Yt/V8SAACAYMhZAgAACCUESwAAADYIlgAAAGwQLAEAANggWAIAALjX3nAvvviixNWsWbPivC0AAEBQ1CzpgE3WTQdtWrJkidsI3jovnC7T9QAAACFXszRp0iTn/ffff19eeeUV+eKLLyRJkiTOeeHefvtt29EvAQAAgna6E1fZsmWTFStWSKFChdyWR0RESKVKleTs2bMSapjuBACA4L1+e53grfPA7dq1K8ZyXRYVFeV9SQEAAAK9Gc5V69atpU2bNrJ3714pV66cWbZmzRr55JNPzDoAAICQDpaGDx8uOXPmlBEjRsjx48fNsly5ckn37t2lW7du/igjAABA4OQsRW/rU6Ge2E3OEgAAgcdvOUtW3tLixYvlhx9+kLCwMLPs2LFjcuXKlfiXGAAAIBia4Q4ePCh16tSRQ4cOyc2bN+WZZ56RdOnSyZAhQ8xjHVIAAAAgWHhds9SlSxcpU6aMnD9/XlKlSuVc3rBhQzMwJQAAQEjXLP3999+ycuVKSZ48udvyhx56SI4ePerLsgEAAARezZKOpaQjdkd35MgR0xwHAAAQ0sFSrVq1ZPTo0c7HmuCtid39+vWTevXq+bp8AAAAgTV0gNYg1a5dW/Rpe/bsMflL+n/WrFll+fLlkj17dgk1DB0AAEDwXr/jNc6SDh0wffp02bJli6lVevzxx6VZs2ZuCd+hhGAJAIDgvX57neBtnpQ0qTRv3vxeygcAABAQ4hQszZkzR+rWrSvJkiUz9+08//zzviobAABAgotTM1x4eLicOHHC5CPp/Vh3FhbmsadcsKMZDgCAEG+G0+ECPN0HAAAIdnEaOiBz5sxy5swZc//111+Xy5cv+7tcAAAAgRMs3bp1y1RVqSlTpsiNGzf8XS4AAIBEIU7NcBUrVpQXXnhBnnjiCTO+UufOnWMdJuCbb77xdRkBAAASd7D0/fffy6hRo2Tv3r0miVsToahdAgAAocDrQSkffvhhWb9+vWTJksV/pQow9IYDACDw+G1Qyv37999r2QAAAAJGnIKlMWPGSLt27SRlypTmvh3NZwIAAAipZjjXpje9H+vOwsJk3759EmpohgMAIMSb4Vyb3miGAwAAoSRO4ywBAACEqjjVLHXt2jXOOxw5cqT407hx42TYsGFmrrqSJUvK2LFjpVy5crFuP3PmTOnTp48cOHBAChYsKEOGDJF69eo512srZL9+/WTixIly4cIFqVy5sowfP95sCwAAEKdgadOmTW6PN27cKHfu3JFChQqZx7t375YkSZKYQSv9acaMGSZw++KLL6R8+fIyevRoqV27tkRERJhJfqNbuXKlNG3aVAYPHiz169eXadOmmcE1tfzFihUz2wwdOtQkrevI5JqPpYGV7nPHjh0moR0AAIQ2r8dZ0pqjP//80wQXmTJlMsvOnz8vrVu3lieffFK6devmr7KaAKls2bLy2WefOSf1zZMnj3Tq1El69uwZY/vGjRvL1atXZe7cuc5lFSpUkFKlSpmASw89d+7cpszvvfeeWa9JXjly5JDJkydLkyZNEjTB+/jBCJ/tCwCAQJYzT0EJCw8PjHGWRowYIQsXLnQGSkrvDxw4UGrVquW3YEnnp9uwYYP06tXLuSw8PFxq1qwpq1at8vgcXR69CVFrjWbPnu1MVtfmPN2HRd80Dcr0ubEFSzdv3jQ3izVvnq9l/qaypAi77Zd9AwAQSG71OinJUyRMi4/XwZIGBqdPn46xXJddvnxZ/OXMmTMSGRlpan1c6eNdu3Z5fI4GQp621+XWemtZbNt4os16AwYMEH+7GZZMvKv3AwAgOIUn4Gt7HSw1bNjQNLlpDZOVWL1mzRrp3r27vPjiixIKtHbLtcZKA0htDvS19P2P+3yfAADAz8GS5vpofs+rr74qt2//vyaipEmTSps2bUwvNX/JmjWrSSI/efKk23J9nDNnTo/P0eV221v/67JcuXK5baN5TbFJkSKFuQEAgODnda1W6tSp5fPPP5ezZ8+aXnJ6O3funFmWJk0a/5RSRJInT2562y1ZssS5TBO89XHFihU9PkeXu26vFi1a5Nxee79pwOS6jdYSaU1ZbPsEAAChxeuaJYsGRpkzZ3bevx+06atly5ZSpkwZ0wSoQwdobzdtFlQtWrSQBx54wOQUqS5duki1atVMk+Gzzz4r06dPN9O2TJgwwTk9yzvvvGOS03VcJWvoAO0hp0MMAAAAeB0saW2OBhcagFy5csUsS5cunekF17t3b9NDzV90KABNJO/bt69JwNamsvnz5zsTtA8dOuT2+pUqVTJjK3344YfywQcfmIBIe8JZYyypHj16mIBLJwrWQSmrVKli9skYSwAAIF7jLGly89dff216g+lo12rFihXSv39/adu2rQwaNCjk3lkm0gUAIHiv314HS9pEpUnezz//vNvyX3/9Vd5++205evSohBqCJQAAgvf67XWbmSZzFy5cOMZyXabrAAAAgonXwZJOXmtNN+JKl+k6AACAkE7w1olntWfZ4sWLnd3rdWqQw4cPy7x58/xRRgAAgMCpWdKu+Lt37zYjeWvvMb3pyN0RERFmIl0AAIBg4nWCN2IiwRsAgOC9fsdrUMobN27Ili1b5NSpU2bcJVfRe8kBAAAEMq+DJR2wUUfKPnPmTIx1OiJ2ZGSkr8oGAAAQeDlLnTp1kpdfflmOHz9uapVcbwRKAABAQj1YOnnypJmjzZpiBAAAIJh5HSy99NJL8ueff/qnNAAAAIHeG+7atWumGS5btmxSvHhxSZYsmdv6zp07S6ihNxwAAIHHb73hfvjhB1m4cKGkTJnS1DBpUrdF74disAQAAIKX18FS7969ZcCAAdKzZ08JD/e6FQ8AACCgeB3t3Lp1Sxo3bkygBAAAQoLXwVLLli1lxowZ/ikNAABAoDfD6VhKOpnuggULpESJEjESvEeOHOnL8gEAAARWsLR161YpXbq0ub9t2za3da7J3gAAACEZLC1btsw/JQEAAEiE6M4GAABgg2AJAADABsESAACADYIlAAAAGwRLAAAAvuwNp/bs2WN6xZ06dUqioqLc1vXt2zc+uwQAAAiOYGnixInSvn17yZo1q+TMmTPGRLoESwAAIKSDpYEDB8qgQYPk/fff90+JAAAAAjln6fz58/Lyyy/7pzQAAACBHixpoLRw4UL/lAYAACDQm+EKFCggffr0kdWrV0vx4sVjTKTbuXNnX5YPAAAgQYU5HA6HN094+OGHY99ZWJjs27dPQs2lS5ckQ4YMcvHiRUmfPn1CFwcAAPjw+u11zdL+/fu9fQoAAEBoDkqplVJeVkwBAAAEf7D07bffmnylVKlSmVuJEiXku+++833pAAAAEpjXzXAjR440Cd4dO3aUypUrm2UrVqyQt956S86cOSPvvvuuP8oJAAAQOAneAwYMkBYtWrgtnzJlivTv3z8kc5pI8AYAIHiv3143wx0/flwqVaoUY7ku03UAAADBJDw+4yz9+OOPMZbPmDFDChYs6KtyAQAABGbOkjbBNW7cWJYvX+7MWfrnn39kyZIlHoMoAACAkKpZatSokaxZs0ayZs0qs2fPNje9v3btWmnYsKF/SgkAABAoCd6IiQRvAABCfARv3Zm1E71vh+k+AABAMIlTsJQpUybT0y179uySMWNGMwdcdFpBpcsjIyP9UU4AAIDEGywtXbpUMmfObO4vW7bM32UCAAAIrGCpWrVqboNS5smTJ0btktYsHT582PclBAAACKTecBosnT59Osbyc+fOmXUAAAAhHSxZuUnRXblyRVKmTOmrcgEAAATWoJRdu3Y1/2ugpBPppk6d2rlOk7p17KVSpUr5p5QAAACJPVjatGmTs2Zp69atkjx5cuc6vV+yZEl57733/FNKAACAxB4sWb3gWrduLZ9++injKQEAgJDgdc7S6NGj5c6dOx4TvO82YCUAAEDQB0tNmjSR6dOnx1iuk+jqOgAAgJAOljSR+6mnnoqxvHr16mYdAABASAdLN2/e9NgMd/v2bbl+/bqvygUAABCYwVK5cuVkwoQJMZZ/8cUX8sQTT/iqXAAAAIHVG84ycOBAqVmzpmzevFlq1Khhli1ZskTWrVsnCxcu9EcZAQAAAqdmqXLlyrJq1SozP5wmdf/2229SoEAB2bJlizz55JP+KSUAAECgBEtKR+qeOnWqbN++XdavXy/ffPONFCxYUPxJhyZo1qyZGd8pY8aM0qZNGzPFip0bN25Ihw4dJEuWLJI2bVpp1KiRnDx50rlea8eaNm1qAr9UqVJJkSJFzBhSAAAA8W6Gix6M3Lp1y22ZBjP+oIHS8ePHZdGiRSaZXAfHbNeunUybNi3W57z77rvy+++/y8yZMyVDhgzSsWNHefHFF+Wff/4x6zds2CDZs2eX77//3gRMK1euNPtMkiSJ2RYAACDMofOXeOHatWvSo0cP0wR39uzZGOt1njhf27lzpxQtWtTkRZUpU8Ysmz9/vtSrV0+OHDkiuXPnjvGcixcvSrZs2Uww9dJLL5llu3btMrVH2oxYoUIFj6+lNVH6ekuXLrXtEag3iw7GqcGWvqa/gkUAAOBbev3WypS7Xb+9bobr3r27CSTGjx8vKVKkkK+++koGDBhgApZvv/1W/EGDG216swIlpUnm4eHhsY7tpLVGWgOl21kKFy4sefPmNfuLjb5hmTNnti3P4MGDzZtr3TRQAgAAwcnrYEkTuj///HOT/5M0aVKT1P3hhx/Kxx9/bPKY/OHEiROmucyVvrYGNboutufoBL8aZLnKkSNHrM/RZrgZM2aYpjg7vXr1MkGVdTt8+LDXxwQAAII0WNJE6/z585v7WmWlj1WVKlVk+fLlXu2rZ8+eEhYWZnvTprP7Ydu2bdKgQQPp16+f1KpVy3ZbrVHTY3e9AQCA4OR1grcGSvv37zfNWdqspblLOlCl1jhFr8W5m27dukmrVq3u+no5c+aUU6dOuS3XUcQ1UNN1nuhyTT6/cOGCW7m0N1z05+zYscOMGaU1SlpLBgAAEO9gSXuhaZf7atWqmZqh5557Tj777DOTHzRy5Eiv9qUJ2Hq7m4oVK5qgR/OQrFHCNW8qKipKypcv7/E5ul2yZMnMgJnaZKgiIiLk0KFDZn8WHf7g6aeflpYtW8qgQYO8Kj8AAAh+XveGi+7gwYMmiNGBKUuUKCH+UrduXVMrpNOqWEMHaMK3NXTA0aNHTe2QJplrTZdq3769zJs3TyZPnmyayjp16uTMTbKa3jRQql27tgwbNsz5Wjp0QFyCOG+z6QEAQJD3htMgRQOSPXv2OJfly5fPjF3kz0BJafK4Nvvp6+uQAZoj5TpHnZZNa450aAPLqFGjpH79+qZmqWrVqqb5bdasWc71P/30k5w+fdqMs5QrVy7nrWzZsn49FgAAEMQ1S1rjojUz/h6xO5BQswQAQODx2zhLzZs3l6+//vpeywcAABCcCd7aC03nglu8eLFJok6TJo3bem+TvAEAAIIqWNKk6Mcff9zc3717t9s6HRcJAAAg5IKlLVu2SLFixcz0IsuWLfN/qQAAABKJOOUslS5dWs6cOeMcJNLTBLoAAAAhGyzpCNg6arc6cOCAGQwSAAAgFMSpGU7HKdIRu3UMIs1L0sEgdeBGT/bt2+frMgIAACTuYEkHf9SBJ//77z/p3LmztG3bVtKlS+f/0gEAAARKb7g6deqY/3Vqky5duhAsAQCAkOD10AGTJk3yT0kAAAASIa9H8AYAAAglBEsAAAA2CJYAAABsECwBAADYIFgCAACwQbAEAABgg2AJAADABsESAACADYIlAAAAGwRLAAAANgiWAAAAbBAsAQAA2CBYAgAAsEGwBAAAYINgCQAAwAbBEgAAgA2CJQAAABsESwAAADYIlgAAAGwQLAEAANggWAIAALBBsAQAAGCDYAkAAMAGwRIAAIANgiUAAAAbBEsAAAA2CJYAAABsECwBAADYIFgCAACwQbAEAABgg2AJAADABsESAACADYIlAAAAGwRLAAAANgiWAAAAbBAsAQAA2CBYAgAAsEGwBAAAYINgCQAAwAbBEgAAgA2CJQAAABsESwAAADYIlgAAAGwQLAEAANggWAIAALBBsAQAABAMwdK5c+ekWbNmkj59esmYMaO0adNGrly5YvucGzduSIcOHSRLliySNm1aadSokZw8edLjtmfPnpUHH3xQwsLC5MKFC346CgAAEGgCJljSQGn79u2yaNEimTt3rixfvlzatWtn+5x3331XfvvtN5k5c6b89ddfcuzYMXnxxRc9bqvBV4kSJfxUegAAEKjCHA6HQxK5nTt3StGiRWXdunVSpkwZs2z+/PlSr149OXLkiOTOnTvGcy5evCjZsmWTadOmyUsvvWSW7dq1S4oUKSKrVq2SChUqOLcdP368zJgxQ/r27Ss1atSQ8+fPm9qr2Ny8edPcLJcuXZI8efKY19SaLwAAkPjp9TtDhgx3vX4HRM2SBjcavFiBkqpZs6aEh4fLmjVrPD5nw4YNcvv2bbOdpXDhwpI3b16zP8uOHTvko48+km+//dbsLy4GDx5s3lzrpoESAAAITgERLJ04cUKyZ8/utixp0qSSOXNmsy625yRPnjxGDVGOHDmcz9HaoaZNm8qwYcNMEBVXvXr1MlGodTt8+HC8jgsAACR+CRos9ezZ0yRU29206cxfNOjRZrnmzZt79bwUKVKY6jrXGwAACE5JE/LFu3XrJq1atbLdJn/+/JIzZ045deqU2/I7d+6YHnK6zhNdfuvWLdOzzbV2SXvDWc9ZunSpbN26VX766Sfz2Erfypo1q/Tu3VsGDBhwz8cIAAACW4IGS5qArbe7qVixogl6NA/piSeecAY6UVFRUr58eY/P0e2SJUsmS5YsMUMGqIiICDl06JDZn/r555/l+vXrzudoAvnrr78uf//9tzzyyCM+OkoAABDIEjRYiittKqtTp460bdtWvvjiC5O43bFjR2nSpImzJ9zRo0dNTzZN1C5XrpxJvNbhALp27Wpym7SprFOnTiZQsnrCRQ+Izpw543w9u95wAAAgdAREsKSmTp1qAiQNiLTXmtYWjRkzxrleAyitObp27Zpz2ahRo5zbajJ37dq15fPPP0+gIwAAAIEoIMZZCpZxGgAAQOIRVOMsAQAAJBSCJQAAABsESwAAADYIlgAAAGwQLAEAANggWAIAALBBsAQAAGCDYAkAAMAGwRIAAIANgiUAAAAbBEsAAAA2CJYAAABsECwBAADYIFgCAACwQbAEAABgg2AJAADABsESAACADYIlAAAAGwRLAAAANgiWAAAAbBAsAQAA2CBYAgAAsEGwBAAAYINgCQAAwAbBEgAAgA2CJQAAABsESwAAADYIlgAAAGwQLAEAANggWAIAALBBsAQAAGCDYAkAAMAGwRIAAIANgiUAAAAbBEsAAAA2CJYAAABsECwBAADYIFgCAACwQbAEAABgg2AJAADABsESAACADYIlAAAAG0ntViJuHA6H+f/SpUu8ZQAABAjrum1dx2NDsOQDly9fNv/nyZPHF7sDAAD3+TqeIUOGWNeHOe4WTuGuoqKi5NixY5IuXToJCwvzacSrAdjhw4clffr0QXcmgv34QuEYg/34QuEYOb7AxzmMPw2BNFDKnTu3hIfHnplEzZIP6Bv84IMPir/oD3Qw/kiHyvGFwjEG+/GFwjFyfIGPcxg/djVKFhK8AQAAbBAsAQAA2CBYSsRSpEgh/fr1M/8Ho2A/vlA4xmA/vlA4Ro4v8HEO/Y8EbwAAABvULAEAANggWAIAALBBsAQAAGCDYAkAAMAGwVICGDRokFSqVElSp04tGTNm9LjNoUOH5NlnnzXbZM+eXbp37y537txx2+bPP/+Uxx9/3PSEKFCggEyePDnGfsaNGycPPfSQpEyZUsqXLy9r166V+03LqSObe7qtW7fObHPgwAGP61evXu22r5kzZ0rhwoXN8RQvXlzmzZsniYG+x9HL/sknn7hts2XLFnnyySdN2XVE6KFDh8bYT2I9Pj0/bdq0kYcfflhSpUoljzzyiOkhduvWLbdtAvkcepIYvj/xMXjwYClbtqyZVUB/P1544QWJiIhw26Z69eoxztVbb73l9e9QQujfv3+MsutnynLjxg3p0KGDZMmSRdKmTSuNGjWSkydPBsSx2f2m6E2PKxDP3/Lly+W5554zI2VrWWfPnh1jJO2+fftKrly5zG9MzZo1Zc+ePW7bnDt3Tpo1a2YG39Rrp/4mXblyxevf2XjR6U5wf/Xt29cxcuRIR9euXR0ZMmSIsf7OnTuOYsWKOWrWrOnYtGmTY968eY6sWbM6evXq5dxm3759jtSpU5t97NixwzF27FhHkiRJHPPnz3duM336dEfy5Mkd33zzjWP79u2Otm3bOjJmzOg4efKk4366efOm4/jx4263N954w/Hwww87oqKizDb79+/XaXccixcvdtvu1q1bzv38888/5hiHDh1qjvnDDz90JEuWzLF161ZHQsuXL5/jo48+civ7lStXnOsvXrzoyJEjh6NZs2aObdu2OX744QdHqlSpHF9++WVAHN8ff/zhaNWqlWPBggWOvXv3On799VdH9uzZHd26dXNuE+jnMLrE8v2Jj9q1azsmTZpkPmv//vuvo169eo68efO6fSarVatmjsn1XOnn1JvfoYTSr18/x2OPPeZW9tOnTzvXv/XWW448efI4lixZ4li/fr2jQoUKjkqVKgXEsVlOnTrldnyLFi0y369ly5YF5PmbN2+eo3fv3o5Zs2aZ4/jll1/c1n/yySfmejh79mzH5s2bHc8//7y5Rly/ft25TZ06dRwlS5Z0rF692vH33387ChQo4GjatKlXv7PxRbCUgPTHzFOwpB+q8PBwx4kTJ5zLxo8f70ifPr0JPFSPHj3Mj4Wrxo0bmx9JS7ly5RwdOnRwPo6MjHTkzp3bMXjwYEdC0otntmzZTHAR/UKrX+rYvPLKK45nn33WbVn58uUdb775piMxBEujRo2Kdf3nn3/uyJQpk/P8qffff99RqFChgDg+TzTg0R+zYDmH0SXW7098L7x6bv766y/nMr3YdunSJdbnxOV3KCGDJb1oenLhwgUTgM+cOdO5bOfOneb4V61aleiPLTZ6rh555BHnH5iBfP4kWrCkx5QzZ07HsGHD3M5jihQpTMCj9I8rfd66devc/ogLCwtzHD16NM6/s/FFM1witGrVKtM8kSNHDuey2rVrm8kSt2/f7txGqyld6Ta6XGnzyIYNG9y20Tns9LG1TUKZM2eOnD17Vlq3bh1j3fPPP2+qi6tUqWK2c3W3Y05o2uym1f6lS5eWYcOGuVV3axmrVq0qyZMndyu7No2cP38+II4vuosXL0rmzJmD6hxaEvP3J77nSkU/X1OnTpWsWbNKsWLFpFevXnLt2jWvfocSkjbRaJNO/vz5TdOMNjkpPW+3b992O3faRJc3b17nuUvsx+bp8/j999/L66+/7jZZeyCfP1f79++XEydOuJ0zna9Nm75dz5k2vZUpU8a5jW6v38s1a9bE+Xc2vphINxHSD43rB1xZj3Wd3Tb6Rbh+/br5YERGRnrcZteuXZKQvv76a/MBdp18WPMKRowYIZUrVzYf/p9//tnkWWi7tl587Y7Zek8SUufOnU3+mF6MVq5caX64jh8/LiNHjjTrtYya7xPbOc2UKVOiPr7o/vvvPxk7dqwMHz48aM6hqzNnziTa74+3oqKi5J133jHnRS+qlldffVXy5ctnAg7N83j//ffNRWXWrFlx/h1KKHoR1RzNQoUKme/ZgAEDTJ7Ktm3bTNn0Yhk9H9T1c5aYj80T/Q5duHBBWrVqFRTnLzqrPHa/Dfq//hHmKmnSpOY313Wbu/3OxhfBko/07NlThgwZYrvNzp073ZIQQ/GYjxw5IgsWLJAff/zRbTv966hr167Ox5qceuzYMVNDY11oE/PxuZa9RIkS5sf6zTffNIm2iXmajPicw6NHj0qdOnXk5ZdflrZt2ybqcwgxCcEaRKxYscLt7WjXrp3zvtZAaGJtjRo1ZO/evSaBPzGrW7eu2/dNgycNHPR3RZODg43+ganHrIFRMJy/QESw5CPdunVzi/o90eriuMiZM2eMXjdWTw5dZ/0fvXeHPtZeAvpjkSRJEnPztI21j4Q45kmTJpmmqrhcPPUHcNGiRc7HsR2zr47Hl+dUy67NcNpDTP/6ja3scTmn/jq++ByjBj9PPfWU6c05YcKERH8O40sDP39/f+6Hjh07yty5c01PJNea3NjOlVVrqBfbuPwOJRZai/Too4+asj/zzDOm2UprYlxrl1zPXSAd28GDB2Xx4sXOGqNgPH85/395tHwa9Fn0calSpZzbnDp1yu15+hurPeTu9hvq+hrxds9ZT/BbgrdrrxvN5tfEvBs3bjgTvLWngyvtFRA9wbtjx45uCaoPPPBAgiWoahKfJgS79qCyoz3mSpcu7ZYcXL9+fbdtKlasmCiTg7///ntzDs+dO+eWeOjaM0x7pURP8E7Mx3fkyBFHwYIFHU2aNDE9bYL9HCa274+33zVNTteE9N27d8fpOStWrDAJtNoTKa6/Q4nF5cuXzffr008/dSZ4//TTT871u3bt8pjgHQjHpsnsmvx8+/btoDl/EkuC9/Dhw916tnlK8NbejRbtnespwdvudzbeZb7nPcBrBw8eND2GBgwY4EibNq25rzf9wrt2+axVq5bp9qvDAWjvMU9DB3Tv3t309Bg3bpzHoQP0wzZ58mTzQWvXrp3p+uzaO+J+0i7l+mHX8kanZZw2bZpZp7dBgwaZL7p223btdp40aVLzhdJt9EckMXQ7X7lypekJp+dKu9VroKTnq0WLFs5t9Adcu7S+9tprpkurnhs9f9GHDkiMx2cFStpNt0aNGua+a3flYDiHniS274832rdvb/4Q+/PPP93O1bVr18z6//77z/RG1QuP9mLUoSDy58/vqFq1qnMfcfkdSij6B5cem5ZdP1PaPV67xWuvP2voAB0qYenSpeYYNSDXWyAcmysN0PU4tEeXq0A8f5cvX3Ze6/Q6oMPn6H29HlpDB+j3S49ly5YtjgYNGngcOkD/+FqzZo0JDvWPN9ehA+LyOxtfBEsJoGXLlubDEv1mjZ+hDhw44Khbt64ZI0J/BPTHIfpfFrp9qVKlzFgw+kXRmqrodPwl/bLpNvqXso5PkVD0Q+061okrvSAVKVLEfLD1Lx8tq2vXX8uPP/7oePTRR83x6NAJv//+uyOhbdiwwXR/14tTypQpzXF8/PHHMf5607/4qlSpYi7AWkOhPw6BcHxKP1uePrOuldOBfA5jk5i+P96I7VxZvxGHDh0yF9bMmTObz6MGwvqHl+s4PXH9HUoIOkxKrly5zHnR75I+1gDCohfYt99+29Qy6OexYcOGboF9Yj42V1pzouctIiLCbXkgnr9ly5Z5/Ezq9dCqXerTp48JdvSY9A+z6Md99uxZcx3RSgb9jWndurWzksGb39n4CNN/7q0hDwAAIHgxzhIAAIANgiUAAAAbBEsAAAA2CJYAAABsECwBAADYIFgCAACwQbAEAABgg2AJAADABsESAACADYIlAAAAGwRLAAAANgiWAISk6tWrS8eOHc0tQ4YMkjVrVunTp4/ODGzWnz9/Xlq0aCGZMmWS1KlTS926dWXPnj3O5x88eFCee+45sz5NmjTy2GOPybx58xLwiAD4C8ESgJA1ZcoUSZo0qaxdu1Y+/fRTGTlypHz11VdmXatWrWT9+vUyZ84cWbVqlQmi6tWrJ7dv3zbrO3ToIDdv3pTly5fL1q1bZciQIZI2bdoEPiIA/hDmsP6MAoAQq1k6deqUbN++XcLCwsyynj17muDo119/lUcffVT++ecfqVSpkll39uxZyZMnjwmwXn75ZSlRooQ0atRI+vXrl8BHAsDfqFkCELIqVKjgDJRUxYoVTVPbjh07TI1T+fLlneuyZMkihQoVkp07d5rHnTt3loEDB0rlypVNwLRly5YEOQYA/kewBADx8MYbb8i+ffvktddeM81wZcqUkbFjx/JeAkGIYAlAyFqzZo3b49WrV0vBggWlaNGicufOHbf12gwXERFh1lm0We6tt96SWbNmSbdu3WTixIn3tfwA7g+CJQAh69ChQ9K1a1cTBP3www+mZqhLly4mYGrQoIG0bdtWVqxYIZs3b5bmzZvLAw88YJard955RxYsWCD79++XjRs3yrJly6RIkSIJfUgA/CCpP3YKAIFAhwa4fv26lCtXTpIkSWICpXbt2pl1kyZNMo/r168vt27dkqpVq5qhAZIlS2bWR0ZGmh5xR44ckfTp00udOnVk1KhRCXxEAPyB3nAAQrY3XKlSpWT06NEJXRQAiRzNcAAAADYIlgAAAGzQDAcAAGCDmiUAAAAbBEsAAAA2CJYAAABsECwBAADYIFgCAACwQbAEAABgg2AJAADABsESAACAxO7/AOoHuAd7UXFjAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from dimelo import plot_enrichment_profile\n", + "plot_enrichment_profile.by_modification(\n", + " mod_file_name=pileup_file_coded,\n", + " regions=ctcf_target_regions,\n", + " window_size=1000,\n", + " motifs=['A,0,a','CG,0,m'], # We know from above that these mod codes aren't present in our file!\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " regions_5to3prime=False, # if set to True, regions on the reverse (-) strand will be flipped so everything is 5 prime to 3 prime\n", + " smooth_window=50,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `modkit` command below will make the bam file work for the to-spec mod codes m and a. However, you must use this command with caution: `modkit` can and will adjust any mod code to any other mod code; you need to verify yourself that they are actually equivalent and thus that the operation is in fact valid." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "modkit required: 0.6.1\n", + "modkit active : 0.6.1\n", + "$ modkit adjust-mods dimelo/test/output/ctcf_demo.updated.bam dimelo/test/output/ctcf_demo.recoded.bam --convert Y a --convert Z m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;32m>\u001b[0m Converting Y to a\n", + "\u001b[0;32m>\u001b[0m Converting Z to m\n", + "[modkit-core/src/modbam_util/subcommands.rs:774:9] &self.cpg = false\n", + "\u001b[0;32m>\u001b[0m done, 1024 records processed\n" + ] + } + ], + "source": [ + "run_modkit_checked([\n", + " \"adjust-mods\",\n", + " \"dimelo/test/output/ctcf_demo.updated.bam\",\n", + " \"dimelo/test/output/ctcf_demo.recoded.bam\",\n", + " \"--convert\",\n", + " \"Y\",\n", + " \"a\",\n", + " \"--convert\",\n", + " \"Z\",\n", + " \"m\",\n", + "])\n" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ctcf_bam_file_recoded = output_dir / 'ctcf_demo.recoded.bam'\n", + "pysam.index(str(ctcf_bam_file_recoded))" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No specified number of cores requested. 10 available on machine, allocating all.\n", + "Detected multiple motif contexts with modkit 0.6.x; running per-motif pileups and merging outputs to avoid mixed-motif empty output behavior.\n", + "Modification threshold of 128.0 assumed to be for range 0-255. 128.0/255=0.5019607843137255 will be sent to modkit.\n" + ] + } + ], + "source": [ + "pileup_file_recoded, pileup_regions = ensure_pileup_output(\n", + " input_file=ctcf_bam_file_recoded,\n", + " output_name='ctcf_demo_pileup_on_target_mod_code_specified_adjusted',\n", + " ref_genome=ref_genome_file,\n", + " output_directory=output_dir,\n", + " regions=[ctcf_target_regions,ctcf_off_target_regions],\n", + " motifs=['A,0,a','CG,0,m'],\n", + " thresh=128,\n", + " window_size=1000,\n", + " # cores=1,\n", + " # cleanup=False,\n", + " # log=True,\n", + " quiet=PARSE_QUIET,\n", + " modkit_executable=MODKIT_BINARY,\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "daf9919c66324ff29405d517de62001e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading data: 0%| | 0/50 [00:00" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from dimelo import plot_enrichment_profile\n", + "plot_enrichment_profile.by_modification(\n", + " mod_file_name=pileup_file_recoded,\n", + " regions=ctcf_target_regions,\n", + " window_size=1000,\n", + " motifs=['A,0,a','CG,0,m'],\n", + " single_strand=False, # if set to True, regions that specify the strand will only pull from reads on the strand (as opposed to both strands)\n", + " regions_5to3prime=False, # if set to True, regions on the reverse (-) strand will be flipped so everything is 5 prime to 3 prime\n", + " smooth_window=50,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Advanced Use Cases" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Clustering and Classification\n", + "\n", + "This section shows how to work with processed files (`pileup.sorted.bed.gz` and `extract.h5`) to discover structure in your data.\n", + "\n", + "The key idea is to turn each read or region into a feature vector that makes the biological pattern you care about more salient for the current window, motif set, and comparison. A feature is not a result by itself; it is a lens. Good lenses emphasize structure that is plausible for the question (for example center enrichment, flank depletion, periodicity, or motif-specific density) while minimizing nuisance variation such as read depth, missing motif coverage, or arbitrary row order.\n", + "\n", + "- **Read clustering**: build fixed-length windows per read, compute features (PCA, autocorrelation, density, summary stats), and cluster molecules. Supports single motifs or combined multi-motif windows.\n", + "- **Read classification**: train a binary classifier on read features to quantify separability between two samples; includes train/test metrics, confusion matrices, and per-read profile plots.\n", + "- **Region clustering**: cluster loci on their pileup profiles (single motif or concatenated multi-motif features), and visualize mean profiles plus genome distribution.\n", + "- **Region classification**: treat each locus feature vector as a sample for downstream classifiers.\n", + "\n", + "Optional clustering deps: `scikit-learn`, `scipy`, `hdbscan`, `umap-learn`, `pyranges`, `xgboost` (install via `pip install dimelo[clustering]`).\n", + "Make sure earlier parsing steps have produced the processed files before running these examples." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Read analysis and feature-engineering tips\n", + "\n", + "- Use `span_full_window=True` and `orientation_aware=True` when extracting to keep windows comparable across reads.\n", + "- For multi-motif windows, set `require_all_motifs=True` to drop reads missing motifs instead of zero-filling.\n", + "- `read_window_feature_matrix` currently computes features from the row matrix it receives. For multi-motif windows, that row is a concatenation of motif windows, so PCA, autocorrelation, densities, and summary statistics are computed over the combined representation unless you explicitly build/slice motif-specific matrices.\n", + "- Treat feature choices as part of the analysis design. Decide whether the question is about whole-read signal, a centered window, motif-specific signal, cross-motif relationships, or long-range structure before interpreting clusters.\n", + "- Drop low-information reads by setting `require_nonzero_valid=True` and `min_valid_fraction` in `read_window_feature_matrix`.\n", + "- Inspect feature behavior before trusting a clustering/classifier: check matrix shape, feature names, missing/zero-heavy rows, feature variance, and whether known labels or regions dominate the result.\n", + "- Validate rigorously with held-out labels, shuffled labels, sensitivity to window width/motif choice, and visual inspection of representative reads. A clean cluster is only useful if it is stable and biologically interpretable under reasonable parameter changes.\n", + "- Inspect cluster/classifier QC: profile plots for clusters, confusion matrices and per-read profile plots for classifiers.\n", + "- Check locus homogeneity with `summarize_read_clusters_by_region` (dominant_fraction/entropy) to see if clusters are locus-driven.\n", + "- Beware class imbalance in classifiers; look at both accuracy and ROC-AUC, and at confusion matrices to spot overfitting." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Read Clustering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Single motif (merged on-target vs off-target reads)\n", + "\n", + "This example uses the notebook test data directly: extract read windows separately from `ctcf_target_regions`\n", + "and `ctcf_off_target_regions`, merge them, and run unsupervised clustering to see whether clusters enrich for one group.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Motif semantics*: extract output stores one row per motif per read. For multi-motif analyses, use\n", + "`build_multimotif_read_windows` and either:\n", + "- `motif_profile_mode=\"single_axis\"` to overlay motif profiles on a shared y-axis, or\n", + "- `motif_profile_mode=\"separate_axes\"` to keep each motif on its own y-scale.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Sampling for speed**: Use `cluster.sample_rows` to downsample reads or loci before clustering/classification. Example:\n", + "\n", + "```python\n", + "sampled_feat, sampled_labels, idx = cluster.sample_rows(feature_matrix, labels, frac=0.2, stratify=True)\n", + "```\n", + "\n", + "Run heavy algorithms (e.g., spectral/DBSCAN/UMAP) on the sample, then apply insights back to the full set." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reads used: on_target=2173, off_target=4030, total=6203\n", + "Feature matrix: 6203 reads x 31 features\n", + "K-means input: feature matrix scaled inside read_window_feature_matrix(scale_features=True, family_weighting='equal_family')\n", + "Cluster purity vs on/off labels: 0.803\n", + "Feature families\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "family", + "rawType": "str", + "type": "string" + }, + { + "name": "n_features", + "rawType": "int64", + "type": "integer" + } + ], + "ref": "8110799a-da23-41df-b7d4-2149835f5a81", + "rows": [ + [ + "1", + "density", + "13" + ], + [ + "3", + "summary", + "7" + ], + [ + "2", + "pca", + "6" + ], + [ + "0", + "autocorr", + "5" + ] + ], + "shape": { + "columns": 2, + "rows": 4 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
familyn_features
1density13
3summary7
2pca6
0autocorr5
\n", + "
" + ], + "text/plain": [ + " family n_features\n", + "1 density 13\n", + "3 summary 7\n", + "2 pca 6\n", + "0 autocorr 5" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Most variable default features\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "feature_name", + "rawType": "str", + "type": "string" + }, + { + "name": "variance", + "rawType": "float64", + "type": "float" + }, + { + "name": "missing_fraction", + "rawType": "float64", + "type": "float" + } + ], + "ref": "7dc4e40d-39c4-4406-ae7a-0a7b3690b935", + "rows": [ + [ + "0", + "density_minus240", + "1.0000000000002067", + "0.0" + ], + [ + "1", + "density_minus150", + "1.0000000000001972", + "0.0" + ], + [ + "2", + "density_pm300", + "1.000000000000195", + "0.0" + ], + [ + "3", + "density_minus320", + "1.000000000000117", + "0.0" + ], + [ + "4", + "density_pm500", + "1.0000000000001081", + "0.0" + ], + [ + "5", + "pca_2", + "1.0000000000000788", + "0.0" + ], + [ + "6", + "global_mod_fraction", + "1.0000000000000608", + "0.0" + ], + [ + "7", + "density_minus800", + "1.0000000000000524", + "0.0" + ] + ], + "shape": { + "columns": 3, + "rows": 8 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
feature_namevariancemissing_fraction
0density_minus2401.00.0
1density_minus1501.00.0
2density_pm3001.00.0
3density_minus3201.00.0
4density_pm5001.00.0
5pca_21.00.0
6global_mod_fraction1.00.0
7density_minus8001.00.0
\n", + "
" + ], + "text/plain": [ + " feature_name variance missing_fraction\n", + "0 density_minus240 1.0 0.0\n", + "1 density_minus150 1.0 0.0\n", + "2 density_pm300 1.0 0.0\n", + "3 density_minus320 1.0 0.0\n", + "4 density_pm500 1.0 0.0\n", + "5 pca_2 1.0 0.0\n", + "6 global_mod_fraction 1.0 0.0\n", + "7 density_minus800 1.0 0.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Feature scaling preview\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "feature_name", + "rawType": "str", + "type": "string" + }, + { + "name": "scaling_method", + "rawType": "str", + "type": "string" + }, + { + "name": "family", + "rawType": "str", + "type": "string" + }, + { + "name": "family_weight", + "rawType": "float64", + "type": "float" + }, + { + "name": "scaled", + "rawType": "bool", + "type": "boolean" + }, + { + "name": "center", + "rawType": "float64", + "type": "float" + }, + { + "name": "scale", + "rawType": "float64", + "type": "float" + }, + { + "name": "raw_mean", + "rawType": "float64", + "type": "float" + }, + { + "name": "raw_std", + "rawType": "float64", + "type": "float" + }, + { + "name": "scaled_mean", + "rawType": "float64", + "type": "float" + }, + { + "name": "scaled_std", + "rawType": "float64", + "type": "float" + } + ], + "ref": "b0342416-7c5c-4ff5-8b0c-26038eba4b89", + "rows": [ + [ + "0", + "pca_0", + "standard", + "pca", + "1.0", + "True", + "2.7491577717624383e-17", + "0.25804970135868555", + "-1.806711563812547e-15", + "0.2580497013586851", + "1.492091957002131e-14", + "1.0000000000000024" + ], + [ + "1", + "pca_1", + "standard", + "pca", + "1.0", + "True", + "1.1368912868642584e-16", + "0.18175693772912851", + "1.674037518490596e-16", + "0.1817569377291284", + "3.191510859842573e-16", + "0.9999999999999521" + ], + [ + "2", + "pca_2", + "standard", + "pca", + "1.0", + "True", + "7.588820932469231e-17", + "0.18011870418727277", + "6.48992968202537e-17", + "0.18011870418728018", + "-1.3273724820854936e-16", + "1.0000000000000393" + ], + [ + "3", + "pca_3", + "standard", + "pca", + "1.0", + "True", + "-4.975689196288788e-17", + "0.1778403661994923", + "-6.90645348423718e-17", + "0.17784036619948876", + "8.182592475947218e-17", + "0.9999999999999918" + ], + [ + "4", + "pca_4", + "standard", + "pca", + "1.0", + "True", + "1.4461715361875326e-16", + "0.17127270439983974", + "7.534678990675082e-17", + "0.17127270439984799", + "-6.260419306035583e-16", + "0.9999999999999944" + ], + [ + "5", + "pca_5", + "standard", + "pca", + "1.0", + "True", + "4.3886294637770176e-17", + "0.1706551436413648", + "5.1269286700608755e-17", + "0.17065514364135828", + "-2.866927681516324e-16", + "0.9999999999999446" + ], + [ + "6", + "autocorr_20", + "standard", + "autocorr", + "1.0", + "True", + "0.006838371543611701", + "0.03218155896626967", + "0.00683837154361214", + "0.03218155896626913", + "6.913702240080694e-16", + "1.000000000000013" + ], + [ + "7", + "autocorr_75", + "standard", + "autocorr", + "1.0", + "True", + "0.0010282527368907757", + "0.01892136162649048", + "0.0010282527368908104", + "0.01892136162648973", + "-1.265489584976974e-15", + "1.0000000000000135" + ] + ], + "shape": { + "columns": 11, + "rows": 8 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
feature_namescaling_methodfamilyfamily_weightscaledcenterscaleraw_meanraw_stdscaled_meanscaled_std
0pca_0standardpca1.0True2.749158e-170.258050-1.806712e-150.2580501.492092e-141.0
1pca_1standardpca1.0True1.136891e-160.1817571.674038e-160.1817573.191511e-161.0
2pca_2standardpca1.0True7.588821e-170.1801196.489930e-170.180119-1.327372e-161.0
3pca_3standardpca1.0True-4.975689e-170.177840-6.906453e-170.1778408.182592e-171.0
4pca_4standardpca1.0True1.446172e-160.1712737.534679e-170.171273-6.260419e-161.0
5pca_5standardpca1.0True4.388629e-170.1706555.126929e-170.170655-2.866928e-161.0
6autocorr_20standardautocorr1.0True6.838372e-030.0321826.838372e-030.0321826.913702e-161.0
7autocorr_75standardautocorr1.0True1.028253e-030.0189211.028253e-030.018921-1.265490e-151.0
\n", + "
" + ], + "text/plain": [ + " feature_name scaling_method family family_weight scaled center \\\n", + "0 pca_0 standard pca 1.0 True 2.749158e-17 \n", + "1 pca_1 standard pca 1.0 True 1.136891e-16 \n", + "2 pca_2 standard pca 1.0 True 7.588821e-17 \n", + "3 pca_3 standard pca 1.0 True -4.975689e-17 \n", + "4 pca_4 standard pca 1.0 True 1.446172e-16 \n", + "5 pca_5 standard pca 1.0 True 4.388629e-17 \n", + "6 autocorr_20 standard autocorr 1.0 True 6.838372e-03 \n", + "7 autocorr_75 standard autocorr 1.0 True 1.028253e-03 \n", + "\n", + " scale raw_mean raw_std scaled_mean scaled_std \n", + "0 0.258050 -1.806712e-15 0.258050 1.492092e-14 1.0 \n", + "1 0.181757 1.674038e-16 0.181757 3.191511e-16 1.0 \n", + "2 0.180119 6.489930e-17 0.180119 -1.327372e-16 1.0 \n", + "3 0.177840 -6.906453e-17 0.177840 8.182592e-17 1.0 \n", + "4 0.171273 7.534679e-17 0.171273 -6.260419e-16 1.0 \n", + "5 0.170655 5.126929e-17 0.170655 -2.866928e-16 1.0 \n", + "6 0.032182 6.838372e-03 0.032182 6.913702e-16 1.0 \n", + "7 0.018921 1.028253e-03 0.018921 -1.265490e-15 1.0 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Counts by cluster and region set:\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "cluster", + "rawType": "int64", + "type": "integer" + }, + { + "name": "off_target", + "rawType": "int64", + "type": "integer" + }, + { + "name": "on_target", + "rawType": "int64", + "type": "integer" + } + ], + "ref": "d143eb08-c011-4f85-90bd-e66d308cefab", + "rows": [ + [ + "0", + "3758", + "952" + ], + [ + "1", + "272", + "1221" + ] + ], + "shape": { + "columns": 2, + "rows": 2 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
region_setoff_targeton_target
cluster
03758952
12721221
\n", + "
" + ], + "text/plain": [ + "region_set off_target on_target\n", + "cluster \n", + "0 3758 952\n", + "1 272 1221" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Within-cluster fractions:\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "cluster", + "rawType": "int64", + "type": "integer" + }, + { + "name": "off_target", + "rawType": "float64", + "type": "float" + }, + { + "name": "on_target", + "rawType": "float64", + "type": "float" + } + ], + "ref": "76449b52-614e-46d8-8a4e-3517e345b99c", + "rows": [ + [ + "0", + "0.798", + "0.202" + ], + [ + "1", + "0.182", + "0.818" + ] + ], + "shape": { + "columns": 2, + "rows": 2 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
region_setoff_targeton_target
cluster
00.7980.202
10.1820.818
\n", + "
" + ], + "text/plain": [ + "region_set off_target on_target\n", + "cluster \n", + "0 0.798 0.202\n", + "1 0.182 0.818" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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Yfw9CEjl6/nahd4D0ASgdi77xV/8RaUL5s9VV1112c9VrVq1atWqVatWrVq1atWqVatW7fx22aILpM393d/9ndLovuVbviV+9Ed/VMDSH/zBH+i1y7X73e9+8fGPf1yVKP7v//2/8cxnPjP+5m/+Ju5Me8lLXnKaaDsAVgWmqlWrVq1atWrVqlWrVq1atWrV7kRQ6r/9t/8WMzMz+je6Uvz7937v9yRWzmuXa7ChqIiHPepRj4p/+Id/iNe85jXxtKc9TYwstJ8G2VJU30PYHOPvD33oQ6ddL6vzDb7nzIp9/J9qfxMTE+e8J1hb/FSrVq1atWrVqlWrVq1atWrVqlW7i4BSVN0bTOVDWPy2Fs5G0wmAiip6VMt76lOfqtc++9nPxo033ijNKYy/f+EXfiEOHjwosXXs3e9+twAnUgDzPe985ztP+w7ek9eoVq1atWrVqlWrVq1atWrVqlWrdsfbLaqZDHuJVDv0pF74wheqYt5HP/pRaTXt2rXrstLkqOSHePmpU6dUae+9731v/MVf/IW0nJ71rGcpjY7rAzQ973nPE5iUaYJPetKTBD49/elPj1e+8pXSj3rpS18az33uc1umEzpVr33ta+NFL3pR/MAP/EC85z3vibe+9a2qyFetWrVq1apVq1atWrVq1apVq1btCgGl/vEf/1HV7ACNvvSlL8V/+A//QaARmlKwmH7nd37nkq8Fw4lKfvv27dP1HvrQhwqQeuITn6jXX/3qV6u6G0wp2FNUzXv961/ffr7b7cbb3/72eM5zniOwCuYWmlRUAky79tprBUBdf/31SgvcvXt3vPGNb9S1qlWrVq1atWrVqlWrVq1atWrVqt051mmaprmcDwBIPfKRjxQzac2aNfGJT3xCKX3vf//749/9u38noOruZgidA5ohxg5jq1q1atWqVatWrVq1atVqPFGtWrVqt86GLvcDCJH/p//0n876PWl7pM9Vq1atWrVq1apVq1atWrVq1apVq3abg1JoNcEcOtM+97nPxZYtWy73ctWqVatWrVq1atWqVatWrVq1atXugXbZoNR3fMd3SLNpeXlZ/+90OtKSevGLX9xWyatWrVq1atWqVatWrVq1atWqVatW7TYFpV71qlfFzMxMbN26Nebn5+Pxj3983Oc+95G+1C/8wi9c7uWqVatWrVq1atWqVatWrVq1atWq3QPt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jje96U1alH/wB38wduzYITG/atWqVatWrVq1atWqVbsSDNHyZz7zmfFVX/VV8dVf/dXxK7/yK5JT+f7v/369Tiy0a9cu6T5hP/IjPyJdqFe96lWKid7ylrdIG/jXfu3X2mtyqE+Vvn/1r/6VClehKfWnf/qnOtivVu0W28oXa+NVu21BqazaACD1Uz/1U2ImpQEO/f3f/71ykC/XALO+7/u+L379139d4npp5Cf/r//1v+LNb35zfMM3fIN+95u/+Zuikn7wgx9UKdN3vetd8U//9E/xl3/5lxLs4/upCvjiF784fuZnfkbIP7nQMLhYiAepqK9+9asrKFWtWrVq1apVq1atWrUrxgCPyFr5L//lv0isnPgHECnFzMkioSJf2td8zdconnrpS18aP/ETPxH3ve99VXkPneC07/qu71LMBJD1wz/8w3G/+90vfv/3f18awtWqVat2lwGlEDJPptQnP/lJAT5p/PthD3tY/NiP/dhl3wDpeaD2lDIdBKUod7q8vKzfp93//veXZhWlTQGl+PshD3nIaRUlYD895znPUUXARzziEXrP4DXyPYNpgtWqVatWrVq1atWqVat2JRjZI/ycy87Fbvru7/5u/VzIfuAHfkA/1apVq3aXBaUoCYpBDf3v//2/x5o1a271l0Mf/ehHP6r0vTMN5B+wa/369ectYXq+Eqf52oXec/LkyZifn4+JiYmzvntxcVE/aby3WrVq1apVq1atWrVq1apVq3bpBqklVj4Z0f2K6AxN16arduuEzmEu/e7v/m58+ctfjltrlA4lxxmdp1uqRXV7GdRVNKzyh2qD1apVq1atWrVq1apVq1atWrViTe9UNL25izTHSkRvPmJ1T222arcelEJAnPQ5NKRurZGed/DgQVXFowwpP3/zN38jFhb/hs20tLQUx48fP28J0/OVOM3XLvSetWvXnpMlhb3kJS+RplX+AKBVq1atWrVq1apVq1atWrVq1WzL/xSx/MmLNEdTm6vabQdKYT/5kz8pkbyjR4/GrbFv/MZvlDbVxz/+8faHKhKInue/AcEGS5hSqhTxvixhyt9cA3BrsMQpgNMDH/jASyqDei4bGxvTNQZ/qlWrVq1atWrVqlWrVq1atWq3xDq12ardOk2ptNe+9rXx+c9/Pnbu3BnXXHNNTE1NnfY6GlGXYmhSDVZ9wLjWpk2b2t8/61nPUtW/jRs3Chh63vOeJzAJkXPsSU96ksCnpz/96fHKV75S+lFUlkA8HWAJe/azn617ftGLXiTxvve85z3x1re+Nd7xjndc7qNXq1atWrVq1apVq1atWrVq1apVu7NAqSc/+clxR9mrX/1qlTR96lOfKuFxqua9/vWvb1/vdrvx9re/XdX2AKsAtZ75zGfGz/7sz7bvufbaawVAXX/99fGa17wmdu/eHW984xt1rWrVqlWrVq1atWrVqlWrVq3a7W2VKVXt3NZpJIdf7UJG9T0Ez9GXqql81apVq1atWrVq1apVuxy7O8cTd+dnq3Zhaxb/Xn93xq4772sx+rCIpU9EDG2Mzsh9a5Peg+zkJa4Nl82UGhQq/+d//mf9+0EPelA84hGPuKWXqlatWrVq1apVq1atWrVq1ard7cwcmE5lSlW7jUApRMW/53u+J9773vfG+vXr9Tsq5H391399vOUtb4ktW7Zc7iWrVatWrVq1atWqVatWrVq1anc3q4lZ1W7r6nuIjZ86dSo+/elPqwIfP5/61KdEzfrhH/7hy71ctWrVqlWrVq1atWrVqlWrVq1atXugXTZT6s///M/jL//yL+MBD3hA+zsq4L3uda9TNbxq1apVq1atWrVq1apVq1atWrU2fa9atduKKdXr9WJkZOSs3/M7XqtWrVq1atWqVatWrVq1atWqVatW7TYHpb7hG74hfuRHfiT27t3b/m7Pnj1x/fXXxzd+4zde7uWqVatWrVq1atWqVatWrVq1andLq0yparcxKPXa175W+lH3ute94t73vrd+rr32Wv3uf/yP/3G5l6tWrVq1atWqVatWrVq1atWqXaHWNMvRrB65s2+j2j1FU+qqq66Kj370o9KV+sxnPqPfoS/1hCc84fa4v2rVqlWrVq1atWrVqlWrVq3aXdVWvhjROx7N0MbodDp39t1Uu7uDUhgeqaOkAAEAAElEQVQD7YlPfKJ+qlWrVq1atWrVqlWrVq1atWr3VFsdSNU7DyjV1DS+arcyfe8DH/hAvP3tbz/td7/zO7+j1L2tW7fGf/yP/zEWFxcv9XLVqlWrVq1atWrVqlWrVq1atSvdeqf8jwo8VbsdQamf/dmfjU9/+tPt/z/5yU/Gs571LKXt/fiP/3j86Z/+afziL/7iLbiFatWqVatWrVq1atWqVatWrdqVbecCpSpQVe02AqU+/vGPn1Zd7y1veUtcd9118eu//uvxghe8IP77f//v8da3vvVSL1etWrVq1apVq1atWrVq1apVu9tYBaCq3Y6g1LFjx2Lbtm3t///mb/4mvvmbv7n9/6Mf/ei46aabbsEtVKtWrVq1atWqVatWrVq1atWubKugVLXbEZQCkLrhhhv076WlJVXge8xjHtO+furUqRgZGbkFt1CtWrVq1apVq1atWrVq1apVu/tZBaqq3Uag1Ld8y7dIO+pv//Zv4yUveUlMTk7G137t17av/+M//mPc+973vtTLVatWrVq1atWqVatWrVq1atXuEQBUBaeqnduG4xLt537u5+IpT3lKPP7xj4/p6en47d/+7RgdHW1f/43f+I140pOedKmXq1atWrVq1apVq1atWrVq1ardbawXTbMUnU4fJ6hgVLXbDJTavHlz/L//9//ixIkTAqW63e5pr7/tbW/T76tVq1atWrVq1apVq1atWrVq9zDrzUasfCGakfvd2XdS7e4ISqWtW7funL/fuHHjbXE/1apVq1atWrVq1apVq1atWrUrzZoF/z13Z99JtbujplS1atWqVatWrVq1atWqVatW7Z5rzeqhaJqV8716ib+rVq1vFZSqVq1atWrVqlWrVq1atWrVql3QBEatfDFi9Uvne4f/7gz8rldbtdoFrYJS1apVq1atWrVq1apVq1atWrWLmEGnZvXCrw+yo5b/pbZqtQtaBaWqVatWrVq1atWqVatWrVq1aue0pncsmuV/PP13Kzee650XaMGaxlft3FZBqWrVqlWrVq1atWrVqlW7Qux1r3td3Ote94rx8fG47rrr4kMf+tAF30+V9Pvf//56/0Me8pB45zvfed73PvvZz45OpxO/8iu/cjvcebUr1la+HNGbP/13q/su8IHB9L2+Nb2ZaHqnbtt7q3bFWwWlqlWrVq1atWrVqlWrVu0KsN/7vd+LF7zgBfHTP/3T8dGPfjQe9rCHxTd90zfFwYMHz/n+97///fG93/u98axnPSs+9rGPxZOf/GT9fOpTnzrrvX/4h38YH/zgB2Pnzp13wJNUu7LsfMLml8mKWv50xPI/3RY3VO1uZJ2maSqP7iJ28uTJWLduXXzuc5+LoaGh6Ha7MTY2FsePH48dO3bEnj17Yvfu3XHzzTfHrl27Yt++fbF+/fpYXFyM1dXVmJqaiiNHjrTv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Uwj8fccdd5TuA8KpbHt87t/7Z+22KWJoaIg/DqggyuZKCczAh6FE6pdKfVXNo4z4kjhPi1h3kAwtFanCs2lltfttRfhIh8frs8+XNzFAjyweek5H/V4kpTrA4sV6IyCNjYiIiIiIiIiIiIiIWN55BZRIUxlnnnmmnH766S2fx7lSRETE8vR7kZTqAIizfvDBB2X69OlrRGYCrEKg00eZosdVrOepjNiWYz2vLYhtOdbz2oLYlmM9r01Yk9ozlAKYmGFesbyYO3eu1Go1efRReB9lwN8bbNCavh7A5+Nt7//jM2TfC7eB71QZTjrpJJqtOxYsWCCbb765PPDAA1OecFtb2tsziVgPsQ4m2+9FUqoDIFZ7k03c7X7NATq7dbnDW12I9RzreG1BbMuxjtcWxLYc63htQWzL61Y9ryhh02g0ZJdddpErrriCGfTc6Bx/v+997yvdZ8899+T3xx13XPrZZZddxs+BLbfcksQUtnESCqQCsvAdc8wxpcfs7u7mT9n1rQn1/ExjTWlvzzRiPcQ66LTfi6RURERERERERERERETEFAAUSocffrjsuuuusvvuu8s555zD7HpHHHEEvz/ssMNk4403ZogdcOyxx8o+++wjX/ziF+XVr361/OAHP5Abb7xRzj//fH6PKBAQVp/+9Kdl6623Jkl1yimnUNngxFdERETEqkQkpSIiIiIiIiIiIiIiIqYADj74YHn88cfl1FNPpRE51E2XXnppalSOEDpEeTj22msvueCCC+Tkk0+Wj33sYySeLr74Ytl+++3TbU488UQSW0cddRRD8V784hfzmD09Pc/INUZERKxbiKTUFATksqeddlqpbDYi1vNUQmzLsZ7XFsS2HOt5bUFsy7Ge1yasre0ZoXrtwvV+85vftHx20EEH8acdoJb65Cc/yZ/lwdpaz5NFrIdYD7EtLB8qyYrkJY2IiIiIiIiIiIiIiIiIiIiIiFgOZNrOiIiIiIiIiIiIiIiIiIiIiIiI1YRISkVERERERERERERERERERERErHZEUioiIiIiIiIiIiIiIiIiIiIiYrUjklJrED7zmc8wQ0ZfX5/MmjWrdBtk1EA6V2wzb948+fCHPyyjo6MtBocveMELaLa31VZbybe+9a2W43z1q1+VLbbYglk19thjD7n++utlXQTqCuaOZT833HADt7nvvvtKv//jH/+YO9aFF14oz33uc1mnO+ywg1xyySXP0FWtmUB7K9bhZz/72dw2f/nLX+QlL3kJ63DTTTeVs846q+U4sZ7LgXZ65JFHMpVzb2+vPPvZz6bp6PDwcG6b2JZXDWKfuvxA2vLddttNpk+fzvcaUpDfeeeduW323XfflrZ79NFHT/r9uK7iE5/4REv94X3lGBwclPe+972y3nrrybRp0+RNb3qTPProo7ljxPpdvvccflC3QGzHy4ff/e538trXvlY22mgj1icyx4WAPS4y0W244YZ8/+2///5y11135bZ56qmn5NBDD5UZM2ZwjI335ZIlSyY9BlkXEecn5Yjj2nVvPBTfpasQMDqPWDNw6qmnJmeffXZy/PHHJzNnzmz5fnR0NNl+++2T/fffP7n55puTSy65JJk7d25y0kknpdvcc889SV9fH4/x17/+NTn33HOTWq2WXHrppek2P/jBD5JGo5F84xvfSG6//fbkXe96VzJr1qzk0UcfTdY1DA0NJQ8//HDu553vfGey5ZZbJs1mk9vce++9SAaQXH755bnthoeH0+P84Q9/YD2fddZZrPeTTz456erqSm699dZn8OrWLGy++ebJJz/5yVwdLlmyJP1+4cKFyfz585NDDz00ue2225Lvf//7SW9vb/L1r3893SbWc3v88pe/TN7+9rcnv/rVr5K77747+elPf5rMmzcvOeGEE9JtYlteNYh96orhgAMOSL75zW/yub/llluSV73qVclmm22W6x/22WcfvqvC/gN9xmTej+syTjvttGS77bbL1d/jjz+efn/00Ucnm266aXLFFVckN954Y/LCF74w2WuvvdLvY/12hsceeyxXx5dddhnHD1dddRW/j+14+YDn+eMf/3jyk5/8hPV50UUX5b7/7Gc/y3HzxRdfnPz5z39OXve613EcNzAwkG5z4IEHJjvuuGPyxz/+Mbn66quTrbbaKjnkkEMmNQZZVxHnJ+WI49p1bzwU36WrDpGUWgOBwXkZKYWXcrVaTR555JH0s/POOy+ZMWMGyRXgxBNP5MAzxMEHH8xBv2P33XdP3vve96Z/j42NJRtttFFy5plnJus6QDStv/76JE+KE3lMdNrhzW9+c/LqV78699kee+yRvPvd716l5Z1KwMv7S1/6Utvv/+u//iuZPXt22paBj3zkI8k222yT/h3reXIASYqBuSO25VWD2Keu/Ik9+tzf/va36WeYzB977LFt9+nk/bguAwNpTMjLsGDBAi6iXHjhhelnf/vb33gPrr32Wv4d63f5gDb77Gc/O13kiu14xVEkpVC3G2ywQfL5z38+16a7u7tJLAFYLMR+N9xwQ24hp1KpJA899FDHY5B1HXF+kkcc165746H4Ll11iOF7UwjXXnstw8Lmz5+ffnbAAQfIokWL5Pbbb0+3gWw5BLbB5wBCeW666abcNtVqlX/7Nusyfvazn8mTTz4pRxxxRMt3r3vd6xgS8uIXv5jbhZio3iMUCNdDeMjOO+8sn//853OhNairvffeWxqNRq4OEcbz9NNPx3peDixcuFDmzJkT2/IqROxTV027BYpt93vf+57MnTtXtt9+eznppJNk2bJlk3o/rutAOBPCn571rGcxjAnheADGBCMjI7l3GEL7Nttss/QdFut3+fqG7373u/KOd7yDIWeO2I5XLu6991555JFHcu135syZDBsK2y9C9nbdddd0G2yP8e91113X8Rgkohzr8vwkjmszTNV7OFnEd+mqQX0VHTdiFQAv3bDDB/xvfDfeNngxDAwM8MU6NjZWus0dd9yxzt+3//3f/+VLcpNNNknrAv4aX/ziF+VFL3oRO9cf//jH9DyBpwGIqvHq3e9LhMgHPvABep1honnNNddwUvnwww/L2WefndYh/JDate/Zs2fHep4E/vGPf8i5554rX/jCF2JbXoV44oknYp+6EtFsNuW4445jfwvyyfHWt75VNt98c5Iq8H35yEc+wsniT37yk47fj+syMEGHv+Q222zDfvf000+nd85tt93G+sFEvOhlGb7DYv1OHhgjLFiwQN7+9renn8V2vPLhbXS8MRj+x6JiiHq9zvFIuM1EY5CI9vdgXZyfxHHtujceiu/SVYdISq1ifPSjH5XPfe5z427zt7/9LWc4GvHM1Ps///lP+dWvfiU/+tGPctthZf74449P/4Yh77/+9S8qfZyUWlcxmXoO6/D5z38+J0Hvfve7aXIMU/6IFa9jx0MPPSQHHnigHHTQQfKud70r/Ty25Yg1HTCEBlHy+9//Pvf5UUcdlf6OFXkYGu+3335y991309Q/Yny88pWvzPW/GFiD5MP7DsbQEatmkQv1DiLVEdtxxJqCOD9Z8XqJ49p1D/FduuoQSalVjBNOOCG3SlYGSOk7wQYbbNCSwcCz4+A7/7+YMQd/I9sIBp61Wo0/Zdv4MdbVev/mN7/J0LJOiCYM6C+77LL073b1vjbV6cpu36hDhO8hIxxW79vVYSfte22u58nWMQjTl770pczkef755094/NiWVwwg+taFPnV14H3ve5/8/Oc/Z6atUK3art26IhCkVCfvx4gMUEU95znPYf29/OUvZ9gFVD2hWipsw7F+J4f7779fLr/88lTJF9vxqoO3UbRXkNUO/L3TTjul2zz22GO5/TD+QEa+icYX4TnWJsT5ycqvl3V9XLsujofiu3TlIXpKrWKsv/76ZNPH+wnj18fDnnvuKbfeemvuxQpiBITT8573vHSbK664IrcftsHnAM61yy675LZBuAT+9m3WxXqHdyZIqcMOO0y6uromPP4tt9ySG/xMVO9rK1akfaMOEQ7pknrUFSaj8DYJ6xAvdpfNr4v1PJk6hkIKKcfxjKM9o34nQmzLK4Z1pU9dlUD/C0LqoosukiuvvLIlhKZduwW8H+7k/RiRYcmSJVSZof7QfvHeC9swQiPhOeVtONbv5ID+F++2V7/61bEdr2Kgv8CEN2y/CAmDV1TYfkG6wu/Ggb4GfbUT3J2MQdYmxPnJyq+XdX1cuy6Oh+K7dCViFZqoR0wS999/PzO8nX766cm0adP4O34WL16cS8n8ile8gmmzL730UmaKC1Ne33PPPUlfX1/y4Q9/mNlzvvrVrya1Wo3bhuk6kZXkW9/6FjOSHHXUUUzXGWYtWtdw+eWXMzML6qwI1NMFF1zA7/Dzmc98hlmekO7U8Yc//CGp1+vJF77wBW6D7AzIZnTrrbeu5itZM3HNNdcw8x7a7d13351897vfZds97LDDctlykI75P/7jP5iOGe0UbTlMxxzruT3++c9/MsX1fvvtx9/DtOSO2JZXDWKfumI45phjmHH2N7/5Ta7dLlu2jN//4x//YEbUG2+8kRkkf/rTnybPetazkr333js9Rifvx3UZJ5xwAusX9Yd+dP/990/mzp3LTIfA0UcfnWy22WbJlVdeyXrec889+eOI9ds5kG0KdYnMbSFiO15+YBzsY2KM1c4++2z+jnEz8NnPfpbjWPQNf/nLX5LXv/71zDw7MDCQHuPAAw9Mdt555+S6665Lfv/73ydbb711csghh0xqDLKuIs5PWhHHtevmeCi+S1cdIim1BuHwww/ny7b4c9VVV6Xb3HfffckrX/nKpLe3lwNKPBwjIyO542D7nXbaKWk0Ghy4I4VrEeeeey4HTdgG6Tv/+Mc/JusyMDDZa6+9Sr9Dx7rttttycIL04qivMHW240c/+lHynOc8h3W63XbbJb/4xS9WQ8mnBm666aZkjz324MSzp6eH9XnGGWckg4ODue3+/Oc/Jy9+8Yv5Qtt444050Cwi1nM58JyX9R/h2kNsy6sOsU9dfrRrt/7ueuCBB0hAzZkzh30DyFcsvCxcuDB3nE7ej+sqDj744GTDDTfk+wl9K/4GSeLA5P0973lPMnv2bL7r3vjGN+YIbSDWb2f41a9+xfZ755135j6P7Xj5gXFtWR+BcTPQbDaTU045haQS+ggszhTr/8knn+RYD4u+GMsdccQR6aLvZMYg6yLi/KQVcVy7bo6H4rt01aGCf1am8ioiIiIiIiIiIiIiIiIiIiIiImIiRE+piIiIiIiIiIiIiIiIiIiIiIjVjkhKRURERERERERERERERERERESsdkRSKiIiIiIiIiIiIiIiIiIiIiJitSOSUhERERERERERERERERERERERqx2RlIqIiIiIiIiIiIiIiIiIiIiIWO2IpFRERERERERERERERERERERExGpHJKUiIiIiIiIiIiIiIiIiIiIiIlY7IikVEREREREREREREREREREREbHaEUmpiIiIiCmGSqUiF198sayN+MQnPiHz589fq6/RscUWW8g555zzTBdjyuA//uM/5Iwzzlip9ffEE0/IvHnz5J///OdKKGFERERERERERMRkEUmpiIiIiDUAb3/720nE4Kerq4vEzMtf/nL5xje+Ic1mM7ftww8/LK985Ss7Ou5UInf+9re/yemnny5f//rXJ3WNUxU33HCDHHXUUbI2EIk77bTTKj3Hn//8Z7nkkkvkAx/4wEo97ty5c+Wwww6T0047baUeNyIiIiIiIiIiojNEUioiIiJiDcGBBx5IMua+++6TX/7yl/LSl75Ujj32WHnNa14jo6Oj6XYbbLCBdHd3y9qGu+++m/+//vWvb3uNw8PDz0DJVs35119/fenr61tpx5vqGK9uzz33XDnooINk2rRpK/28RxxxhHzve9+Tp556aqUfOyIiIiIiIiIiYnxEUioiIiJiDQFIGJAxG2+8sbzgBS+Qj33sY/LTn/6UBNW3vvWtUvUTJvLve9/7ZMMNN5Senh7ZfPPN5cwzz0zDm4A3vvGN3Mf/BvkD4gdqLEzyd9ttN7n88stzZcG2CJV6xzveIdOnT5fNNttMzj///Nw2CHk65JBDZM6cOdLf3y+77rqrXHfdden3KDuuA+V61rOeRRVUSK4V1Tavfe1r+Xu1WmV5XUH2hje8QT7zmc/IRhttJNtssw0/v/XWW+VlL3uZ9Pb2ynrrrUfF0ZIlS9Lj+X64BlznrFmz5JOf/CTP/+EPf5hl3mSTTeSb3/zmuPdk3333Zf0ed9xxVNUccMAB/Py2226jkgv1h+MjtAyhYI7FixfLoYceynrBvfnSl77EY+E47cLPHnjgAd4XHHPGjBny5je/WR599NEWRdL//d//cd+ZM2fKW97yFp5rPPzhD3/guUGAzZ49m9fw9NNP8zuo8NBettxyS9bljjvuKP/v//2/dN/f/OY3vBdXXHEF7y+Osddee8mdd97J79EucV+hZHKln7fVBQsWyDvf+U6Sb7ge3C9sV7ye//mf/+H50U7KMDY2xjJ5+wiBa0cbRD3jufnqV7+a+x7lOe+883ivcH1oh+H1Adtttx3b1kUXXTRuPUZERERERERERKx8RFIqIiIiYg0GJvIgCn7yk5+Ufv/lL39Zfvazn8mPfvQjEgVQfDj5hPAwAMQLFFj+N8ibV73qVSQabr75Ziq0MOEHKRLii1/8IokIbPOe97xHjjnmmJSMwDH22Wcfeeihh3h+kA0nnnhiGmp49dVXMywKSq+//vWvDMkDWQFyqQwf+tCHUoIIZcWPA+XEeS+77DL5+c9/LkuXLiWxAoIF13ThhReSVAN5FOLKK6+Uf/3rX/K73/1Ozj77bIZoQXWG/UCeHX300fLud797Qj+hb3/729JoNEjufO1rXyPZgvuy8847y4033iiXXnopySOQSI7jjz+e26NuUG7Ux5/+9Ke250C9gZCCWue3v/0t97nnnnvk4IMPzm0HQhGEJOoBP9j2s5/9bNvj3nLLLbLffvvJ8573PLn22mvl97//Pe81iB4AhNR3vvMdXtftt98uH/zgB+Vtb3sbjxvi4x//ONsDrrder5OsBFC+E044gcSO3zcvM5RNjz32GEnVm266iQQlyhIqkv7xj3/Ij3/8Y7ZvlLUMf/nLX2ThwoVsi0V8/vOf5/OBNvrRj36U7Q11F+KUU06RN73pTWyjIApB5CFUNMTuu+/OexQREREREREREbGakUREREREPOM4/PDDk9e//vWl3x188MHJtttum/6Nrvuiiy7i7+9///uTl73sZUmz2SzdN9x2PGy33XbJueeem/69+eabJ29729vSv3H8efPmJeeddx7//vrXv55Mnz49efLJJ0uPt99++yVnnHFG7rP/+7//SzbccMO2ZUA5i68l1Mv8+fOToaGh9LPzzz8/mT17drJkyZL0s1/84hdJtVpNHnnkkXQ/XMPY2Fi6zTbbbJO85CUvSf8eHR1N+vv7k+9///tty7TPPvskO++8c+6zT33qU8krXvGK3GcPPvggy37nnXcmixYtSrq6upILL7ww/X7BggVJX19fcuyxx6afoXxf+tKX+Puvf/3rpFarJQ888ED6/e23385jXn/99fz7tNNO4zFwfMeHP/zhZI899mhb/kMOOSR50YteVPrd4OAgj3fNNdfkPj/yyCO5H3DVVVexDJdffnmurvHZwMBAWq4dd9wxd4yrr746mTFjBs8R4tnPfjbbju+HenrssceS8YB2gboptnHU34EHHtjyrLzyla9M/0Y5jz766Nw2qK9jjjkm99kHP/jBZN999x23HBERERERERERESsf9dVNgkVERERETA6YW3s4WxEIU4MhOsLaoHiCEugVr3jFuMeDygmhU7/4xS+obEFI28DAQItS6vnPf376O86P0EIoXwCoWqAUQhhcGaBKgVIoVEZBnTM4OCjLli2blJfSDjvsQKWSAyoXqGMQsuV40YteRLURFFUIpwOg3kEooAOfb7/99unftVqNoX9+Te2wyy67tFzbVVddVepvBCUT6nJkZITqGwdC7Tz0sAy4pk033ZQ/DqibEHaI7xBiCUAFh3BKB0IDxys/7hMUS2WASgn3Au0nBEJCcW/btQWcE8B5EdZZBtQR2hnqNwTqxr3DAISbIrxvPGAfhLaWPQN77rlny9/FjHxl2xRVWQjtQ11ERERERERERESsXkRSKiIiImINB0gJeO6UASFR9957L0OkEMKGELL999+/xTenGCqHEKcvfOELstVWW3FC/u///u8tRtPIAhgCpICH52Gf8QBCAl5D//Zv/9byXTvvoHYIyafJoKz8411Tp+fHtSEE7nOf+1zLtiBsQPasKky2/OPdJ/fgAjkJP6YQRZP58LxODo13XhwbdQFPqiJAtE3m3sLLC4QR2mdITq5MIKRwInIsIiIiIiIiIiJi5SN6SkVERESswYAvEky94YnTDjCRho/Pf//3f8sPf/hDevS4bw/IBPcPckDBBIUVDNChQoICChn/JgMoZ6A2aZexDGQZVEsgvYo/oXppebDttttSiQNvqfCacNzx1EgrC7g2+C9BtVS8NpAsMNNGvbuHFwBPpL///e/jXtODDz7IHwe8uOBfBcXU8gL3CZ5cZcBxQT5BIVe8jlCxNRFAFBXbGOrokUceof9U8dggmSYDmKF7fRTxxz/+seVv1OVkt4FxfVEdFhERERERERERseoRSamIiIiINQRDQ0OcyMM8HKbYyBwH82uE5ME0vAww8P7+978vd9xxB0kPmH6DZHI1CogTkBI4rmdc23rrrVNjaZA7b33rWydUCxWBjGc4DzLcgRCCKTfIMJhpA6eeeioNtKGWAoEDtdcPfvADOfnkk1e4nmBWDbXV4YcfTjIBoXTvf//7mQHPQ/dWJd773veSjEMdgHhCONqvfvUrOeKII0jOILwOZUOWP5QN13/kkUfmsgoWAXUbCEJcG+799ddfz3sOM/kyg+9OcdJJJ7GMMKqHYTjaCbLRIVMgygnVHMzNYeaO68C5zz33XP7dKdDGoNZDe8Jx0Y5xPQiTQ/v49a9/TdLzmmuuoWE6zNInAyiYQHLBpL0ItL2zzjqLbR+Z99D+YXYeAp994xvf4DYwu0fdhqb4UGHBiH2isNeIiIiIiIiIiIiVj0hKRURERKwhQBY3hDxhkg9/KBAayK7305/+lP5HZQCxgEk5iAv4DmHyf8kll6RqJGRMQ6gelC+uBAGRhQx0e+21F8PQkMkOk/7JAOoYkA3z5s1jJj8QKsgC5+XEMZEdDtugXC984QvlS1/6Ej2EVhTwowIJBGIIx0boIbK6feUrX5HVgY022ohkCAgoEBm49uOOO45EoNc76hikDAhFEDTwvII6p13oIsgq3Gfcl7333pv7QHEF5duK4DnPeQ7vAchHeFyhTDgPFEzApz71KWanQxY+lA/tDuF87cJFywAVH/Z76UtfSgIJJCmuB+0Q1wKyDuVA1rv7779/uYjDd77zncwsWQQy/4HkQtv+9Kc/zXpH2wsBYhSEKFRjIEpRvlB9hvqAN9ZLXvKSSZcrIiIiIiIiIiJixVCB2/kKHiMiIiIiIiJiHCDUEL5NIAmhmoqYHGB2jtBMkHRF4/LxAHLsoosuomKrHUCYfuADH6BiMCIiIiIiIiIiYvUiGp1HRERERESsZNx8880MlYM6CX5Sn/zkJ/k5wjEjJg8YtkPlhPDAlQkcD2b8CMWMiIiIiIiIiIhY/YikVERERERExCoAshvC7B2hjrvssotcffXVkzb5jsiw7777rvTqwP048cQTYzVHRERERERERDxDiOF7ERERERERERERERERERERERGrHdHoPCIiIiIiIiIiIiIiIiIiIiJitSOSUhERERERERERERERERERERERqx2RlIqIiIiIiIiIiIiIiIiIiIiIWO2IpFRERERERERERERERERERERExGpHJKUiIiIiIiIiIiIiIiIiIiIiIlY7IikVEREREREREREREREREREREbHaEUmpiIiIiIiIiIiIiIiIiIiIiIjVjkhKRURERERERERERERERERERESsdkRSKiIiIiIiIiIiIiIiIiIiIiJCVjf+f7IupRC974/7AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from dimelo import cluster\n", + "import importlib\n", + "import inspect\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "# If this notebook kernel already imported dimelo before local edits, reload the module.\n", + "cluster = importlib.reload(cluster)\n", + "required_kwargs = {\"return_feature_table\", \"scale_features\", \"return_scale_table\"}\n", + "observed_kwargs = set(inspect.signature(cluster.read_window_feature_matrix).parameters)\n", + "if not required_kwargs.issubset(observed_kwargs):\n", + " raise RuntimeError(\n", + " \"This notebook is using an older dimelo.cluster module. \"\n", + " f\"Imported from: {cluster.__file__}. \"\n", + " \"Restart the kernel or install this repo in editable mode with `python -m pip install -e .`.\"\n", + " )\n", + "\n", + "# Fixed window sizes for tutorial reproducibility.\n", + "shared_window = 1000\n", + "region_window_size = 1000\n", + "\n", + "# Exposed plotting handles for tuning.\n", + "plot_point_size = 1.2\n", + "plot_point_alpha = 0.015\n", + "profile_smoothing = \"gaussian\" # None | \"gaussian\" | \"boxcar\"\n", + "show_unsmoothed_overlay = True\n", + "\n", + "rw_on = cluster.extract_read_windows(\n", + " hdf5_file=extract_file,\n", + " motifs=[\"A,0\"],\n", + " regions=ctcf_target_regions,\n", + " config=cluster.ReadWindowExtractionConfig(\n", + " window_size=shared_window,\n", + " orientation_aware=True,\n", + " ),\n", + " span_full_window=False,\n", + ")\n", + "\n", + "rw_off = cluster.extract_read_windows(\n", + " hdf5_file=extract_file,\n", + " motifs=[\"A,0\"],\n", + " regions=ctcf_off_target_regions,\n", + " config=cluster.ReadWindowExtractionConfig(\n", + " window_size=shared_window,\n", + " orientation_aware=True,\n", + " ),\n", + " span_full_window=False,\n", + ")\n", + "\n", + "rw_single = cluster.merge_read_window_results(\n", + " [rw_on, rw_off],\n", + " source_labels=[\"on_target\", \"off_target\"],\n", + " align=\"error\",\n", + ")\n", + "\n", + "feat_single_scaled, feat_single_names, feat_single_table, feat_single_scale_table = cluster.read_window_feature_matrix(\n", + " rw_single,\n", + " n_pca=6,\n", + " use_peak_features=False,\n", + " scale_features=True,\n", + " scaling_method=\"standard\",\n", + " family_weighting=None,\n", + " return_feature_table=True,\n", + " return_scale_table=True,\n", + ")\n", + "feat_single = feat_single_scaled\n", + "\n", + "clust_single = cluster.cluster_read_windows(\n", + " feat_single_scaled,\n", + " method=\"kmeans\",\n", + " n_clusters=2,\n", + " random_state=42,\n", + ")\n", + "\n", + "cluster.plot_cluster_profiles(\n", + " data_matrix=rw_single.data_matrix,\n", + " labels=clust_single.labels_size_ordered,\n", + " val_matrix=rw_single.val_matrix,\n", + " metadata=rw_single.metadata,\n", + " window_size=shared_window,\n", + " point_size=plot_point_size,\n", + " point_alpha=plot_point_alpha,\n", + " smoothing=profile_smoothing,\n", + " show_unsmoothed_overlay=show_unsmoothed_overlay,\n", + ")\n", + "\n", + "sample_labels_single = np.array([m.get(\"source_label\", \"unknown\") for m in rw_single.metadata])\n", + "feature_summary_single = cluster.summarize_feature_matrix(\n", + " feat_single,\n", + " feat_single_names,\n", + " feature_table=feat_single_table,\n", + " labels=sample_labels_single,\n", + ")\n", + "cluster_vs_group = pd.crosstab(\n", + " clust_single.labels_size_ordered,\n", + " sample_labels_single,\n", + " rownames=[\"cluster\"],\n", + " colnames=[\"region_set\"],\n", + ")\n", + "cluster_vs_group_frac = cluster_vs_group.div(cluster_vs_group.sum(axis=1), axis=0)\n", + "purity = cluster_vs_group.max(axis=1).sum() / cluster_vs_group.values.sum()\n", + "\n", + "print(f\"Reads used: on_target={rw_on.data_matrix.shape[0]}, off_target={rw_off.data_matrix.shape[0]}, total={rw_single.data_matrix.shape[0]}\")\n", + "print(f\"Feature matrix: {feature_summary_single['n_reads']} reads x {feature_summary_single['n_features']} features\")\n", + "print(\"K-means input: feature matrix scaled inside read_window_feature_matrix(scale_features=True, family_weighting='equal_family')\")\n", + "print(f\"Cluster purity vs on/off labels: {purity:.3f}\")\n", + "print(\"Feature families\")\n", + "display(feature_summary_single[\"feature_counts_by_family\"])\n", + "print(\"Most variable default features\")\n", + "display(feature_summary_single[\"top_variable_features\"].head(8))\n", + "print(\"Feature scaling preview\")\n", + "display(feat_single_scale_table.head(8))\n", + "print(\"Counts by cluster and region set:\")\n", + "display(cluster_vs_group)\n", + "print(\"Within-cluster fractions:\")\n", + "display(cluster_vs_group_frac.round(3))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Multiple motif (merged on-target vs off-target reads)\n", + "\n", + "This example plots both motifs with motif-matched colors. You can switch between:\n", + "- `motif_profile_mode=\"single_axis\"` (shared y-axis) and\n", + "- `motif_profile_mode=\"separate_axes\"` (one y-axis per motif).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Multi-motif reads used: on_target=2173, off_target=4030, total=6203\n", + "Default feature matrix: 6203 reads x 31 features\n", + "Rich feature matrix: 6203 reads x 108 features\n", + "K-means input for cluster labels: raw default feature matrix (legacy tutorial behavior)\n", + "Scaled default features are still computed for classifier/QC examples below.\n", + "Multi-motif cluster purity vs on/off labels: 0.807\n", + "Default feature families\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "family", + "rawType": "str", + "type": "string" + }, + { + "name": "n_features", + "rawType": "int64", + "type": "integer" + } + ], + "ref": "6fbbfd98-4b6f-4847-a23b-e1b3222ad14f", + "rows": [ + [ + "1", + "density", + "13" + ], + [ + "3", + "summary", + "7" + ], + [ + "2", + "pca", + "6" + ], + [ + "0", + "autocorr", + "5" + ] + ], + "shape": { + "columns": 2, + "rows": 4 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
familyn_features
1density13
3summary7
2pca6
0autocorr5
\n", + "
" + ], + "text/plain": [ + " family n_features\n", + "1 density 13\n", + "3 summary 7\n", + "2 pca 6\n", + "0 autocorr 5" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rich feature families\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "family", + "rawType": "str", + "type": "string" + }, + { + "name": "n_features", + "rawType": "int64", + "type": "integer" + } + ], + "ref": "27d9eb70-d64c-4006-9b78-a1b5f22e1109", + "rows": [ + [ + "3", + "density", + "39" + ], + [ + "6", + "summary", + "21" + ], + [ + "4", + "fft", + "15" + ], + [ + "1", + "autocorr", + "12" + ], + [ + "0", + "asymmetry", + "9" + ], + [ + "2", + "center_edge", + "6" + ], + [ + "5", + "pca", + "6" + ] + ], + "shape": { + "columns": 2, + "rows": 7 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
familyn_features
3density39
6summary21
4fft15
1autocorr12
0asymmetry9
2center_edge6
5pca6
\n", + "
" + ], + "text/plain": [ + " family n_features\n", + "3 density 39\n", + "6 summary 21\n", + "4 fft 15\n", + "1 autocorr 12\n", + "0 asymmetry 9\n", + "2 center_edge 6\n", + "5 pca 6" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rich features by motif\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "motif", + "rawType": "str", + "type": "string" + }, + { + "name": "n_features", + "rawType": "int64", + "type": "integer" + } + ], + "ref": "12cf3a50-d0de-4702-b54b-586da3d4761f", + "rows": [ + [ + "2", + "pooled", + "40" + ], + [ + "0", + "A,0", + "34" + ], + [ + "1", + "CG,0", + "34" + ] + ], + "shape": { + "columns": 2, + "rows": 3 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
motifn_features
2pooled40
0A,034
1CG,034
\n", + "
" + ], + "text/plain": [ + " motif n_features\n", + "2 pooled 40\n", + "0 A,0 34\n", + "1 CG,0 34" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Default feature scaling preview\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "feature_name", + "rawType": "str", + "type": "string" + }, + { + "name": "scaling_method", + "rawType": "str", + "type": "string" + }, + { + "name": "family", + "rawType": "str", + "type": "string" + }, + { + "name": "family_weight", + "rawType": "float64", + "type": "float" + }, + { + "name": "scaled", + "rawType": "bool", + "type": "boolean" + }, + { + "name": "center", + "rawType": "float64", + "type": "float" + }, + { + "name": "scale", + "rawType": "float64", + "type": "float" + }, + { + "name": "raw_mean", + "rawType": "float64", + "type": "float" + }, + { + "name": "raw_std", + "rawType": "float64", + "type": "float" + }, + { + "name": "scaled_mean", + "rawType": "float64", + "type": "float" + }, + { + "name": "scaled_std", + "rawType": "float64", + "type": "float" + } + ], + "ref": "8ac416db-f019-4d6d-995a-473831820e35", + "rows": [ + [ + "0", + "pca_0", + "standard", + "pca", + "0.4082482904638631", + "True", + "8.247473315287315e-17", + "0.26355477077539524", + "2.9535324156837427e-16", + "0.26355477077539485", + "-1.1638067674739491e-15", + "0.4082482904638646" + ], + [ + "1", + "pca_1", + "standard", + "pca", + "0.4082482904638631", + "True", + "-8.820214517737823e-17", + "0.18811959268629744", + "-5.0144359216791774e-17", + "0.18811959268629783", + "-1.3920407807449935e-16", + "0.40824829046386346" + ], + [ + "2", + "pca_2", + "standard", + "pca", + "0.4082482904638631", + "True", + "-5.097396701809521e-17", + "0.18474617797555246", + "-5.361170872781845e-17", + "0.1847461779755514", + "-6.316709027338923e-17", + "0.40824829046386096" + ], + [ + "3", + "pca_3", + "standard", + "pca", + "0.4082482904638631", + "True", + "2.2451455136059913e-16", + "0.18337894839163327", + "2.2429306159871397e-16", + "0.1833789483916325", + "-1.6392479649823717e-17", + "0.40824829046386174" + ], + [ + "4", + "pca_4", + "standard", + "pca", + "0.4082482904638631", + "True", + "-5.4983155435248767e-17", + "0.18173755904387098", + "-6.391657583128344e-17", + "0.18173755904387007", + "-3.460162280117014e-17", + "0.4082482904638644" + ], + [ + "5", + "pca_5", + "standard", + "pca", + "0.4082482904638631", + "True", + "-1.0252067523864093e-16", + "0.17671025733181234", + "-1.0386527470220638e-16", + "0.17671025733181248", + "1.0676253976929e-17", + "0.4082482904638619" + ], + [ + "6", + "autocorr_20", + "standard", + "autocorr", + "0.4472135954999579", + "True", + "0.00872424766826622", + "0.03266366933767494", + "0.008724247668266407", + "0.0326636693376748", + "3.266056706974022e-16", + "0.44721359549996526" + ], + [ + "7", + "autocorr_75", + "standard", + "autocorr", + "0.4472135954999579", + "True", + "0.0034970248304790263", + "0.0229627213372743", + "0.0034970248304791625", + "0.02296272133727445", + "-2.815380973295778e-17", + "0.4472135954999559" + ] + ], + "shape": { + "columns": 11, + "rows": 8 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
feature_namescaling_methodfamilyfamily_weightscaledcenterscaleraw_meanraw_stdscaled_meanscaled_std
0pca_0standardpca0.408248True8.247473e-170.2635552.953532e-160.263555-1.163807e-150.408248
1pca_1standardpca0.408248True-8.820215e-170.188120-5.014436e-170.188120-1.392041e-160.408248
2pca_2standardpca0.408248True-5.097397e-170.184746-5.361171e-170.184746-6.316709e-170.408248
3pca_3standardpca0.408248True2.245146e-160.1833792.242931e-160.183379-1.639248e-170.408248
4pca_4standardpca0.408248True-5.498316e-170.181738-6.391658e-170.181738-3.460162e-170.408248
5pca_5standardpca0.408248True-1.025207e-160.176710-1.038653e-160.1767101.067625e-170.408248
6autocorr_20standardautocorr0.447214True8.724248e-030.0326648.724248e-030.0326643.266057e-160.447214
7autocorr_75standardautocorr0.447214True3.497025e-030.0229633.497025e-030.022963-2.815381e-170.447214
\n", + "
" + ], + "text/plain": [ + " feature_name scaling_method family family_weight scaled center \\\n", + "0 pca_0 standard pca 0.408248 True 8.247473e-17 \n", + "1 pca_1 standard pca 0.408248 True -8.820215e-17 \n", + "2 pca_2 standard pca 0.408248 True -5.097397e-17 \n", + "3 pca_3 standard pca 0.408248 True 2.245146e-16 \n", + "4 pca_4 standard pca 0.408248 True -5.498316e-17 \n", + "5 pca_5 standard pca 0.408248 True -1.025207e-16 \n", + "6 autocorr_20 standard autocorr 0.447214 True 8.724248e-03 \n", + "7 autocorr_75 standard autocorr 0.447214 True 3.497025e-03 \n", + "\n", + " scale raw_mean raw_std scaled_mean scaled_std \n", + "0 0.263555 2.953532e-16 0.263555 -1.163807e-15 0.408248 \n", + "1 0.188120 -5.014436e-17 0.188120 -1.392041e-16 0.408248 \n", + "2 0.184746 -5.361171e-17 0.184746 -6.316709e-17 0.408248 \n", + "3 0.183379 2.242931e-16 0.183379 -1.639248e-17 0.408248 \n", + "4 0.181738 -6.391658e-17 0.181738 -3.460162e-17 0.408248 \n", + "5 0.176710 -1.038653e-16 0.176710 1.067625e-17 0.408248 \n", + "6 0.032664 8.724248e-03 0.032664 3.266057e-16 0.447214 \n", + "7 0.022963 3.497025e-03 0.022963 -2.815381e-17 0.447214 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rich feature scaling preview\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "feature_name", + "rawType": "str", + "type": "string" + }, + { + "name": "scaling_method", + "rawType": "str", + "type": "string" + }, + { + "name": "family", + "rawType": "str", + "type": "string" + }, + { + "name": "family_weight", + "rawType": "float64", + "type": "float" + }, + { + "name": "scaled", + "rawType": "bool", + "type": "boolean" + }, + { + "name": "center", + "rawType": "float64", + "type": "float" + }, + { + "name": "scale", + "rawType": "float64", + "type": "float" + }, + { + "name": "raw_mean", + "rawType": "float64", + "type": "float" + }, + { + "name": "raw_std", + "rawType": "float64", + "type": "float" + }, + { + "name": "scaled_mean", + "rawType": "float64", + "type": "float" + }, + { + "name": "scaled_std", + "rawType": "float64", + "type": "float" + } + ], + "ref": "03b0bc38-e805-44b6-a310-ef713790133c", + "rows": [ + [ + "0", + "pooled__pca_0", + "standard", + "pca", + "0.4082482904638631", + "True", + "8.247473315287315e-17", + "0.26355477077539524", + "2.9535324156837427e-16", + "0.26355477077539485", + "-1.1638067674739491e-15", + "0.4082482904638646" + ], + [ + "1", + "pooled__pca_1", + "standard", + "pca", + "0.4082482904638631", + "True", + "-8.820214517737823e-17", + "0.18811959268629744", + "-5.0144359216791774e-17", + "0.18811959268629783", + "-1.3920407807449935e-16", + "0.40824829046386346" + ], + [ + "2", + "pooled__pca_2", + "standard", + "pca", + "0.4082482904638631", + "True", + "-5.097396701809521e-17", + "0.18474617797555246", + "-5.361170872781845e-17", + "0.1847461779755514", + "-6.316709027338923e-17", + "0.40824829046386096" + ], + [ + "3", + "pooled__pca_3", + "standard", + "pca", + "0.4082482904638631", + "True", + "2.2451455136059913e-16", + "0.18337894839163327", + "2.2429306159871397e-16", + "0.1833789483916325", + "-1.6392479649823717e-17", + "0.40824829046386174" + ], + [ + "4", + "pooled__pca_4", + "standard", + "pca", + "0.4082482904638631", + "True", + "-5.4983155435248767e-17", + "0.18173755904387098", + "-6.391657583128344e-17", + "0.18173755904387007", + "-3.460162280117014e-17", + "0.4082482904638644" + ], + [ + "5", + "pooled__pca_5", + "standard", + "pca", + "0.4082482904638631", + "True", + "-1.0252067523864093e-16", + "0.17671025733181234", + "-1.0386527470220638e-16", + "0.17671025733181248", + "1.0676253976929e-17", + "0.4082482904638619" + ], + [ + "6", + "A,0__mean", + "standard", + "summary", + "0.2182178902359924", + "True", + "0.0035774625181363862", + "0.004131523596149701", + "0.003577462518136417", + "0.004131523596149662", + "3.846315137706692e-17", + "0.21821789023599472" + ], + [ + "7", + "A,0__var", + "standard", + "summary", + "0.2182178902359924", + "True", + "0.0035475947928421737", + "0.0040760002537805875", + "0.003547594792842113", + "0.0040760002537806", + "-1.5568269263172284e-15", + "0.21821789023599458" + ] + ], + "shape": { + "columns": 11, + "rows": 8 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
feature_namescaling_methodfamilyfamily_weightscaledcenterscaleraw_meanraw_stdscaled_meanscaled_std
0pooled__pca_0standardpca0.408248True8.247473e-170.2635552.953532e-160.263555-1.163807e-150.408248
1pooled__pca_1standardpca0.408248True-8.820215e-170.188120-5.014436e-170.188120-1.392041e-160.408248
2pooled__pca_2standardpca0.408248True-5.097397e-170.184746-5.361171e-170.184746-6.316709e-170.408248
3pooled__pca_3standardpca0.408248True2.245146e-160.1833792.242931e-160.183379-1.639248e-170.408248
4pooled__pca_4standardpca0.408248True-5.498316e-170.181738-6.391658e-170.181738-3.460162e-170.408248
5pooled__pca_5standardpca0.408248True-1.025207e-160.176710-1.038653e-160.1767101.067625e-170.408248
6A,0__meanstandardsummary0.218218True3.577463e-030.0041323.577463e-030.0041323.846315e-170.218218
7A,0__varstandardsummary0.218218True3.547595e-030.0040763.547595e-030.004076-1.556827e-150.218218
\n", + "
" + ], + "text/plain": [ + " feature_name scaling_method family family_weight scaled center \\\n", + "0 pooled__pca_0 standard pca 0.408248 True 8.247473e-17 \n", + "1 pooled__pca_1 standard pca 0.408248 True -8.820215e-17 \n", + "2 pooled__pca_2 standard pca 0.408248 True -5.097397e-17 \n", + "3 pooled__pca_3 standard pca 0.408248 True 2.245146e-16 \n", + "4 pooled__pca_4 standard pca 0.408248 True -5.498316e-17 \n", + "5 pooled__pca_5 standard pca 0.408248 True -1.025207e-16 \n", + "6 A,0__mean standard summary 0.218218 True 3.577463e-03 \n", + "7 A,0__var standard summary 0.218218 True 3.547595e-03 \n", + "\n", + " scale raw_mean raw_std scaled_mean scaled_std \n", + "0 0.263555 2.953532e-16 0.263555 -1.163807e-15 0.408248 \n", + "1 0.188120 -5.014436e-17 0.188120 -1.392041e-16 0.408248 \n", + "2 0.184746 -5.361171e-17 0.184746 -6.316709e-17 0.408248 \n", + "3 0.183379 2.242931e-16 0.183379 -1.639248e-17 0.408248 \n", + "4 0.181738 -6.391658e-17 0.181738 -3.460162e-17 0.408248 \n", + "5 0.176710 -1.038653e-16 0.176710 1.067625e-17 0.408248 \n", + "6 0.004132 3.577463e-03 0.004132 3.846315e-17 0.218218 \n", + "7 0.004076 3.547595e-03 0.004076 -1.556827e-15 0.218218 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rich features most associated with on/off labels (QC, not proof)\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "feature_name", + "rawType": "str", + "type": "string" + }, + { + "name": "effect_score", + "rawType": "float64", + "type": "float" + }, + { + "name": "min_group_mean", + "rawType": "float64", + "type": "float" + }, + { + "name": "max_group_mean", + "rawType": "float64", + "type": "float" + }, + { + "name": "overall_std", + "rawType": "float64", + "type": "float" + } + ], + "ref": "5bc21322-43a2-4c49-8b99-b0090219bf1c", + "rows": [ + [ + "0", + "pooled__pca_0", + "1.355536849758361", + "-0.1938624282161216", + "0.3595331733598573", + "0.4082482904638646" + ], + [ + "1", + "A,0__density_pm300", + "1.2870861023455598", + "-0.0721993424973482", + "0.13389937886070555", + "0.16012815380507683" + ], + [ + "2", + "A,0__density_pm100", + "1.2814577673492937", + "-0.071883619963065", + "0.13331384650306113", + "0.1601281538050976" + ], + [ + "3", + "A,0__center_minus_edge", + "1.2151217395298082", + "-0.17378092749409654", + "0.32229044537561385", + "0.408248290463843" + ], + [ + "4", + "A,0__density_pm500", + "1.2149432990479243", + "-0.0681524780688479", + "0.12639414938677265", + "0.16012815380508683" + ], + [ + "5", + "A,0__density_pm75", + "1.2080158234620955", + "-0.06776387999327602", + "0.12567346358624126", + "0.1601281538050804" + ], + [ + "6", + "A,0__density_pm50", + "1.0840493559065434", + "-0.06080995714933015", + "0.11277686484666381", + "0.1601281538050883" + ], + [ + "7", + "A,0__var", + "1.042807624434637", + "-0.07971728435388671", + "0.14784199537329196", + "0.21821789023599458" + ], + [ + "8", + "A,0__mean", + "1.0407913617734987", + "-0.07956315143413832", + "0.14755614370896333", + "0.21821789023599472" + ], + [ + "9", + "A,0__mod_fraction", + "1.0220006283904153", + "-0.07812669642439393", + "0.14489212452384143", + "0.2182178902359909" + ], + [ + "10", + "A,0__density_minus150", + "0.9166853081717443", + "-0.051421638697189846", + "0.09536548732152554", + "0.16012815380509796" + ], + [ + "11", + "A,0__density_plus150", + "0.8740970250519107", + "-0.04903264076322191", + "0.0909349021057452", + "0.16012815380510037" + ] + ], + "shape": { + "columns": 5, + "rows": 12 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
feature_nameeffect_scoremin_group_meanmax_group_meanoverall_std
0pooled__pca_01.355537-0.1938620.3595330.408248
1A,0__density_pm3001.287086-0.0721990.1338990.160128
2A,0__density_pm1001.281458-0.0718840.1333140.160128
3A,0__center_minus_edge1.215122-0.1737810.3222900.408248
4A,0__density_pm5001.214943-0.0681520.1263940.160128
5A,0__density_pm751.208016-0.0677640.1256730.160128
6A,0__density_pm501.084049-0.0608100.1127770.160128
7A,0__var1.042808-0.0797170.1478420.218218
8A,0__mean1.040791-0.0795630.1475560.218218
9A,0__mod_fraction1.022001-0.0781270.1448920.218218
10A,0__density_minus1500.916685-0.0514220.0953650.160128
11A,0__density_plus1500.874097-0.0490330.0909350.160128
\n", + "
" + ], + "text/plain": [ + " feature_name effect_score min_group_mean max_group_mean \\\n", + "0 pooled__pca_0 1.355537 -0.193862 0.359533 \n", + "1 A,0__density_pm300 1.287086 -0.072199 0.133899 \n", + "2 A,0__density_pm100 1.281458 -0.071884 0.133314 \n", + "3 A,0__center_minus_edge 1.215122 -0.173781 0.322290 \n", + "4 A,0__density_pm500 1.214943 -0.068152 0.126394 \n", + "5 A,0__density_pm75 1.208016 -0.067764 0.125673 \n", + "6 A,0__density_pm50 1.084049 -0.060810 0.112777 \n", + "7 A,0__var 1.042808 -0.079717 0.147842 \n", + "8 A,0__mean 1.040791 -0.079563 0.147556 \n", + "9 A,0__mod_fraction 1.022001 -0.078127 0.144892 \n", + "10 A,0__density_minus150 0.916685 -0.051422 0.095365 \n", + "11 A,0__density_plus150 0.874097 -0.049033 0.090935 \n", + "\n", + " overall_std \n", + "0 0.408248 \n", + "1 0.160128 \n", + "2 0.160128 \n", + "3 0.408248 \n", + "4 0.160128 \n", + "5 0.160128 \n", + "6 0.160128 \n", + "7 0.218218 \n", + "8 0.218218 \n", + "9 0.218218 \n", + "10 0.160128 \n", + "11 0.160128 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cluster counts by source\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "row_0", + "rawType": "int64", + "type": "integer" + }, + { + "name": "off_target", + "rawType": "int64", + "type": "integer" + }, + { + "name": "on_target", + "rawType": "int64", + "type": "integer" + } + ], + "ref": "ec714ede-5002-4247-8a53-24b15f394b81", + "rows": [ + [ + "0", + "3942", + "1112" + ], + [ + "1", + "88", + "1061" + ] + ], + "shape": { + "columns": 2, + "rows": 2 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
col_0off_targeton_target
row_0
039421112
1881061
\n", + "
" + ], + "text/plain": [ + "col_0 off_target on_target\n", + "row_0 \n", + "0 3942 1112\n", + "1 88 1061" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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OnUq7r333j77+fnPf46JEydKOtijjjoKr732GvZHsKyYlaTU6/XXX5dt1q9fX/L7V155pWBfDzzwAA444AAp0wMPPBD/+te/3qGr2jfB+lZchrfddlvBNosXL8YJJ5wgZThu3Dj84Ac/6LMfU86lwXp6ySWXYNKkSSgrK8OUKVMkE046nS7YxtTlPQPTpu46br31VhxxxBGoqKiQfu3ss8/GypUrC7Y5+eST+9Tdyy+/fMj94/6Kb33rW33Kj/2VRjKZxJVXXokRI0YgGo1KOvjm5uaCfZjy3bV+ji+WLWHq8a7h+eefx4c//GE0NjZKeT700EMF3zNn04033ojRo0dL/3faaafh7bffLtimra0N559/PiorK2WMzf6yp6dnyGOQ/RFmflIaZly7/42HTF+6B8Hsewb7Bm688Ubr9ttvt77yla9YVVVVfb7PZrPWnDlzrNNOO81auHCh9a9//cuqq6uzrr/+emebtWvXWpFIRPaxbNky684777R8Pp/16KOPOtv88Y9/tILBoHXPPfdYS5cutS699FKrurraam5utvY3pFIpa9u2bQWvz33uc9akSZOsfD4v26xbt44ZKq0nn3yyYLt0Ou3s58UXX5Ry/sEPfiDl/s1vftMKBALWW2+99Q5e3b6FCRMmWLfccktBGfb09Djfd3Z2WvX19db5559vLVmyxPrDH/5glZWVWXfddZezjSnn/vHvf//buuiii6zHHnvMWrNmjfXwww9bo0aNsq655hpnG1OX9wxMm7p7+MAHPmD99re/lef+zTfftD74wQ9a48ePL2gfTjrpJOmr3O0H24yh9I/7M2666SZr9uzZBeW3Y8cO5/vLL7/cGjdunPXUU09Z8+fPt44++mjr2GOPdb435Ts4bN++vaCMn3jiCRk/PPPMM/K9qce7Bj7P3/jGN6y//e1vUp4PPvhgwfe33XabjJsfeugha9GiRdZHPvIRGcclEglnmzPOOMM6+OCDrVdeecWaN2+eNXXqVOtTn/rUkMYg+yvM/KQ0zLh2/xsPmb50z8GQUvsgODgvRUqxU/Z6vVZTU5Pz2S9/+UursrJSyBXiuuuuk4GnG+edd54M+jWOPPJI68orr3Te53I5q7Gx0br11lut/R0kmkaOHCnkSfFEnhOd/vCJT3zCOuusswo+O+qoo6zPf/7ze/R8301g533HHXf0+/0v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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from dimelo import cluster\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "if \"shared_window\" not in globals():\n", + " shared_window = 1000\n", + "\n", + "motif_labels = [\"A,0\", \"CG,0\"]\n", + "\n", + "rw_on_multi = cluster.build_multimotif_read_windows(\n", + " hdf5_file=extract_file,\n", + " motifs=motif_labels,\n", + " regions=ctcf_target_regions,\n", + " window_size=shared_window,\n", + " orientation_aware=True,\n", + " span_full_window=False,\n", + " require_all_motifs=True,\n", + ")\n", + "\n", + "rw_off_multi = cluster.build_multimotif_read_windows(\n", + " hdf5_file=extract_file,\n", + " motifs=motif_labels,\n", + " regions=ctcf_off_target_regions,\n", + " window_size=shared_window,\n", + " orientation_aware=True,\n", + " span_full_window=False,\n", + " require_all_motifs=True,\n", + ")\n", + "\n", + "rw_multi = cluster.merge_read_window_results(\n", + " [rw_on_multi, rw_off_multi],\n", + " source_labels=[\"on_target\", \"off_target\"],\n", + " align=\"error\",\n", + ")\n", + "\n", + "# Default pooled feature scheme. Keep the raw legacy matrix for clustering so the\n", + "# read-cluster association heatmap matches the original tutorial behavior.\n", + "feat_multi, feat_multi_names, feat_multi_table = cluster.read_window_feature_matrix(\n", + " rw_multi,\n", + " n_pca=6,\n", + " use_peak_features=False,\n", + " return_feature_table=True,\n", + ")\n", + "feat_multi_scaled, feat_multi_scale_table = cluster.scale_feature_matrix(\n", + " feat_multi,\n", + " feat_multi_names,\n", + " feature_table=feat_multi_table,\n", + " method=\"standard\",\n", + " family_weighting=\"equal_family\",\n", + ")\n", + "\n", + "# Rich explicit feature scheme used for inspection and feature-salience QC.\n", + "rich_feature_spec = {\n", + " \"motif_mode\": \"per_motif\",\n", + " \"motif_count\": len(motif_labels),\n", + " \"motif_labels\": motif_labels,\n", + " \"pooled\": True,\n", + " \"pca\": {\"enabled\": True, \"n_components\": 6, \"scope\": \"pooled\"},\n", + " \"densities\": {\"enabled\": True},\n", + " \"asymmetry\": {\"enabled\": True, \"spans\": [100, 300, 500]},\n", + " \"center_edge\": {\"enabled\": True, \"center_bp\": 100, \"edge_bp\": 200},\n", + " \"autocorr\": {\"enabled\": True, \"lags\": [20, 75, 150, 250]},\n", + " \"fft\": {\"enabled\": True, \"periods_bp\": [10, 50, 100, 150, 200]},\n", + " \"cross_motif\": {\"enabled\": False},\n", + " \"peaks\": {\"enabled\": False},\n", + "}\n", + "feat_multi_rich_scaled, feat_multi_rich_names, feat_multi_rich_table, feat_multi_rich_scale_table = cluster.read_window_feature_matrix(\n", + " rw_multi,\n", + " feature_spec=rich_feature_spec,\n", + " scale_features=True,\n", + " scaling_method=\"standard\",\n", + " family_weighting=\"equal_family\",\n", + " return_feature_table=True,\n", + " return_scale_table=True,\n", + ")\n", + "feat_multi_rich = feat_multi_rich_scaled\n", + "\n", + "sample_labels_multi = np.array([m.get(\"source_label\", \"unknown\") for m in rw_multi.metadata])\n", + "\n", + "summary_default = cluster.summarize_feature_matrix(\n", + " feat_multi,\n", + " feat_multi_names,\n", + " feature_table=feat_multi_table,\n", + " labels=sample_labels_multi,\n", + ")\n", + "summary_rich = cluster.summarize_feature_matrix(\n", + " feat_multi_rich,\n", + " feat_multi_rich_names,\n", + " feature_table=feat_multi_rich_table,\n", + " labels=sample_labels_multi,\n", + ")\n", + "ranked_rich_features = cluster.rank_read_features_by_group_difference(\n", + " feat_multi_rich,\n", + " feat_multi_rich_names,\n", + " sample_labels_multi,\n", + " top_n=12,\n", + ")\n", + "\n", + "clust_multi = cluster.cluster_read_windows(\n", + " feat_multi,\n", + " method=\"kmeans\",\n", + " n_clusters=2,\n", + " random_state=42,\n", + ")\n", + "\n", + "cluster.plot_cluster_profiles(\n", + " data_matrix=rw_multi.data_matrix,\n", + " labels=clust_multi.labels_size_ordered,\n", + " val_matrix=rw_multi.val_matrix,\n", + " metadata=rw_multi.metadata,\n", + " window_size=shared_window,\n", + " motif_count=len(motif_labels),\n", + " motif_labels=motif_labels,\n", + " plot_all_motifs=True,\n", + " color_points_by=\"motif\",\n", + " motif_profile_mode=\"single_axis\",\n", + " point_size=plot_point_size,\n", + " point_alpha=plot_point_alpha,\n", + " smoothing=profile_smoothing,\n", + " show_unsmoothed_overlay=show_unsmoothed_overlay,\n", + ")\n", + "\n", + "table_multi = pd.crosstab(clust_multi.labels_size_ordered, sample_labels_multi)\n", + "purity_multi = table_multi.max(axis=1).sum() / table_multi.values.sum()\n", + "\n", + "print(f\"Multi-motif reads used: on_target={rw_on_multi.data_matrix.shape[0]}, off_target={rw_off_multi.data_matrix.shape[0]}, total={rw_multi.data_matrix.shape[0]}\")\n", + "print(f\"Default feature matrix: {summary_default['n_reads']} reads x {summary_default['n_features']} features\")\n", + "print(f\"Rich feature matrix: {summary_rich['n_reads']} reads x {summary_rich['n_features']} features\")\n", + "print(\"K-means input for cluster labels: raw default feature matrix (legacy tutorial behavior)\")\n", + "print(\"Scaled default features are still computed for classifier/QC examples below.\")\n", + "print(f\"Multi-motif cluster purity vs on/off labels: {purity_multi:.3f}\")\n", + "print(\"Default feature families\")\n", + "display(summary_default[\"feature_counts_by_family\"])\n", + "print(\"Rich feature families\")\n", + "display(summary_rich[\"feature_counts_by_family\"])\n", + "print(\"Rich features by motif\")\n", + "display(summary_rich[\"feature_counts_by_motif\"])\n", + "print(\"Default feature scaling preview\")\n", + "display(feat_multi_scale_table.head(8))\n", + "print(\"Rich feature scaling preview\")\n", + "display(feat_multi_rich_scale_table.head(8))\n", + "print(\"Rich features most associated with on/off labels (QC, not proof)\")\n", + "display(ranked_rich_features)\n", + "print(\"Cluster counts by source\")\n", + "display(table_multi)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Read Classification\n", + "\n", + "The next cells ask a supervised question: given the engineered read-feature matrix, can a classifier distinguish reads from the on-target and off-target region sets? This is not meant to replace visual inspection or clustering. It is a QC layer that checks whether the feature representation carries separable signal, reports held-out train/test metrics, plots confusion matrices, and then shows the read profiles behind true/false positives and negatives. The examples use the same scaled feature matrices used for k-means so distance- and model-based steps see comparable feature weighting." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Single Motif" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'test': {'accuracy': 0.8364222401289283, 'roc_auc': 0.8979606970708195, 'confusion_matrix': [[710, 96], [107, 328]]}, 'train': {'accuracy': 0.8409915356711004, 'roc_auc': 0.891370139660258, 'confusion_matrix': [[2878, 346], [443, 1295]]}}\n" + ] + }, + { + "data": { + "image/png": "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/ed//qf33HPP8fWhDP73f//X3+b+++/3ksmk9z//8z9cLueee66XSqW8Z5991huJGEl1WA3OPPNM77777vPefPNNrrv3vOc93pQpU5gfAT7/+c978+bN8+655x7vscce8w488EB+Kcq5V8YqKialFC+//LL3xz/+kV/4ezRjpD5E6gUMHBuBtKnXIEMHP40OlH+jnycmF5hkpLOZhiEvhvJ4w32PONSf0EFf2Oj3YSlkhoh8QXk1ysSwkr7LESS1HzsUI5G13xyp91OlpMlIhxL7jd5HV7NoUS+M1PnEiSeeqGK10Ovee+/1t1mwYAELFFpbW3mSjsl7Oh0ec2H7+fPns6Bh66239n7+858XHOvKK6/kST222W+//byHHnrIa0Q8/vjj3v777++NHz/ea2lp8XbaaSfve9/7ntfb2xva7umnn/YOOuggJmpmz57tff/73y/Y169//Wtv++2352veZZddvNtvv90byRgpdVgNjjvuOG/mzJl8bahPvH/ttdf873t6erz/+q//8iZOnMgE5Ic//GFv2bJloX2Uc6+MRcTwn+FWazU6kBli/PjxtGHDBpbXjXWwvJ79ogqNieuFUplcXLah4UUpb696+vZUAg4LYT+KyjKbDSq8NJvhEJehOJ7D8IDDZ01f6FKSF4ca1gNDZcY9WHDYjwlDH011q33yYOpBy0azxFb6W3aQGwFtoBJUmz1zpIItBHLo/3C9jdv/1TsLYKVw8wkHBweHaFT1FFm8eDFdc801nKEAmQfsV62xZMkS+sQnPkGTJ09mI7zddtuNHnvssdAD59vf/jbHcuJ7+Fq9+uqroX2sXbuWPv7xjzOhBOOxT3/60xwDOqqzuNUxjt73Chgig1MhFDL+gC8qi6DL5Da8ZA9nDIxob+qRNZgBay0yOQ51dircG2x2PUIm4A7VoZTflUMYIO9AZYyULIGavXE01a1meGViahD1AC9HXZyqFCjP0dwnNmKSknqNBXGtTEw22DWjfesYuFqfPgcHBweHBs++B/OuD37wg5x5ACkxd911V84ygM5/r732qunJrVu3jt7+9rfTIYccQn/961/ZfA6Ek5rDAZdccgldccUVdMMNN9BWW21F55xzDqdofOGFF6ilpYW3ASGFrIEwEoPZ3Mknn0ynnHIK3XTTTTQaAYIAD2SehMcSQ5opq17QFetowsKjGNQ4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g9ODhx56yJse1fYgTAWnQaTLs88+m3eZiqIolUQjpRRFURRFURRFUWoApM0dcsghtNVWW9E222xDv//977m63mGHHcbfT548mVZeeWX2fQInnngi7bzzznTllVfSXnvtRbfddhu98MILdM011/D3sCY56aST6He/+x2tvfbaLFKdffbZnG6z77779uq+KopSH6goVYMgXHbKlCk5w2YVbedaQvuytnN/QfuytnN/QvuztnF/oT/25UmTJtFXX31F55xzDhuRI8VuxowZnlE5fJ1gPeLYfvvt6ZZbbqGzzjqLfvvb37LwhMp7G20k1czAaaedxsLWUUcdxf5QO+64Iy+zubm5btu5HLQdtB20L5RHxHSnLqmiKIqiKIqiKIqiKIqilIF6SimKoiiKoiiKoiiKoig9jopSiqIoiqIoiqIoiqIoSo+jopSiKIqiKIqiKIqiKIrS46gopSiKoiiKoiiKoiiKovQ4Kkr1IS688EKukNHS0kJDhw7NOQ0qaqCcK6YZMWIEnXrqqZRMJkPTPPbYY7TFFltwBYi11lqLbrjhhqzlTJs2jVZbbTWuqjF27Fh67rnnqB5BW6EUbq7X888/z9N8+OGHOb9/5plnQsu68847ab311uM23XjjjemBBx7opb3qm6C/ZbbhJZdcEppmzpw5tNNOO3EbrrrqqnTZZZdlLUfbOTfop0cccQSXch4wYACtueaaXAmns7MzNI325eqg59TyQdnyrbfemgYPHszXNZQgnzt3bmiaXXbZJavvHnPMMSVfH+uVc889N6v9cL1ytLe303HHHUfLL788DRo0iPbbbz/64osvQsvQ9i3vOocX2hZoPy6P//73v7T33nvTqFGjuD1ROS4IajahEt1KK63E178JEybQO++8E5rm22+/pYMOOohaW1v5HhvXy7a2tpLvQeoRHZ/kRu9r6+9+SK+lVQTV95S+wTnnnGOuuuoqc/LJJ5shQ4ZkfZ9MJs1GG21kJkyYYF5++WXzwAMPmOHDh5szzjjDm+b99983LS0tvIw33njDTJ061cRiMTNjxgxvmttuu800Njaa6667zrz++uvmyCOPNEOHDjVffPGFqTc6OjrM559/Hnr9/Oc/N6uvvrpJp9M8zQcffIAKlWbmzJmh6To7O73lPPXUU9zOl112Gbf7WWedZRoaGsyrr77ai3vXtxgzZow5//zz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oosuqmj7ff311zRixAj65JNPKrCFiqIoiqIoSqmoKKUoitIHOPTQQ1mIwauhoYGFme9///t03XXXUTqdDk37+eef0x577FHUcmtJ3HnzzTfpvPPOo7/+9a8l7WOt8vzzz9NRRx1F/UFI3Gyzzaq6jldeeYUeeOABOuGEEyq63OHDh9PkyZNpypQpFV2uoiiKoiiKUhwqSimKovQRJk6cyGLMhx9+SP/+979p1113pRNPPJF+8IMfUDKZ9KZbccUVqampifob7733Hv+/zz775N3Hzs7OXtiy6qx/hRVWoJaWlootr9Yp1LZTp06ln/zkJzRo0KCKr/ewww6jm2++mb799tuKL1tRFEVRFEUpjIpSiqIofQSIMBBjVl55Zdpiiy3ot7/9Ld17770sUN1www05o58wkD/++ONppZVWoubmZhozZgxdfPHFXnoT+NGPfsTzuPcQfyD8IBoLg/ytt96aZs6cGdoWTItUqcMPP5wGDx5Mo0ePpmuuuSY0DVKeDjjgAFpuueVo4MCBtNVWW9Gzzz7rfY9tx35gu9ZYYw2OggqKa5nRNnvvvTf/HY1GeXtdBNm+++5LF154IY0aNYrWXXdd/vzVV1+l733vezRgwABafvnlOeKora3NW56bD/uA/Rw6dCidf/75vP5TTz2Vt3mVVVah66+/vuAx2WWXXbh9TzrpJI6q2X333fnz1157jSO50H5YPlLLkArmWLx4MR100EHcLjg2V199NS8Ly8mXfjZv3jw+Llhma2sr7b///vTFF19kRST94x//4HmHDBlCP/3pT3ldhXjqqad43RDAhg0bxvvw3Xff8XeIwkN/WX311bktN910U/q///s/b97HHnuMj8XDDz/MxxfL2H777Wnu3Ln8PfoljisimVykn+urCxYsoJ///OcsvmF/cLwwXeb+/O///i+vH/0kF6lUirfJ9Y8g2Hf0QbQzfjfTpk0LfY/tmT59Oh8r7B/6YXD/wIYbbsh96+677y7YjoqiKIqiKErlUVFKURSlD4OBPISCu+66K+f3f/zjH+m+++6jO+64g4UCRHw48QnpYQDCCyKw3HuIN3vuuScLDS+//DJHaGHAD1EkyJVXXslCBKb5xS9+Qccee6wnRmAZO++8M3366ae8fogNp512mpdq+MQTT3BaFCK93njjDU7Jg1gBcSkXv/71rz2BCNuKlwPbifU+9NBDdP/999OSJUtYWIHAgn268847WVSDeBTkkUceoc8++4z++9//0lVXXcUpWog6w3wQz4455hg6+uiju/QTuvHGG6mxsZHFnb/85S8stuC4bL755vTCCy/QjBkzWDyCiOQ4+eSTeXq0DbYb7fHSSy/lXQfaDYIUonUef/xxnuf999+nSZMmhaaDoAhBEu2AF6a95JJL8i539uzZNH78eNpggw1o1qxZ9OSTT/KxhtADIEj9/e9/5/16/fXX6Ve/+hUdfPDBvNwgZ555JvcH7G88HmexEmD7TjnlFBZ23HFz24zIpi+//JJF1RdffJEFSmxLMCLp3XffpX/+85/cv7GtuZgzZw4tXLiQ+2Iml19+Of8+0Ed/85vfcH9D2wU5++yzab/99uM+CqEQQh5SRYNss802fIwURVEURVGUHsYoiqIovc4hhxxi9tlnn5zfTZo0yay//vree5y67777bv77l7/8pfne975n0ul0znmD0xZiww03NFOnTvXejxkzxhx88MHeeyx/xIgRZvr06fz+r3/9qxk8eLD55ptvci5v/Pjx5qKLLgp99o9//MOstNJKebcB25l5WUK7jBw50nR0dHifXXPNNWbYsGGmra3N++xf//qXiUajZv78+d582IdUKuVNs+6665qddtrJe59MJs3AgQPNrbfemnebdt55Z7P55puHPrvgggvMbrvtFvrs448/5m2fO3euWbRokWloaDB33nmn9/2CBQtMS0uLOfHEE73PsH1XX301//2f//zHxGIxM2/ePO/7119/nZf53HPP8fspU6bwMrB8x6mnnmrGjh2bd/sPOOAAs8MOO+T8rr29nZf39NNPhz4/4ogjeD7w6KOP8jbMnDkz1Nb4bNmyZd52bbrppqFlPPHEE6a1tZXXEWTNNdfkvuPmQzt9+eWXphDoF2ibzD6O9ps4cWLWb2WPPfbw3mM7jznmmNA0aK9jjz029NmvfvUrs8suuxTcDkVRFEVRFKXyxHtaBFMURVFKA2Nrl86WCdLUYIiOtDZEPCESaLfddiu4PEQ5IXXqX//6F0e2IKVt2bJlWZFSm2yyifc31o/UQkS+AES1IFIIaXC5QFQKIoWCkVGIzmlvb6elS5eW5KW08cYbc6SSA1EuiI5BypZjhx124GgjRFQhnQ4gegepgA58vtFGG3nvY7EYp/65fcrHlltumbVvjz76aE5/I0QyoS0TiQRH3ziQaudSD3OBfVp11VX55UB0E9IO8R1SLAGi4JBO6UBqYKHtx3FCxFIuEKWEY4H+EwQpoTi2+foC1gmwXqR15gJthH6G9g2CtnHeYQDppkjvKwTmQWprrt/Adtttl/U+syJfrmkyo7KQ2oe2UBRFURRFUXoWFaUURVH6OBAl4LmTC6REffDBB5wihRQ2pJBNmDAhyzcnM1UOKU5XXHEFrbXWWjwg//GPf5xlNI0qgEEgCrj0PMxTCAgS8Br6n//5n6zv8nkH5SMoPpVCru0vtE/Frh/7hhS4Sy+9NGtaCDYQe6pFqdtf6Dg5Dy6Ik/BjCpJpMh9crxOHCq0Xy0ZbwJMqEwhtpRxbeHlBMEL/DIqTlQQphV2JY4qiKIqiKErlUU8pRVGUPgx8kWDqDU+cfMBEGj4+1157Ld1+++3s0eN8eyAmOP8gByKYEGEFA3REISECChX/SgGRM4g2yVexDGIZopYgemW+gtFL5bD++utzJA68pYL7hOUWikaqFNg3+C8hailz3yCywEwb7e48vAA8kd5+++2C+/Txxx/zywEvLvhXIWKqXHCc4MmVCywX4hMi5DL3Ixix1RUQijL7GNpo/vz57D+VuWyITKUAM3TXHpk888wzWe/RlqVOA+P6zOgwRVEURVEUpfqoKKUoitJH6Ojo4IE8zMNhio3KcTC/RkoeTMNzAQPvW2+9ld566y0WPWD6DZHJRaNAOIEogeW6imtrr722ZywNcefAAw/sMlooE1Q8w3pQ4Q6CEEy5IYbBTBucc845bKCNaCkIOIj2uu222+iss87qdjvBrBrRVocccgiLCUil++Uvf8kV8FzqXjU57rjjWIxDG0B4Qjragw8+SIcddhiLM0ivw7ahyh+2Dft/xBFHhKoKZoLoNgiE2Dcc++eee46POczkcxl8F8sZZ5zB2wijehiGo5+gGh0qBWI7ETUHc3OYuWM/sO6pU6fy+2JBH0O0HvoTlot+jP1Bmhz6x3/+8x8WPZ9++mk2TIdZeikgggkiF0zaM0Hfu+yyy7jvo/Ie+j/MzoPgs+uuu46ngdk92jZoio8oLBixd5X2qiiKoiiKolQeFaUURVH6CKjihpQnDPLhDwVBA9X17r33XvY/ygWEBQzKIVzAdwiD/wceeMCLRkLFNKTqIfLFRYJAyEIFuu23357T0FDJDoP+UkB0DMSGESNGcCU/CCqoAue2E8tEdThMg+3adttt6eqrr2YPoe4CPyqIQBCGsGykHqKq25/+9CfqCUaNGsViCAQoCBnY95NOOomFQNfuaGOIMhAUIdDA8wrROflSFyFW4TjjuIwbN47nQcQVIt+6wzrrrMPHAOIjPK6wTVgPIpjABRdcwNXpUIUP24d+h3S+fOmiuUAUH+bbddddWUCCSIr9QT/EvkCsw3ag6t1HH31UlnD485//nCtLZoLKfxC50Ld/97vfcbuj7wWBMApBFFFjEEqxfcHoM7QHvLF22mmnkrdLURRFURRF6R4RuJ13cxmKoiiKohQAqYbwbYJIiKgppTRgdo7UTIh0mcblhYA4dvfdd3PEVj4gmJ5wwgkcMagoiqIoiqL0LGp0riiKoigV5uWXX+ZUOUQnwU/q/PPP58+RjqmUDgzbEeWE9MBKguXBjB+pmIqiKIqiKErPo6KUoiiKolQBVDeE2TtSHbfcckt64oknSjb5Vnx22WWXijcHjsdpp52mzawoiqIoitJLaPqeoiiKoiiKoiiKoiiK0uOo0bmiKIqiKIqiKIqiKIrS46gopSiKoiiKoiiKoiiKovQ4KkopiqIoiqIoiqIoiqIoPY6KUoqiKIqiKIqiKIqiKEqPo6KUoiiKoiiKoiiKoiiK0uOoKKUoiqIoiqIoiqIoiqL0OCpKKYqiKIqiKIqiKIqiKD2OilKKoiiKoiiKoiiKoigK9TT/H33q56rLxxG0AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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/ed//qf33HPP8fWhDP73f//X3+b+++/3ksmk9z//8z9cLueee66XSqW8Z5991huJGEl1WA3OPPNM77777vPefPNNrrv3vOc93pQpU5gfAT7/+c978+bN8+655x7vscce8w488EB+Kcq5V8YqKialFC+//LL3xz/+kV/4ezRjpD5E6gUMHBuBtKnXIEMHP40OlH+jnycmF5hkpLOZhiEvhvJ4w32PONSf0EFf2Oj3YSlkhoh8QXk1ysSwkr7LESS1HzsUI5G13xyp91OlpMlIhxL7jd5HV7NoUS+M1PnEiSeeqGK10Ovee+/1t1mwYAELFFpbW3mSjsl7Oh0ec2H7+fPns6Bh66239n7+858XHOvKK6/kST222W+//byHHnrIa0Q8/vjj3v777++NHz/ea2lp8XbaaSfve9/7ntfb2xva7umnn/YOOuggJmpmz57tff/73y/Y169//Wtv++2352veZZddvNtvv90byRgpdVgNjjvuOG/mzJl8bahPvH/ttdf873t6erz/+q//8iZOnMgE5Ic//GFv2bJloX2Uc6+MRcTwn+FWazU6kBli/PjxtGHDBpbXjXWwvJ79ogqNieuFUplcXLah4UUpb696+vZUAg4LYT+KyjKbDSq8NJvhEJehOJ7D8IDDZ01f6FKSF4ca1gNDZcY9WHDYjwlDH011q33yYOpBy0azxFb6W3aQGwFtoBJUmz1zpIItBHLo/3C9jdv/1TsLYKVw8wkHBweHaFT1FFm8eDFdc801nKEAmQfsV62xZMkS+sQnPkGTJ09mI7zddtuNHnvssdAD59vf/jbHcuJ7+Fq9+uqroX2sXbuWPv7xjzOhBOOxT3/60xwDOqqzuNUxjt73Chgig1MhFDL+gC8qi6DL5Da8ZA9nDIxob+qRNZgBay0yOQ51dircG2x2PUIm4A7VoZTflUMYIO9AZYyULIGavXE01a1meGViahD1AC9HXZyqFCjP0dwnNmKSknqNBXGtTEw22DWjfesYuFqfPgcHBweHBs++B/OuD37wg5x5ACkxd911V84ygM5/r732qunJrVu3jt7+9rfTIYccQn/961/ZfA6Ek5rDAZdccgldccUVdMMNN9BWW21F55xzDqdofOGFF6ilpYW3ASGFrIEwEoPZ3Mknn0ynnHIK3XTTTTQaAYIAD2SehMcSQ5opq17QFetowsKjGNQ4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g9ODhx56yJse1fYgTAWnQaTLs88+m3eZiqIolUQjpRRFURRFURRFUWoApM0dcsghtNVWW9E222xDv//977m63mGHHcbfT548mVZeeWX2fQInnngi7bzzznTllVfSXnvtRbfddhu98MILdM011/D3sCY56aST6He/+x2tvfbaLFKdffbZnG6z77779uq+KopSH6goVYMgXHbKlCk5w2YVbedaQvuytnN/QfuytnN/QvuztnF/oT/25UmTJtFXX31F55xzDhuRI8VuxowZnlE5fJ1gPeLYfvvt6ZZbbqGzzjqLfvvb37LwhMp7G20k1czAaaedxsLWUUcdxf5QO+64Iy+zubm5btu5HLQdtB20L5RHxHSnLqmiKIqiKIqiKIqiKIqilIF6SimKoiiKoiiKoiiKoig9jopSiqIoiqIoiqIoiqIoSo+jopSiKIqiKIqiKIqiKIrS46gopSiKoiiKoiiKoiiKovQ4Kkr1IS688EKukNHS0kJDhw7NOQ0qaqCcK6YZMWIEnXrqqZRMJkPTPPbYY7TFFltwBYi11lqLbrjhhqzlTJs2jVZbbTWuqjF27Fh67rnnqB5BW6EUbq7X888/z9N8+OGHOb9/5plnQsu68847ab311uM23XjjjemBBx7opb3qm6C/ZbbhJZdcEppmzpw5tNNOO3EbrrrqqnTZZZdlLUfbOTfop0cccQSXch4wYACtueaaXAmns7MzNI325eqg59TyQdnyrbfemgYPHszXNZQgnzt3bmiaXXbZJavvHnPMMSVfH+uVc889N6v9cL1ytLe303HHHUfLL788DRo0iPbbbz/64osvQsvQ9i3vOocX2hZoPy6P//73v7T33nvTqFGjuD1ROS4IajahEt1KK63E178JEybQO++8E5rm22+/pYMOOohaW1v5HhvXy7a2tpLvQeoRHZ/kRu9r6+9+SK+lVQTV95S+wTnnnGOuuuoqc/LJJ5shQ4ZkfZ9MJs1GG21kJkyYYF5++WXzwAMPmOHDh5szzjjDm+b99983LS0tvIw33njDTJ061cRiMTNjxgxvmttuu800Njaa6667zrz++uvmyCOPNEOHDjVffPGFqTc6OjrM559/Hnr9/Oc/N6uvvrpJp9M8zQcffIAKlWbmzJmh6To7O73lPPXUU9zOl112Gbf7WWedZRoaGsyrr77ai3vXtxgzZow5//zz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oosuqmj7ff311zRixAj65JNPKrCFiqIoiqIoSqmoKKUoitIHOPTQQ1mIwauhoYGFme9///t03XXXUTqdDk37+eef0x577FHUcmtJ3HnzzTfpvPPOo7/+9a8l7WOt8vzzz9NRRx1F/UFI3Gyzzaq6jldeeYUeeOABOuGEEyq63OHDh9PkyZNpypQpFV2uoiiKoiiKUhwqSimKovQRJk6cyGLMhx9+SP/+979p1113pRNPPJF+8IMfUDKZ9KZbccUVqampifob7733Hv+/zz775N3Hzs7OXtiy6qx/hRVWoJaWlootr9Yp1LZTp06ln/zkJzRo0KCKr/ewww6jm2++mb799tuKL1tRFEVRFEUpjIpSiqIofQSIMBBjVl55Zdpiiy3ot7/9Ld17770sUN1www05o58wkD/++ONppZVWoubmZhozZgxdfPHFXnoT+NGPfsTzuPcQfyD8IBoLg/ytt96aZs6cGdoWTItUqcMPP5wGDx5Mo0ePpmuuuSY0DVKeDjjgAFpuueVo4MCBtNVWW9Gzzz7rfY9tx35gu9ZYYw2OggqKa5nRNnvvvTf/HY1GeXtdBNm+++5LF154IY0aNYrWXXdd/vzVV1+l733vezRgwABafvnlOeKora3NW56bD/uA/Rw6dCidf/75vP5TTz2Vt3mVVVah66+/vuAx2WWXXbh9TzrpJI6q2X333fnz1157jSO50H5YPlLLkArmWLx4MR100EHcLjg2V199NS8Ly8mXfjZv3jw+Llhma2sr7b///vTFF19kRST94x//4HmHDBlCP/3pT3ldhXjqqad43RDAhg0bxvvw3Xff8XeIwkN/WX311bktN910U/q///s/b97HHnuMj8XDDz/MxxfL2H777Wnu3Ln8PfoljisimVykn+urCxYsoJ///OcsvmF/cLwwXeb+/O///i+vH/0kF6lUirfJ9Y8g2Hf0QbQzfjfTpk0LfY/tmT59Oh8r7B/6YXD/wIYbbsh96+677y7YjoqiKIqiKErlUVFKURSlD4OBPISCu+66K+f3f/zjH+m+++6jO+64g4UCRHw48QnpYQDCCyKw3HuIN3vuuScLDS+//DJHaGHAD1EkyJVXXslCBKb5xS9+Qccee6wnRmAZO++8M3366ae8fogNp512mpdq+MQTT3BaFCK93njjDU7Jg1gBcSkXv/71rz2BCNuKlwPbifU+9NBDdP/999OSJUtYWIHAgn268847WVSDeBTkkUceoc8++4z++9//0lVXXcUpWog6w3wQz4455hg6+uiju/QTuvHGG6mxsZHFnb/85S8stuC4bL755vTCCy/QjBkzWDyCiOQ4+eSTeXq0DbYb7fHSSy/lXQfaDYIUonUef/xxnuf999+nSZMmhaaDoAhBEu2AF6a95JJL8i539uzZNH78eNpggw1o1qxZ9OSTT/KxhtADIEj9/e9/5/16/fXX6Ve/+hUdfPDBvNwgZ555JvcH7G88HmexEmD7TjnlFBZ23HFz24zIpi+//JJF1RdffJEFSmxLMCLp3XffpX/+85/cv7GtuZgzZw4tXLiQ+2Iml19+Of8+0Ed/85vfcH9D2wU5++yzab/99uM+CqEQQh5SRYNss802fIwURVEURVGUHsYoiqIovc4hhxxi9tlnn5zfTZo0yay//vree5y67777bv77l7/8pfne975n0ul0znmD0xZiww03NFOnTvXejxkzxhx88MHeeyx/xIgRZvr06fz+r3/9qxk8eLD55ptvci5v/Pjx5qKLLgp99o9//MOstNJKebcB25l5WUK7jBw50nR0dHifXXPNNWbYsGGmra3N++xf//qXiUajZv78+d582IdUKuVNs+6665qddtrJe59MJs3AgQPNrbfemnebdt55Z7P55puHPrvgggvMbrvtFvrs448/5m2fO3euWbRokWloaDB33nmn9/2CBQtMS0uLOfHEE73PsH1XX301//2f//zHxGIxM2/ePO/7119/nZf53HPP8fspU6bwMrB8x6mnnmrGjh2bd/sPOOAAs8MOO+T8rr29nZf39NNPhz4/4ogjeD7w6KOP8jbMnDkz1Nb4bNmyZd52bbrppqFlPPHEE6a1tZXXEWTNNdfkvuPmQzt9+eWXphDoF2ibzD6O9ps4cWLWb2WPPfbw3mM7jznmmNA0aK9jjz029NmvfvUrs8suuxTcDkVRFEVRFKXyxHtaBFMURVFKA2Nrl86WCdLUYIiOtDZEPCESaLfddiu4PEQ5IXXqX//6F0e2IKVt2bJlWZFSm2yyifc31o/UQkS+AES1IFIIaXC5QFQKIoWCkVGIzmlvb6elS5eW5KW08cYbc6SSA1EuiI5BypZjhx124GgjRFQhnQ4gegepgA58vtFGG3nvY7EYp/65fcrHlltumbVvjz76aE5/I0QyoS0TiQRH3ziQaudSD3OBfVp11VX55UB0E9IO8R1SLAGi4JBO6UBqYKHtx3FCxFIuEKWEY4H+EwQpoTi2+foC1gmwXqR15gJthH6G9g2CtnHeYQDppkjvKwTmQWprrt/Adtttl/U+syJfrmkyo7KQ2oe2UBRFURRFUXoWFaUURVH6OBAl4LmTC6REffDBB5wihRQ2pJBNmDAhyzcnM1UOKU5XXHEFrbXWWjwg//GPf5xlNI0qgEEgCrj0PMxTCAgS8Br6n//5n6zv8nkH5SMoPpVCru0vtE/Frh/7hhS4Sy+9NGtaCDYQe6pFqdtf6Dg5Dy6Ik/BjCpJpMh9crxOHCq0Xy0ZbwJMqEwhtpRxbeHlBMEL/DIqTlQQphV2JY4qiKIqiKErlUU8pRVGUPgx8kWDqDU+cfMBEGj4+1157Ld1+++3s0eN8eyAmOP8gByKYEGEFA3REISECChX/SgGRM4g2yVexDGIZopYgemW+gtFL5bD++utzJA68pYL7hOUWikaqFNg3+C8hailz3yCywEwb7e48vAA8kd5+++2C+/Txxx/zywEvLvhXIWKqXHCc4MmVCywX4hMi5DL3Ixix1RUQijL7GNpo/vz57D+VuWyITKUAM3TXHpk888wzWe/RlqVOA+P6zOgwRVEURVEUpfqoKKUoitJH6Ojo4IE8zMNhio3KcTC/RkoeTMNzAQPvW2+9ld566y0WPWD6DZHJRaNAOIEogeW6imtrr722ZywNcefAAw/sMlooE1Q8w3pQ4Q6CEEy5IYbBTBucc845bKCNaCkIOIj2uu222+iss87qdjvBrBrRVocccgiLCUil++Uvf8kV8FzqXjU57rjjWIxDG0B4Qjragw8+SIcddhiLM0ivw7ahyh+2Dft/xBFHhKoKZoLoNgiE2Dcc++eee46POczkcxl8F8sZZ5zB2wijehiGo5+gGh0qBWI7ETUHc3OYuWM/sO6pU6fy+2JBH0O0HvoTlot+jP1Bmhz6x3/+8x8WPZ9++mk2TIdZeikgggkiF0zaM0Hfu+yyy7jvo/Ie+j/MzoPgs+uuu46ngdk92jZoio8oLBixd5X2qiiKoiiKolQeFaUURVH6CKjihpQnDPLhDwVBA9X17r33XvY/ygWEBQzKIVzAdwiD/wceeMCLRkLFNKTqIfLFRYJAyEIFuu23357T0FDJDoP+UkB0DMSGESNGcCU/CCqoAue2E8tEdThMg+3adttt6eqrr2YPoe4CPyqIQBCGsGykHqKq25/+9CfqCUaNGsViCAQoCBnY95NOOomFQNfuaGOIMhAUIdDA8wrROflSFyFW4TjjuIwbN47nQcQVIt+6wzrrrMPHAOIjPK6wTVgPIpjABRdcwNXpUIUP24d+h3S+fOmiuUAUH+bbddddWUCCSIr9QT/EvkCsw3ag6t1HH31UlnD485//nCtLZoLKfxC50Ld/97vfcbuj7wWBMApBFFFjEEqxfcHoM7QHvLF22mmnkrdLURRFURRF6R4RuJ13cxmKoiiKohQAqYbwbYJIiKgppTRgdo7UTIh0mcblhYA4dvfdd3PEVj4gmJ5wwgkcMagoiqIoiqL0LGp0riiKoigV5uWXX+ZUOUQnwU/q/PPP58+RjqmUDgzbEeWE9MBKguXBjB+pmIqiKIqiKErPo6KUoiiKolQBVDeE2TtSHbfcckt64oknSjb5Vnx22WWXijcHjsdpp52mzawoiqIoitJLaPqeoiiKoiiKoiiKoiiK0uOo0bmiKIqiKIqiKIqiKIrS46gopSiKoiiKoiiKoiiKovQ4KkopiqIoiqIoiqIoiqIoPY6KUoqiKIqiKIqiKIqiKEqPo6KUoiiKoiiKoiiKoiiK0uOoKKUoiqIoiqIoiqIoiqL0OCpKKYqiKIqiKIqiKIqiKD2OilKKoiiKoiiKoiiKoigK9TT/H33q56rLxxG0AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sample_labels_single = np.array([m.get(\"source_label\", \"unknown\") for m in rw_single.metadata])\n", + "\n", + "clf_single = cluster.classify_read_features_binary(\n", + " feat_single_scaled,\n", + " sample_labels=sample_labels_single,\n", + " classifier=\"logreg\",\n", + " random_state=42,\n", + ")\n", + "print(clf_single[\"metrics\"])\n", + "cluster.plot_confusion_matrices(clf_single[\"predictions\"])\n", + "\n", + "cluster.plot_classification_profiles(\n", + " data_matrix=rw_single.data_matrix,\n", + " predictions=clf_single[\"predictions\"],\n", + " val_matrix=rw_single.val_matrix,\n", + " metadata=rw_single.metadata,\n", + " split=\"test\",\n", + " group_by=\"confusion\",\n", + " window_size=shared_window,\n", + " point_size=plot_point_size,\n", + " point_alpha=plot_point_alpha,\n", + " smoothing=profile_smoothing,\n", + " show_unsmoothed_overlay=show_unsmoothed_overlay,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Tip*: For concatenated motif windows, use `motif_index` in plotting helpers to choose which motif slice to visualize (e.g., 0 for the first motif, 1 for the second)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Multiple motifs" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'test': {'accuracy': 0.8436744560838034, 'roc_auc': 0.9122101480277229, 'confusion_matrix': [[709, 97], [97, 338]]}, 'train': {'accuracy': 0.847037484885127, 'roc_auc': 0.9066018454799589, 'confusion_matrix': [[2868, 356], [403, 1335]]}}\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sample_labels_multi = np.array([m.get(\"source_label\", \"unknown\") for m in rw_multi.metadata])\n", + "\n", + "clf_multi = cluster.classify_read_features_binary(\n", + " feat_multi_scaled,\n", + " sample_labels=sample_labels_multi,\n", + " classifier=\"logreg\",\n", + " random_state=42,\n", + ")\n", + "print(clf_multi[\"metrics\"])\n", + "cluster.plot_confusion_matrices(clf_multi[\"predictions\"])\n", + "\n", + "cluster.plot_classification_profiles(\n", + " data_matrix=rw_multi.data_matrix,\n", + " predictions=clf_multi[\"predictions\"],\n", + " val_matrix=rw_multi.val_matrix,\n", + " metadata=rw_multi.metadata,\n", + " split=\"test\",\n", + " group_by=\"confusion\",\n", + " window_size=shared_window,\n", + " motif_count=len(motif_labels),\n", + " motif_labels=motif_labels,\n", + " plot_all_motifs=True,\n", + " color_points_by=\"motif\",\n", + " motif_profile_mode=\"single_axis\",\n", + " point_size=plot_point_size,\n", + " point_alpha=plot_point_alpha,\n", + " smoothing=profile_smoothing,\n", + " show_unsmoothed_overlay=show_unsmoothed_overlay,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Read-Cluster to Region Association\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Read-level clustering stays the primary inference step. Here we aggregate those cluster labels back onto regions, then plot the region-by-cluster association matrix. The cluster labels in this section intentionally use the raw default feature matrix to preserve the original tutorial grouping; the richer/scaled feature matrices above are kept as QC tools for exploring alternative feature salience.\n", + "\n", + "Sorting controls:\n", + "- `region_sort_mode` controls row order before the tutorial's final source-bed grouping. It can be one key or a list of keys applied hierarchically. Options are `cluster_fraction`, `input`, `genomic`, and `association_strength`; for example, `[\"cluster_fraction\", \"genomic\"]` groups by dominant cluster/fraction and then uses genomic coordinates as the next tie-breaker.\n", + "- `association_strength_aggregate` only matters when `region_sort_mode=\"association_strength\"`. It defines each region's scalar sorting score from its per-cluster values: `max` uses the strongest cluster association for that region, while `mean` uses the average association across clusters.\n", + "- `cluster_sort_mode` controls heatmap column order. Use `natural` for cluster labels in lexical/numeric-looking order, `input` for the order produced by the association table, `total_association` for columns with the largest summed values first, or `max_association` for columns with the strongest single-region association first. You can also pass `cluster_order=[...]` directly to `plot_read_cluster_region_association_heatmap_matplotlib`.\n", + "- `row_annotation_columns` can plot any number of categorical region labels as separate bars to the left of the heatmap. Here we show source BED and strand so the row grouping remains visible without crowding the y-axis labels." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Association payload metadata\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "plot_family", + "rawType": "str", + "type": "string" + }, + { + "name": "value_mode", + "rawType": "str", + "type": "string" + }, + { + "name": "source_format", + "rawType": "str", + "type": "string" + }, + { + "name": "region_order", + "rawType": "object", + "type": "unknown" + }, + { + "name": "cluster_order", + "rawType": "object", + "type": "unknown" + }, + { + "name": "top_n_regions_per_cluster", + "rawType": "int64", + "type": "integer" + }, + { + "name": "has_top_regions_table", + "rawType": "bool", + "type": "boolean" + }, + { + "name": "region_sort", + "rawType": "str", + "type": "string" + }, + { + "name": "association_strength_aggregate", + "rawType": "str", + "type": "string" + }, + { + "name": "cluster_sort", + "rawType": "str", + "type": "string" + }, + { + "name": "chromosome_blocks", + "rawType": "object", + "type": "unknown" + } + ], + "ref": "ed596b6c-f84a-4380-92cd-d9656efcbf0a", + "rows": [ + [ + "0", + "read_cluster_region_association_heatmap", + "fraction", + "legacy_wide", + "['chr1:9168068-9168218:+', 'chr1:9168135-9168218:+', 'chr1:9168136-9168218:-', 'chr1:9169918-9170236:+', 'chr1:9169985-9170303:+', 'chr1:9169986-9170302:-', 'chr1:9172003-9172086:+', 'chr1:9172003-9172152:-', 'chr1:9172003-9172153:+', 'chr1:114356586-114356736:+', 'chr1:114356587-114356736:-', 'chr1:114358436-114358754:+', 'chr1:114358437-114358753:-', 'chr1:114360454-114360603:-', 'chr1:114360454-114360604:+', 'chr1:150101499-150101649:+', 'chr1:150101534-150101649:-', 'chr1:150101535-150101649:+', 'chr1:150103349-150103667:+', 'chr1:150103384-150103702:-', 'chr1:150103385-150103701:+', 'chr1:150105402-150105517:+', 'chr1:150105402-150105551:+', 'chr1:150105402-150105552:-', 'chr1:224661531-224661681:-', 'chr1:224661583-224661681:+', 'chr1:224661584-224661681:-', 'chr1:224663381-224663697:-', 'chr1:224663433-224663751:+', 'chr1:224663434-224663750:-', 'chr1:224665451-224665547:-', 'chr1:224665451-224665600:-', 'chr1:224665451-224665601:+', 'chr2:44482827-44482977:+', 'chr2:44484677-44484995:+', 'chr2:44486695-44486845:+', 'chr3:48462628-48462778:-', 'chr3:48462688-48462778:-', 'chr3:48462689-48462778:+', 'chr3:48464478-48464796:-', 'chr3:48464538-48464856:-', 'chr3:48464539-48464855:+', 'chr3:48466556-48466646:-', 'chr3:48466556-48466705:+', 'chr3:48466556-48466706:-', 'chr5:111666908-111667058:+', 'chr5:111668758-111669076:+', 'chr5:111670776-111670926:+', 'chr5:138876711-138876861:+', 'chr5:138878561-138878879:+', 'chr5:138880579-138880729:+', 'chr6:15561907-15562057:+', 'chr6:15561908-15562057:-', 'chr6:15561985-15562057:+', 'chr6:15561986-15562057:-', 'chr6:15563757-15564075:+', 'chr6:15563758-15564074:-', 'chr6:15563835-15564153:+', 'chr6:15563836-15564152:-', 'chr6:15565853-15565924:-', 'chr6:15565853-15565925:+', 'chr6:15565853-15566002:-', 'chr6:15565853-15566003:+', 'chr6:41196591-41196741:-', 'chr6:41196592-41196741:+', 'chr6:41196657-41196741:+', 'chr6:41196658-41196741:-', 'chr6:41198441-41198759:-', 'chr6:41198442-41198758:+', 'chr6:41198507-41198825:+', 'chr6:41198508-41198824:-', 'chr6:41200525-41200608:+', 'chr6:41200525-41200609:-', 'chr6:41200525-41200674:-', 'chr6:41200525-41200675:+', 'chr6:53009844-53009994:+', 'chr6:53011694-53012012:+', 'chr6:53013712-53013862:+', 'chr6:73588139-73588289:-', 'chr6:73588200-73588289:+', 'chr6:73589989-73590307:-', 'chr6:73590050-73590368:+', 'chr6:73592068-73592157:-', 'chr6:73592068-73592218:+', 'chr6:155875926-155876076:-', 'chr6:155876024-155876076:+', 'chr6:155876025-155876076:-', 'chr6:155877776-155878092:-', 'chr6:155877874-155878192:+', 'chr6:155877875-155878191:-', 'chr6:155879892-155879942:-', 'chr6:155879892-155880041:-', 'chr6:155879892-155880042:+', 'chr7:152343785-152343935:-', 'chr7:152343786-152343935:+', 'chr7:152343847-152343935:-', 'chr7:152343848-152343935:+', 'chr7:152345635-152345953:-', 'chr7:152345636-152345952:+', 'chr7:152345697-152346015:-', 'chr7:152345698-152346014:+', 'chr7:152347715-152347802:+', 'chr7:152347715-152347803:-', 'chr7:152347715-152347864:+', 'chr7:152347715-152347865:-', 'chr8:22846344-22846494:+', 'chr8:22846345-22846494:-', 'chr8:22846428-22846494:+', 'chr8:22848194-22848512:+', 'chr8:22848195-22848511:-', 'chr8:22848278-22848596:+', 'chr8:22850296-22850361:-', 'chr8:22850296-22850362:+', 'chr8:22850296-22850446:+', 'chr9:145234660-145234810:+', 'chr9:145234661-145234810:-', 'chr9:145234697-145234810:-', 'chr9:145236510-145236828:+', 'chr9:145236511-145236827:-', 'chr9:145236547-145236865:-', 'chr9:145238565-145238677:-', 'chr9:145238565-145238678:+', 'chr9:145238565-145238715:-', 'chr12:57871837-57871987:-', 'chr12:57871885-57871987:-', 'chr12:57873687-57874005:-', 'chr12:57873735-57874053:-', 'chr12:57875753-57875855:-', 'chr12:57875753-57875903:-', 'chr14:17377273-17377423:+', 'chr14:17377274-17377423:-', 'chr14:17377341-17377423:+', 'chr14:17379123-17379441:+', 'chr14:17379124-17379440:-', 'chr14:17379191-17379509:+', 'chr14:17381209-17381290:-', 'chr14:17381209-17381291:+', 'chr14:17381209-17381359:+', 'chr14:44123158-44123308:+', 'chr14:44123159-44123308:-', 'chr14:44125008-44125326:+', 'chr14:44125009-44125325:-', 'chr14:44127026-44127175:-', 'chr14:44127026-44127176:+', 'chr15:38664850-38665000:-', 'chr15:38664851-38665000:+', 'chr15:38664892-38665000:-', 'chr15:38664893-38665000:+', 'chr15:38666700-38667018:-', 'chr15:38666701-38667017:+', 'chr15:38666742-38667060:-', 'chr15:38666743-38667059:+', 'chr15:38668760-38668867:+', 'chr15:38668760-38668868:-', 'chr15:38668760-38668909:+', 'chr15:38668760-38668910:-', 'chr15:54632158-54632308:+', 'chr15:54632159-54632308:-', 'chr15:54632192-54632308:-', 'chr15:54632193-54632308:+', 'chr15:54634008-54634326:+', 'chr15:54634009-54634325:-', 'chr15:54634042-54634360:-', 'chr15:54634043-54634359:+', 'chr15:54636060-54636175:-', 'chr15:54636060-54636176:+', 'chr15:54636060-54636209:+', 'chr15:54636060-54636210:-', 'chr16:4279474-4279624:+', 'chr16:4279536-4279624:+', 'chr16:4279537-4279624:-', 'chr16:4281324-4281640:+', 'chr16:4281386-4281704:+', 'chr16:4281387-4281703:-', 'chr16:4283404-4283490:+', 'chr16:4283404-4283553:-', 'chr16:4283404-4283554:+', 'chr16:61261359-61261509:+', 'chr16:61261360-61261509:-', 'chr16:61261438-61261509:+', 'chr16:61261439-61261509:-', 'chr16:61263209-61263527:+', 'chr16:61263210-61263526:-', 'chr16:61263288-61263606:+', 'chr16:61263289-61263605:-', 'chr16:61265306-61265376:-', 'chr16:61265306-61265377:+', 'chr16:61265306-61265455:-', 'chr16:61265306-61265456:+', 'chr16:63442391-63442541:-', 'chr16:63442392-63442541:+', 'chr16:63442436-63442541:-', 'chr16:63444241-63444559:-', 'chr16:63444242-63444558:+', 'chr16:63444286-63444602:-', 'chr16:63446302-63446408:+', 'chr16:63446302-63446409:-', 'chr16:63446302-63446452:-', 'chr17:42517551-42517701:-', 'chr17:42517552-42517701:+', 'chr17:42517613-42517701:-', 'chr17:42519401-42519719:-', 'chr17:42519402-42519718:+', 'chr17:42519463-42519781:-', 'chr17:42521481-42521568:+', 'chr17:42521481-42521569:-', 'chr17:42521481-42521631:-', 'chr17:44334563-44334713:+', 'chr17:44334564-44334713:-', 'chr17:44336413-44336731:+', 'chr17:44336414-44336730:-', 'chr17:44338431-44338580:-', 'chr17:44338431-44338581:+', 'chr17:45987586-45987736:+', 'chr17:45987587-45987736:-', 'chr17:45987626-45987736:+', 'chr17:45987627-45987736:-', 'chr17:45989436-45989754:+', 'chr17:45989437-45989753:-', 'chr17:45989476-45989794:+', 'chr17:45989477-45989793:-', 'chr17:45991494-45991603:-', 'chr17:45991494-45991604:+', 'chr17:45991494-45991643:-', 'chr17:45991494-45991644:+', 'chr17:82008550-82008700:-', 'chr17:82008681-82008700:-', 'chr17:82008682-82008700:+', 'chr17:82010400-82010718:-', 'chr17:82010531-82010849:-', 'chr17:82010532-82010848:+', 'chr17:82012549-82012568:-', 'chr17:82012549-82012698:+', 'chr17:82012549-82012699:-', 'chr19:41732348-41732498:-', 'chr19:41732349-41732498:+', 'chr19:41734198-41734516:-', 'chr19:41734199-41734515:+', 'chr19:41736216-41736365:+', 'chr19:41736216-41736366:-', 'chr19:43453207-43453357:-', 'chr19:43453252-43453357:-', 'chr19:43453253-43453357:+', 'chr19:43455057-43455375:-', 'chr19:43455102-43455420:-', 'chr19:43455103-43455419:+', 'chr19:43457120-43457225:-', 'chr19:43457120-43457269:+', 'chr19:43457120-43457270:-', 'chr19:44926036-44926186:+', 'chr19:44926037-44926186:-', 'chr19:44927886-44928204:+', 'chr19:44927887-44928203:-', 'chr19:44929904-44930053:-', 'chr19:44929904-44930054:+', 'chr20:33335198-33335348:-', 'chr20:33335199-33335348:+', 'chr20:33335286-33335348:-', 'chr20:33337048-33337366:-', 'chr20:33337049-33337365:+', 'chr20:33337136-33337454:-', 'chr20:33339154-33339215:+', 'chr20:33339154-33339216:-', 'chr20:33339154-33339304:-', 'chr20:36033923-36034073:+', 'chr20:36033924-36034073:-', 'chr20:36035773-36036091:+', 'chr20:36035774-36036090:-', 'chr20:36037791-36037940:-', 'chr20:36037791-36037941:+', 'chr20:40161013-40161163:-', 'chr20:40161014-40161163:+', 'chr20:40162863-40163181:-', 'chr20:40162864-40163180:+', 'chr20:40164881-40165030:+', 'chr20:40164881-40165031:-', 'chr20:59988985-59989135:-', 'chr20:59989037-59989135:-', 'chr20:59989038-59989135:+', 'chr20:59990835-59991153:-', 'chr20:59990887-59991205:-', 'chr20:59990888-59991204:+', 'chr20:59992905-59993003:-', 'chr20:59992905-59993054:+', 'chr20:59992905-59993055:-', 'chr21:24894312-24894462:-', 'chr21:24894313-24894462:+', 'chr21:24896162-24896480:-', 'chr21:24896163-24896479:+', 'chr21:24898180-24898329:+', 'chr21:24898180-24898330:-', 'chrX:9700334-9700484:-', 'chrX:9702184-9702502:-', 'chrX:9704202-9704352:-', 'chrX:99209453-99209603:-', 'chrX:99209454-99209603:+', 'chrX:99211303-99211621:-', 'chrX:99211304-99211620:+', 'chrX:99213321-99213470:+', 'chrX:99213321-99213471:-']", + "[0, 1]", + "8", + "True", + "source_bed_then_genomic", + "max", + "natural", + "[{'chrom': 'chr1', 'start_index': 0, 'end_index': 32, 'n_regions': 33}, {'chrom': 'chr2', 'start_index': 33, 'end_index': 35, 'n_regions': 3}, {'chrom': 'chr3', 'start_index': 36, 'end_index': 44, 'n_regions': 9}, {'chrom': 'chr5', 'start_index': 45, 'end_index': 50, 'n_regions': 6}, {'chrom': 'chr6', 'start_index': 51, 'end_index': 92, 'n_regions': 42}, {'chrom': 'chr7', 'start_index': 93, 'end_index': 104, 'n_regions': 12}, {'chrom': 'chr8', 'start_index': 105, 'end_index': 113, 'n_regions': 9}, {'chrom': 'chr9', 'start_index': 114, 'end_index': 122, 'n_regions': 9}, {'chrom': 'chr12', 'start_index': 123, 'end_index': 128, 'n_regions': 6}, {'chrom': 'chr14', 'start_index': 129, 'end_index': 143, 'n_regions': 15}, {'chrom': 'chr15', 'start_index': 144, 'end_index': 167, 'n_regions': 24}, {'chrom': 'chr16', 'start_index': 168, 'end_index': 197, 'n_regions': 30}, {'chrom': 'chr17', 'start_index': 198, 'end_index': 233, 'n_regions': 36}, {'chrom': 'chr19', 'start_index': 234, 'end_index': 254, 'n_regions': 21}, {'chrom': 'chr20', 'start_index': 255, 'end_index': 284, 'n_regions': 30}, {'chrom': 'chr21', 'start_index': 285, 'end_index': 290, 'n_regions': 6}, {'chrom': 'chrX', 'start_index': 291, 'end_index': 299, 'n_regions': 9}]" + ] + ], + "shape": { + "columns": 11, + "rows": 1 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
plot_familyvalue_modesource_formatregion_ordercluster_ordertop_n_regions_per_clusterhas_top_regions_tableregion_sortassociation_strength_aggregatecluster_sortchromosome_blocks
0read_cluster_region_association_heatmapfractionlegacy_wide[chr1:9168068-9168218:+, chr1:9168135-9168218:...[0, 1]8Truesource_bed_then_genomicmaxnatural[{'chrom': 'chr1', 'start_index': 0, 'end_inde...
\n", + "
" + ], + "text/plain": [ + " plot_family value_mode source_format \\\n", + "0 read_cluster_region_association_heatmap fraction legacy_wide \n", + "\n", + " region_order cluster_order \\\n", + "0 [chr1:9168068-9168218:+, chr1:9168135-9168218:... [0, 1] \n", + "\n", + " top_n_regions_per_cluster has_top_regions_table region_sort \\\n", + "0 8 True source_bed_then_genomic \n", + "\n", + " association_strength_aggregate cluster_sort \\\n", + "0 max natural \n", + "\n", + " chromosome_blocks \n", + "0 [{'chrom': 'chr1', 'start_index': 0, 'end_inde... " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Region axis preview (sorted)\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "region_id", + "rawType": "str", + "type": "string" + }, + { + "name": "chrom", + "rawType": "str", + "type": "string" + }, + { + "name": "start", + "rawType": "int64", + "type": "integer" + }, + { + "name": "end", + "rawType": "int64", + "type": "integer" + }, + { + "name": "source_label", + "rawType": "str", + "type": "string" + }, + { + "name": "strand_label", + "rawType": "str", + "type": "string" + } + ], + "ref": "d5632efe-6014-4d54-9878-2f24835a2478", + "rows": [ + [ + "0", + "chr1:9169918-9170236:+", + "chr1", + "9169918", + "9170236", + "on_target", + "+" + ], + [ + "1", + "chr1:9169985-9170303:+", + "chr1", + "9169985", + "9170303", + "on_target", + "+" + ], + [ + "2", + "chr1:9169986-9170302:-", + "chr1", + "9169986", + "9170302", + "on_target", + "-" + ], + [ + "3", + "chr1:114358436-114358754:+", + "chr1", + "114358436", + "114358754", + "on_target", + "+" + ], + [ + "4", + "chr1:114358437-114358753:-", + "chr1", + "114358437", + "114358753", + "on_target", + "-" + ] + ], + "shape": { + "columns": 6, + "rows": 5 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
region_idchromstartendsource_labelstrand_label
0chr1:9169918-9170236:+chr191699189170236on_target+
1chr1:9169985-9170303:+chr191699859170303on_target+
2chr1:9169986-9170302:-chr191699869170302on_target-
3chr1:114358436-114358754:+chr1114358436114358754on_target+
4chr1:114358437-114358753:-chr1114358437114358753on_target-
\n", + "
" + ], + "text/plain": [ + " region_id chrom start end source_label \\\n", + "0 chr1:9169918-9170236:+ chr1 9169918 9170236 on_target \n", + "1 chr1:9169985-9170303:+ chr1 9169985 9170303 on_target \n", + "2 chr1:9169986-9170302:- chr1 9169986 9170302 on_target \n", + "3 chr1:114358436-114358754:+ chr1 114358436 114358754 on_target \n", + "4 chr1:114358437-114358753:- chr1 114358437 114358753 on_target \n", + "\n", + " strand_label \n", + "0 + \n", + "1 + \n", + "2 - \n", + "3 + \n", + "4 - " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Source label counts\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "source_label", + "rawType": "str", + "type": "string" + }, + { + "name": "n_regions", + "rawType": "int64", + "type": "integer" + } + ], + "ref": "ba7bf635-1ba7-44d2-b611-907bfe0ba558", + "rows": [ + [ + "off_target", + "200" + ], + [ + "on_target", + "100" + ] + ], + "shape": { + "columns": 1, + "rows": 2 + } + }, + "text/plain": [ + "source_label\n", + "off_target 200\n", + "on_target 100\n", + "Name: n_regions, dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from dimelo import plotting, plotting_matplotlib\n", + "import importlib\n", + "import inspect\n", + "import pandas as pd\n", + "\n", + "# Reload plotting modules in case this kernel imported dimelo before local edits.\n", + "plotting = importlib.reload(plotting)\n", + "plotting_matplotlib = importlib.reload(plotting_matplotlib)\n", + "required_plotting_kwargs = {\"cluster_sort\", \"cluster_order\"}\n", + "observed_plotting_kwargs = set(\n", + " inspect.signature(plotting.prepare_read_cluster_region_association_data).parameters\n", + ")\n", + "observed_matplotlib_kwargs = set(\n", + " inspect.signature(\n", + " plotting_matplotlib.plot_read_cluster_region_association_heatmap_matplotlib\n", + " ).parameters\n", + ")\n", + "if (\n", + " not required_plotting_kwargs.issubset(observed_plotting_kwargs)\n", + " or \"row_annotation_columns\" not in observed_matplotlib_kwargs\n", + "):\n", + " raise RuntimeError(\n", + " \"This notebook is using an older dimelo.plotting module. \"\n", + " f\"Imported from: {plotting.__file__}. \"\n", + " \"Restart the kernel or install this repo in editable mode with `python -m pip install -e .`.\"\n", + " )\n", + "\n", + "region_sort_mode = [\"genomic\"] # string or list, e.g. [\"cluster_fraction\", \"genomic\"]\n", + "cluster_sort_mode = \"natural\" # \"input\" | \"natural\" | \"total_association\" | \"max_association\"\n", + "association_strength_aggregate = \"max\" # only used when region_sort=\"association_strength\"\n", + "top_regions_per_cluster = 8\n", + "\n", + "association_summary_A = cluster.summarize_read_clusters_by_region(\n", + " metadata=rw_multi.metadata,\n", + " labels=clust_multi.labels_size_ordered,\n", + " include_strand=True,\n", + ")\n", + "association_payload_A = plotting.prepare_read_cluster_region_association_data(\n", + " association_summary_A,\n", + " value_mode=\"fraction\",\n", + " top_n_regions_per_cluster=top_regions_per_cluster,\n", + " region_sort=region_sort_mode,\n", + " association_strength_aggregate=association_strength_aggregate,\n", + " cluster_sort=cluster_sort_mode,\n", + ")\n", + "\n", + "axis_table = cluster.infer_region_source_labels(\n", + " association_payload_A[\"region_axis_table\"],\n", + " rw_multi.metadata,\n", + ")\n", + "source_priority = {\"on_target\": 0, \"off_target\": 1, \"unlabeled\": 2, \"unknown\": 3}\n", + "axis_table[\"strand_label\"] = axis_table.get(\"strand\", pd.Series(\"unknown\", index=axis_table.index)).fillna(\"unknown\").astype(str)\n", + "axis_table[\"_source_rank\"] = axis_table[\"source_label\"].map(source_priority).fillna(99).astype(int)\n", + "axis_sorted = axis_table.sort_values(\n", + " [\"_source_rank\", \"chrom\", \"start\", \"end\"],\n", + " ascending=[True, True, True, True],\n", + ").drop(columns=[\"_source_rank\"]).reset_index(drop=True)\n", + "region_order = axis_sorted[\"region_id\"].tolist()\n", + "\n", + "matrix_sorted = (\n", + " association_payload_A[\"matrix_table\"].copy()\n", + " .set_index(\"region_id\")\n", + " .loc[region_order]\n", + " .reset_index()\n", + ")\n", + "\n", + "association_payload_A[\"region_axis_table\"] = axis_sorted\n", + "association_payload_A[\"matrix_table\"] = matrix_sorted\n", + "association_payload_A[\"metadata\"] = {\n", + " **association_payload_A.get(\"metadata\", {}),\n", + " \"region_sort\": \"source_bed_then_genomic\",\n", + " \"cluster_sort\": cluster_sort_mode,\n", + "}\n", + "\n", + "print(\"Association payload metadata\")\n", + "display(pd.DataFrame([association_payload_A[\"metadata\"]]))\n", + "print(\"Region axis preview (sorted)\")\n", + "display(axis_sorted[[\"region_id\", \"chrom\", \"start\", \"end\", \"source_label\", \"strand_label\"]].head())\n", + "print(\"Source label counts\")\n", + "display(axis_sorted[\"source_label\"].value_counts(dropna=False).rename(\"n_regions\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Top regions per cluster\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "region_id", + "rawType": "str", + "type": "string" + }, + { + "name": "chrom", + "rawType": "str", + "type": "string" + }, + { + "name": "start", + "rawType": "int64", + "type": "integer" + }, + { + "name": "end", + "rawType": "int64", + "type": "integer" + }, + { + "name": "strand", + "rawType": "str", + "type": "string" + }, + { + "name": "cluster", + "rawType": "object", + "type": "unknown" + }, + { + "name": "count", + "rawType": "int64", + "type": "integer" + }, + { + "name": "fraction", + "rawType": "float64", + "type": "float" + }, + { + "name": "total_reads", + "rawType": "int64", + "type": "integer" + }, + { + "name": "value", + "rawType": "float64", + "type": "float" + }, + { + "name": "rank", + "rawType": "int64", + "type": "integer" + } + ], + "ref": "c954f3a2-a1dd-4631-8242-e34cff9a25a1", + "rows": [ + [ + "0", + "chr12:57871837-57871987:-", + "chr12", + "57871837", + "57871987", + "-", + "0", + "24", + "1.0", + "24", + "1.0", + "1" + ], + [ + "1", + "chr12:57871885-57871987:-", + "chr12", + "57871885", + "57871987", + "-", + "0", + "24", + "1.0", + "24", + "1.0", + "2" + ], + [ + "2", + "chr12:57875753-57875855:-", + "chr12", + "57875753", + "57875855", + "-", + "0", + "25", + "1.0", + "25", + "1.0", + "3" + ], + [ + "3", + "chr12:57875753-57875903:-", + "chr12", + "57875753", + "57875903", + "-", + "0", + "25", + "1.0", + "25", + "1.0", + "4" + ], + [ + "4", + "chr14:17377273-17377423:+", + "chr14", + "17377273", + "17377423", + "+", + "0", + "23", + "1.0", + "23", + "1.0", + "5" + ], + [ + "5", + "chr14:17377341-17377423:+", + "chr14", + "17377341", + "17377423", + "+", + "0", + "23", + "1.0", + "23", + "1.0", + "6" + ], + [ + "6", + "chr14:17381209-17381291:+", + "chr14", + "17381209", + "17381291", + "+", + "0", + "19", + "1.0", + "19", + "1.0", + "7" + ], + [ + "7", + "chr15:38664850-38665000:-", + "chr15", + "38664850", + "38665000", + "-", + "0", + "22", + "1.0", + "22", + "1.0", + "8" + ], + [ + "8", + "chr9:145236510-145236828:+", + "chr9", + "145236510", + "145236828", + "+", + "1", + "19", + "0.9047619047619048", + "21", + "0.9047619047619048", + "1" + ], + [ + "9", + "chr9:145236511-145236827:-", + "chr9", + "145236511", + "145236827", + "-", + "1", + "19", + "0.9047619047619048", + "21", + "0.9047619047619048", + "2" + ] + ], + "shape": { + "columns": 11, + "rows": 10 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
region_idchromstartendstrandclustercountfractiontotal_readsvaluerank
0chr12:57871837-57871987:-chr125787183757871987-0241.000000241.0000001
1chr12:57871885-57871987:-chr125787188557871987-0241.000000241.0000002
2chr12:57875753-57875855:-chr125787575357875855-0251.000000251.0000003
3chr12:57875753-57875903:-chr125787575357875903-0251.000000251.0000004
4chr14:17377273-17377423:+chr141737727317377423+0231.000000231.0000005
5chr14:17377341-17377423:+chr141737734117377423+0231.000000231.0000006
6chr14:17381209-17381291:+chr141738120917381291+0191.000000191.0000007
7chr15:38664850-38665000:-chr153866485038665000-0221.000000221.0000008
8chr9:145236510-145236828:+chr9145236510145236828+1190.904762210.9047621
9chr9:145236511-145236827:-chr9145236511145236827-1190.904762210.9047622
\n", + "
" + ], + "text/plain": [ + " region_id chrom start end strand cluster \\\n", + "0 chr12:57871837-57871987:- chr12 57871837 57871987 - 0 \n", + "1 chr12:57871885-57871987:- chr12 57871885 57871987 - 0 \n", + "2 chr12:57875753-57875855:- chr12 57875753 57875855 - 0 \n", + "3 chr12:57875753-57875903:- chr12 57875753 57875903 - 0 \n", + "4 chr14:17377273-17377423:+ chr14 17377273 17377423 + 0 \n", + "5 chr14:17377341-17377423:+ chr14 17377341 17377423 + 0 \n", + "6 chr14:17381209-17381291:+ chr14 17381209 17381291 + 0 \n", + "7 chr15:38664850-38665000:- chr15 38664850 38665000 - 0 \n", + "8 chr9:145236510-145236828:+ chr9 145236510 145236828 + 1 \n", + "9 chr9:145236511-145236827:- chr9 145236511 145236827 - 1 \n", + "\n", + " count fraction total_reads value rank \n", + "0 24 1.000000 24 1.000000 1 \n", + "1 24 1.000000 24 1.000000 2 \n", + "2 25 1.000000 25 1.000000 3 \n", + "3 25 1.000000 25 1.000000 4 \n", + "4 23 1.000000 23 1.000000 5 \n", + "5 23 1.000000 23 1.000000 6 \n", + "6 19 1.000000 19 1.000000 7 \n", + "7 22 1.000000 22 1.000000 8 \n", + "8 19 0.904762 21 0.904762 1 \n", + "9 19 0.904762 21 0.904762 2 " + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "association_payload_A[\"top_regions_table\"].head(10)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Karyotype view of top associated regions\n", + "\n", + "This adds a genome-level view under **Top regions per cluster**:\n", + "- each chromosome is vertical and proportional to true chromosome length,\n", + "- coordinate increases downward (0 near the top, chromosome length near the bottom),\n", + "- loci are colored by cluster assignment, and\n", + "- optional haplotype-aware backbone shading is enabled when maternal/paternal naming is detected.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Wrote karyotype BED to artifacts/notebook/association_regions_by_dominant_cluster.bed\n", + "Using chromosome sizes from dimelo/test/output/chm13.draft_v1.0.fasta.fai\n", + "chr9: 9 plotted region(s)\n", + "chr12: 6 plotted region(s)\n", + "chr20: 30 plotted region(s)\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABJsAAAUxCAYAAAAvB4chAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAtHJJREFUeJzs/QecFeXZP/7fIOIiKAj2gnWxRw2KosYauxiNxvIk9h419lhjLGCNvWGL5UmiscUSfSyxxq5YY0VFjR0VEMsCwv5f1/39nf3vLrtL8QaW3ff79Tq6Z2bOnDlz5pxlPnvd13Sora2tTQAAAABQQMcSKwEAAAAAYRMAAAAARalsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQCTsNhii6XddtutXe6neN3x+luz9957L3Xo0CFdc801qTWKbTvwwANTaxLv6ZZbbjmjN4NJ2HzzzdPee+/d6vbTiSeemI/r9vy905q0pt9R0/rYmJm+0wcPHpx69+6dxowZM122C6C1ETYB7dY777yT9t1337TEEkukqqqqNOecc6a11lornX/++en777+fLtvw3Xff5X+cP/zww2lmtt566+V/gFduXbp0ST/5yU/SeeedlyZMmDCjN6/d+Oyzz9IRRxyRlllmmTT77LOnrl27pr59+6aBAwemkSNHzujNYwo8/vjj6b777ktHHXVUvv+73/0uf7befvvtZh9z3HHH5WVefvnldvvdFKFz/e+i+G7v06dPDgfi8zGzeuKJJ/L70VY+xxGOdevWLbXl/R2vcezYsemyyy4rum0AM4tOM3oDAGaEu+66K/3qV79Ks802W9pll13SCiuskP9R+Nhjj6Ujjzwyvfrqq+nyyy+f5tsRJ3QnnXRSXWAzM1t44YXTaaedln/+4osv0t/+9rd06KGHpuHDh6dBgwZNs+dddNFFczg466yzpvbs2WefzZUw33zzTfrNb36TQ6bw3HPPpdNPPz09+uijObxg5nDWWWelDTfcMC211FL5/q9//et04YUX5s/VCSec0ORjrr/++rTiiivmoHdafjcdf/zx6eijj06t2cknn5wWX3zxVFNTk7/XL7300nT33Xen//znPzmIndlE+BHvRwQYPXr0aDDvzTffTB07+vvx9NrfkyuCzl133TWdc8456aCDDmrzFV8AjQmbgHZn2LBhaccdd8whxYMPPpgWWGCBunkHHHBArhyIMGpm9u233+aqlumpe/fuOeSo2G+//XKFTZwgx4nfLLPMMk2et1K90J7FX9+32WabvI9feOGFvN/ri7DviiuumGmPrfbm888/z99BMQynYvXVV8/BUwRKTYVNTz75ZP5ui2Dxx4hKxAjeW9KpU6d8a80222yztOqqq+af99prr9SrV6980n/77bennXbaaaY5tidnm+KPJrRO22+/fTrzzDPTQw89lDbYYIMZvTkA05U/gwDtTvzDL6o/rrrqqgZBU0Wc0B188MFT3JOiMnwjeghVRFXJJptskuaee+48tCz+0r7HHnvkebHcPPPMk3+Ov6BWhn3E+iveeOONtN1226WePXvmQCVOnu64444mn/eRRx5Jv/3tb9O8886bq4wq/u///i/97Gc/yycsc8wxR9piiy1y5VZjt912W67wiueJ///jH/9IP0asZ7XVVkujR4/OJ8/1/eUvf8mVN7FP4rVF+Pff//53onVcfPHFeZhjLNevX7/073//O1dZ1K+0aK5nUwSJldcdf5n+xS9+kV5//fUm38sIGCt/wY7QbPfdd8+VHfXdf//9ae21187LxPCPpZdeOh177LGTvT/++te/5sfEfonXHpVGFXEiEtvR1D6PSpaYF2FCc2KYxkcffZRPphsHTWG++ebL1SiNRcVH7NfYptjP11133RQdW5dccklafvnl88nuggsumMPaxsNO4r2K4ymGdq277rq5qiQ+YzfffHOeH+uOICXe49g///rXvybazgjQIjyIoa6x76Pi56mnnmqwzLhx4/LnqLq6Or+eCBfi/Yr3bWqPi7feeisHqHFMxGf1D3/4Q6qtrc3Hajwutmf++edPZ5999kTbHH1a/vjHP+bXGvtnkUUWSb///e8nq39LBE0//PBD+vnPf95gelQ3xXfC888/3+xxUglSJvf5K/1n4visvJcRcrX03dTcd2B8ruN4ivd4rrnmSuuss06DaroIeuL7J46VeJ4ll1wynXLKKWn8+PFpWquc6EcgV38YVwynjorA+G6M/VsJeA4//PC8z2I747j805/+lN/75vZdc5/tKTmGm/u8xf6OitsQv0Mq70fld01TPZvefffdXL0b36/xfqyxxhoT/RElhkjGem688cYcSMdzxWuIbWs8XDO+e2N90YOocjxF5eq0HnL+9NNPp0033TR/BuN1xHdIDDGd2u/x2N4Ykhq/k+M932qrrfJ3Z+Pju6X93fh3ZuyP+Ozcc889E21/HA/xHsSxD9DetO4/SwFMA3feeWc+sV5zzTWn6f6NgGXjjTfOJ20x5CT+ARz/WL311lvz/JgeQzv233//XJXyy1/+Mk+vDIGJQCh6SC200EL58XFyHCcFW2+9dbrlllvyY+qLk5NYZ1Q9xMlS+N///d9cxh+B1xlnnJH/4R3PGSfhcfJTacIbJ4TbbrttWm655fJQuC+//DL/Q71+sDA1KkFQ/WEIcVITJ+3xF9+oOIhhdlH9FCemsU2VZWM740QugoE4qYl1xWuPk9hJbVcEFnFiF+9znDjECUY8R+zPOFFv3Hw4tiVOKuK1x/wrr7wyn+jFPqu8F9FQO96bqNKKk4s4sWl80tOcOHn8+9//nk9y4rER0sQJ1DPPPJNPViKQiZO3OGlt/L7GtDgp79+/f7PrjwAywpoIJidXbH8sv+eee+Zj5M9//nM+UYuTozhxmtSxFfs1gogIROIYjqE88Z7FcL7YL/WHNY4YMSLvvwgV44Q1louf47UdcsghuQruf/7nf/LQsdimCHPiRLCy7+MYiJP0CEtivRGuxT6rBFWV7Yn3L46pCDy+/vrrHPbG+7nRRhtN1XGxww47pGWXXTZXC8WJevS+ihPHeP4IL+L4iNcQfbIiWI1juFIdFCexEebts88+eR2vvPJKOvfcc3OAFSepkxrCE2FZVF/WF2FI7PMIln7605/WTY+wJr4bYj9FGDClzx8BXDw+Pm9xEr7SSiu1+N3UlNiu2KfxvRqfkc6dO+egINYd34OVMCWClsMOOyz/P+bFMRXvVbz301KESiH2a0UEevHdGN+HESZFmBGBUuy7CIDjs7Hyyiune++9N4cPEUrEPpySz/aUHMPNfd7imI33Lara4vnjPQqVQLCx6E0V70N838d2xWu+9tpr8+uKkLfxd0wc3zEML47jUaNG5T/IxLEW71/FTTfdlNcXx0SsL15ffHY+/PDDPG9aiOMjXnt8J0VwGtt49dVX589ehF/xOZ+S7/EQ33FxrO+88845gIv9HwFofXG8T2p/x2crfpfHexXfVRdccEH+HfrBBx80OMZCfFYn93cFQJtSC9COjBo1Kv40XfuLX/xish+z6KKL1u6666519//4xz/mdTR29dVX5+nDhg3L9//xj3/k+88++2yz6x4+fHheJtbZ2IYbbli74oor1tbU1NRNmzBhQu2aa65ZW11dPdHzrr322rU//PBD3fTRo0fX9ujRo3bvvfdusN5PP/20tnv37g2mr7zyyrULLLBA7ciRI+um3XfffXm98fonZd11161dZpll8uuJ2xtvvFF75JFH5sdvscUWdcu99957tbPMMkvtoEGDGjz+lVdeqe3UqVPd9DFjxtT26tWrdrXVVqsdN25c3XLXXHNNXmc8X0Xs75gW+6H+65l33nlrv/zyy7ppL730Um3Hjh1rd9lll4neyz322KPB9myzzTb5+SvOPffcvFy8tikVj4vbc889Vzft/fffr62qqsrPU3HMMcfUzjbbbA3eg88//zzvl6aOj/rmmmuu2pVWWmmytyne09imRx99tMFzxfMffvjhkzy2YtnOnTvXbrzxxrXjx4+vm37RRRfl5f/85z/XTYv3Kqb97W9/q5sWx0dMi/fjqaeeqpt+7733TvRebr311vm53nnnnbppH3/8ce0cc8xRu84669RNi9df/1hrypQeF/vss0/dtHj9Cy+8cG2HDh1qTz/99LrpI0aMqO3SpUuD74j//d//zev897//3eD5Bw8enNf7+OOPt7idsb/79u3b5Lz4TMR21N/v99xzT17vZZddNsXPX3kfXn311cn+bmr8HTh06NC8jjie629X5Tur4rvvvptoXfvuu2/t7LPP3uB7Lvbl5HzvNKVyzP7rX//Kr+G///1v7Q033JA/z/E+ffjhh3XPEcsdffTRDR5/22235ekDBw5sMH277bbL7/3bb789xZ/tyT2Gm/u8hbPOOqvB75eWfkcdcsghedn673/8Plh88cVrF1tssbr36KGHHsrLLbvssvk7t+L888/P0+N7uaX37rTTTsv7JF7zpH4/Nhbb27Vr12bnx3ETv+c22WSTiY6heB0bbbTRFH+PDxkyJC8X+6e+3XbbbaJjvaX9HdPj/ax/LMT3SEy/8MILJ1o+vkfi2ANobwyjA9qV+At6qFRNTEuVCp1//vOfeYjPlPjqq6/yX3XjL7UxDC0absctKo7iL/FDhw7Nf2WvLy6RXr8vUgwfiiFNMaym8vi4xTLxl/T4y3345JNP0osvvpirW2LoQUVUg0Sl0+SK4T3xl9+4xVCu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WjH6AMbzxzEd2xBDSKdE7MPoo3jeeeflcDoqjh955JG6gL7+75Up2d8tGTJkSP59Gj0bAdqdGX05PIAZ5a233qrde++986Wg4zLGcRnqtdZaK1+6uP5luBtfVrpyCeXVV189P653796155xzTt1lqyuXSn7++edrd9pppzw/Likfl1zfcsstG1wmOzzxxBP5MuexrsaXX45LZccl2eeff/7aWWedtXahhRbK67j55pvrlqk877PPPtvk64zLW8flo7t3754vyb3kkkvmSz033o5bbrklXwI7tnW55ZarvfXWWyf7EuRxefvll1++yXkPP/zwRK8rnisu7x2Xvo7bMsssU3vAAQfUvvnmmw0ee8EFF+Tnj23q169fvmR77KtNN920bpnY37H+2A/1xaXP4/2MS07POeectQMGDKh97bXXGixTuWR2XCK9vsbv5QMPPFD7i1/8onbBBRfM71P8P97bOIYmJdYTr+0vf/lLvpR3vJZVVlklvy9NiUuQzzXXXPn9+v7772unRFxO/dBDD63t06dPfq/jkvKxvwYNGlQ7atSouuVin26xxRZNvo9xm9xj66KLLsrvXRyb8803X+3+++9fO2LEiMk6Nprbhsr+qi8+S3EMd+vWLb+m9ddfP39u6otL1ccx0qNHj/yex3bF6x47dmyx46K5y7U39Rrjec8444w8Pd7zeE/jvTjppJMavBfN2WqrrWo33HDDZuffddddeRsXWGCBukvZT83zN7W/J/Xd1Nzl7f/85z/nY7vyfLFf7r///rr58fldY4018r6Pz9Dvf//72nvvvTevq/7noanvnW233TY/rvHx1dikjtlJvZdh9OjR+XMU2xjHdnxuzzrrrNoJEyZM9Wd7co7hSW37Kaeckn8HdOzYscH3U1O/o+J3x3bbbZc/D/FdEJ+Nf/7znw2Wie2M9dx0000Npjf1nRqfkZ///Od5++eee+78u/Oll16aaLnmjo3GYntjuaZu8Tuq4oUXXqj95S9/WdurV6+8f+O1br/99vk7eUq/x8O3336b37OePXvm17L11lvn3zux3Omnnz5Z+7u5z0xT78NRRx2V/w3Q+NgBaA86xH9mdOAFAJMjhuhEVU1UTjQ1RKgtiGGDUQ0Sf0WPYWm0T//+979zpVZUIzV1xbP2Jq4CGVV/UZ3WWkQlzAEHHDBZTfxpveLiCqusskoevty4SvDHiMq1qMKLSrO48ANAe6NnEwCtUlzSvfHfQ6677ro8JCFOwtuq2267Lfcjqd+Il/YnhqPFkN3JHWbVlr366qt5yGNcCQ1+jDiOGothdTG0sH7j+hKuvvrqPPwuhrMCtEcqmwBoleLqVYceemj61a9+lZuFR++hqPSJPlvRByP6OrUlTz/9dHr55Zdz35toCl6/6TDQ+qhsmvlEL634/RF9uaJ3VjTYj1v0Ros+WgCUo0E4AK1SDD+IS4LHFc+imimaxka1z+mnn97mgqYQzdxjGEc0+I5G6ACUFY3qo1l7hPpx8Yu42EdcHCAagQNQlsomAAAAAIrRswkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAaPU6dOiQbrvtthm9GUwGYRMAAAAww3366afpoIMOSksssUSabbbZ0iKLLJIGDBiQHnjggeLP9fDDD+fwauTIkcXXTUqd7AQAAABgRnrvvffSWmutlXr06JHOOuustOKKK6Zx48ale++9Nx1wwAHpjTfeaJVvUG1tbRo/fnzq1Em8Up/KJgAAAGCG+u1vf5srjZ555pm07bbbpj59+qTll18+HXbYYempp56arMqkF198MU+L4Cq8//77uTJqrrnmSl27ds3ru/vuu/P89ddfPy8T8+Ixu+22W74/YcKEdNppp6XFF188denSJa200krp5ptvnuh5/+///i/17ds3V2A99thj02EPzVxEbwAAAMAM89VXX6V77rknDRo0KIdCjUW109SIiqixY8emRx99NK/3tddeS926dcvD82655ZYcar355ptpzjnnzMFSiKDpL3/5Sxo8eHCqrq7Oj/3Nb36T5plnnrTuuuvWrfvoo49Of/rTn/KQvwisaEjYBAAAAMwwb7/9dh6OtswyyxRd7wcffJADpRiSFyIYqujZs2f+/7zzzlsXZo0ZMyadeuqp6V//+lfq379/3WOicumyyy5rEDadfPLJaaONNiq6vW2JsAkAAACYYSJomhZ+97vfpf333z/dd9996ec//3kOnn7yk5+0GHp99913E4VIUR21yiqrNJi26qqrTpNtbiuETQAAAMAME8PVog/SlDQB79ix40RBVTQUr2+vvfZKm2yySbrrrrty4BRD5M4+++x8xbumfPPNN/n/sfxCCy3UYF70ZqqvqeF+/P9pEA4AAADMMDGkLUKhiy++OH377bcTza/fBLwieiiFTz75pEGD8MaiP9N+++2Xbr311nT44YenK664Ik/v3Llz/n9cSa5iueWWy6FSDL9baqmlGtxiPUw+YRMAAAAwQ0XQFMFPv379cvPuoUOHptdffz1dcMEFdf2T6qsEQCeeeGJeNqqRomqpvkMOOSTde++9adiwYen5559PDz30UFp22WXzvEUXXTRXU/3zn/9Mw4cPz1VNc8wxRzriiCPSoYcemq699tr0zjvv5MddeOGF+T6TT9gEAAAAzFDRiDuCnfXXXz9XIK2wwgq5d9IDDzyQLr300omWn3XWWdP111+fh95FH6YzzjgjDRw4sMEyEV7FFekiYNp0001Tnz590iWXXJLnxTC5k046KV9Vbr755ksHHnhgnn7KKaekP/zhD3nIXeVxEWQtvvji02lPtA0daqdVJy4AAAAA2h2VTQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAMM19+umn6aCDDkpLLLFEmm222dIiiyySBgwYkB544IE8v6amJh1wwAGpV69eqVu3bmnbbbdNn332mXfmR+7Xyy+/PK233nppzjnnTB06dEgjR45M01qH2tra2mn+LAAAAEBREybUphHfjZ2he3Wu2Tunjh07THK59957L6211lqpR48e6eSTT04rrrhiGjduXLr33ntzGPLGG2+k/fffP911113pmmuuSd27d08HHnhg6tixY3r88cfTdDNhQkrff5VmqC49U+o4ebVBk7NfzzvvvBzkhWOOOSaNGDEiLz8tCZsAAABgJvTlN2NS34H/mqHbMOT4n6de3Wab5HKbb755evnll9Obb76Zunbt2mBeVNpExc0888yT/va3v6XtttsuT4+gZNlll01PPvlkWmONNdJ08e0XKZ21ZJqhjnwnpa5zT9aik9qv9UOlhx9+OK2//vrTJWwyjA4AAACYZr766qt0zz335CFyjQOREMHHkCFDckXOz3/+87rpyyyzTOrdu3cOm5i6/TqjCJsAAACAaebtt99O0cEnwqOW+g517tx5ooBkvvnmy/OYuv06owibAAAAgGlGq+j2t187zegNAAAAAKauOXf0TJrR2zAp1dXVuSdT9GBqzvzzz5/Gjh07UZ+huBpdzJuuzbmjZ9KM1KXnZC02Oft1RtEgHAAAAJimNttss/TKK69MskH49ddfn7bddts8PZaNIWLTtUF4G9uvPTQIBwAAANqiiy++OI0fPz7169cv3XLLLWno0KHp9ddfTxdccEHq379/6t69e9pzzz3TYYcdlh566KHcMHz33XfP8wRNU79fQ/S8evHFF3OPpxDhVNyPBuPTisomAAAAYJr75JNP0qBBg9I///nP/HNUMvXt2zcdeuihab311ks1NTXp8MMPz9VNY8aMSZtsskm65JJLpu8wuja4X0888cR00kknTfS4q6++Ou22227TZJuETQAAAAAU42p0AAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYjqlNuyTTz5JgwcPTqNHj06zzz572m+//dLCCy+c2iP7wn5wTPh8+J7wnen3BwDAjPdJO8gqOtTW1tamNmrgwIHpZz/7WVp33XXT008/ne688848rT2yL+wHx4TPh+8J35l+f0y99vCPwsllX9gXjgmfD98VvjP9/vhxBraDrKLNDqMbNWpUGjZsWFp77bXz/X79+qUvv/wyffrpp6m9sS/sB8eEz4fvCd+Zfn/8OFdddVXaYIMN0jnnnJMGDBiQg6f2yr6wLxwTPh++K3xn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mwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgmE7lVgUAAADt2wF/HZLueuXTFpfZYsWv08W/7jvdtgmmN2ETAAAAFBIh0iFDh6aampom51dVVaXq6mr7mzbNMDoAAAAAihE2AQAAAFCMYXRAu7PGoPvTp6PHtrjM/HO8l546bqPptk0AAABthbAJaHciRBpqHD0AAMA0YRgdAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACK0SAcAABgKm1+/qPptU9Gt7jMcgt8ku4+eB37GGg3hE0AAABTKUIkV7kFaMgwOgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxXQqtypakyeffCz1vWerlpfZ9I7Uv//a022bAAAAgLZPZRMAAAAAxahsaqOiYmno3E+nmpqaJudXVVWl/tXV0327AACAtueAvw5Jd73yaYvLbLHi1+niX/edbtsEzDjCJgAAAH6UCJEOGTq0xT92V/tjN7QbhtEBAAAAUIywCQAAAIBiDKMDAAAApgnDJ9snlU0AAAAAFKOyCQAAANqhhx9+OF1++eXp0EMPTauttlo65ZRT0hdffJFmn332PP9nP/tZ2nzzzSd63IQJE9Jf//rX9PLLL6eOHTumOeaYI+21115p/vnnTx988EG65ppr0qhRo9Iss8ySllxyybT77runzp0753mXXnpp3Xq+/fbb9P3336crrriiye17/fXX8/OMGTMm3997771Tnz59ciP6eI5hw4alH374Ia266qppxx13TB06dEjDhw9PgwcPTu+9916ad95502mnnTbN9h/NEzYBAABAOxOhzEMPPZSWWmqpBtN/85vf5OCpJUOGDElvvfVWDnI6deqU/vGPf6S///3v6eCDD06zzjpr2m233VLv3r1zKHXRRRelO+64I2233XZ5Wv3w5+qrr84BUVNGjBiRg6mjjjoqLbTQQmncuHFp7Nixed7tt9+e13366aen8ePHp7PPPjs9/fTTaY011khdunRJv/rVr3KIdeONNxbZV0w5w+gAAACgHYmgJiqadt111xwOTakIiKKiKAKg2traHOz07Nkzz1tggQVyqBSi6mmJJZbI1VKNRXD0+OOPp/XWW6/J57j//vvTmmuumYOmENvZtWvX/HNUSK200kp5OyLsWmGFFdJjjz2W53Xr1i0ts8wyabbZZpvi10U5KpsAAACgHbn77rvT0ksvnYOgxm644YZ0880355Bnhx12SPPNN99Ey/z0pz9Nr732Wvrtb3+bqqqq0lxzzZVOOOGEiZaL4W5RPRVD3Bp79tln8zC3xRZbrMlt/Oijj9Lcc8+dBg0alL755pu8vbGeeL7FF188VzL169cvVzZFpVUMyaP1UNkEAAAA7cR///vf9Mwzz6Stt956onkRHsWQtBieFuHOn/70pybX8e677+b1xBC5iy++OFcWXXXVVQ2WicqnCy+8MP3kJz9pclhe9Itaf/31m93OCJGiZ1MMzYteUhEm3XLLLXneVlttlXr16pUDrjPPPDP3hYr+ULQeKpsAAACgnXjjjTfysLbDDjss349G3hEUjRw5Mm200UZ5WgxP22STTdLf/va3NHr06NwAvL5///vfafnll68b1rbOOus06MUUQdMFF1yQevTokXbZZZeJtuHzzz9Pb7/9djrkkEOa3c4IkxZddNE8LC7EkLro1RSi2XgMAayInlALL7zwj9wzlKSyCQAAANqJCJQuueSSHAbFLRqE77nnnmmDDTbIwVNFVD917969LmiK4XX33ntv/jmGv7366qs5VArPP/98XdgTFUlR0RQhUVyhrqkG4FHVFFeQq4RVFbFdMbwurLXWWnmoXvSFCi+++GIOn8J3331Xd4W6CK7+9a9/NXnVPGYclU0AAADQzkWoE0PSIkCKgChCpsMPP7xu/vvvv597JYWNN944ffzxx+noo4/Ow9eigmmPPfbI85588skcGEWT8GOPPTZP69OnT9p9993rmpM/+uijaf/9959oG4YNG5Y23XTTusdEb6hYRzQajx5SEYpVAqYIyuK5Y15cQa/S+ylCqNjueD0RSh144IFp7bXXbrJvFNOOsAkAAADaqT/84Q91P0cz7qZEQBTD6Sq9l+LKcHvvvXeTy0awE7fmRDgUvZ4a+/rrr3Oj8fpNywcMGJBvjUWwdM455zS5/rgKXVPrZ/oSNtHmnXPReenQ4X9seZl5TkqHHdj8eGEAAID2KgKigQMHTtPnmHPOOesqoZj5CZto8yJEGjp0i3zZzabEpTMPq66e7tsFAAAAbZEG4QAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFNOp3KoAZg7XPTEsnXDHWy0uc/JWndIuay4+3bYJAACgrRA2Ae1OhEj95/kh1dTUNDm/qqoqVVcLmgAAAKaGYXQAAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMa5GBwAAk+HGM/dJv/r27y0v03WHtP3vL7c/AWjXhE0AADAZIkQaOvTIVFNT0+T8qqqqtH11tX0JQLtnGB0AAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAiulUblUAAG3HrddfnrZ548iWl1nmrPTLnfaZbtsEADAzEDYBADQhQqShQ9dPNTU1Te6fqqqq9MvqavsOAKARw+gAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAAAibAAAAAGh9VDYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxXQqtyoAAKA92PnUa9JVYw5reZnZzkn/e+xu022bAGg9hE0AAMAUiRBp6NC1Uk1NTZPzq6qq0v9WV9urAO2UsAkAAIAfrVrACPx/9GwCAAAAoBiVTQAAAG3YuHHj0l/+8pf08ssvp86dO6fevXunAw44IF177bVpyJAh6YsvvkinnnpqWmyxxZp8/G233Zaefvrpuvuff/55Wm+99dLOO++c7z/00EPpjjvuSLW1tWn55ZdPu+++e+rUqVN6+OGH07333lv3uC+//DItu+yy6dBDD53oOf7+97+n559/PnXs+P/qIQYMGJDWXHPN/PPgwYPTK6+8kuacc858f4UVVki//vWv88+ffPJJuvrqq9PXX3+dxo8fn375y1+m/v37F91/wJQTNgEAALRhN9xwQ+rQoUM655xz8v9HjhyZp/fr1y9tueWW6aSTTmrx8VtvvXW+VYKrCKrWWmutuuDppptuymFV9+7d09lnn50efPDBtPHGG+dAKm4Vv//97+se11hsxw477JB//uqrr9KRRx6ZQ6VKwBTzN9tss4ked9lll6V11103rb/++jlwOv7449PSSy+devbsOdX7C/jxDKMDAABoo6KJe1QYbb/99jloCj169Mj/jyqjXr16TdH6nnvuufyYJZZYIt+Piqe+ffvmdcb6N9xww/TEE09M9Li33347h0E//elPm1xv165dG2xzVEnFbVLef//9tPLKK+efI5iKqq0nn3xyil4TUJ7KJgAAgDYqKo8iyLn99tvTf/7znzyMbtttt81VQ1Mjgqv61UoxNG7uueeuuz/PPPPkaY3FULu11147D69rzj333JPuv//+XNm0995750qp+vPiueO5fvWrX9UN+Vt88cXTY489lofdffbZZ+mtt97K2wDMWMImAACANir6GEVPpoUWWijttNNO6b333stD3s4666wGYc7kGD58eHrzzTfTQQcdNEWPi0qlp556apLD9TbddNN8i2qlSy65JK244oppjjnmyFVZUTkV/ZyeffbZdOaZZ+YhgVVVVWn//ffP/aiOOeaYHERFiFbp+wTMOMImAABgimx+/qPptU9Gt7jMcgt8ku4+eB17dgaLIW8xvC2qikJUBM0777zpgw8+yGHOlHjkkUfykLlu3bo1WH9UT9UPpBoPzYuhdhF2LbzwwpP1PIsuumiaa6650uuvv577StXvv7TaaqvlHlQff/xxHsoXVUz1G46ffvrpU/y6gPKETQAAwBSJEGno0KG5YqUpUXFSXV1tr7YC0ccoqn1eeumltMoqq+RgKG4R/rQkKosi2IlbmDBhQnr00Ufz8Lb6IgyKiqUYmheVUg888MBEV4OL4W/RwLuxww8/PB133HE5TPrwww/rwqgYDhcVWJVtjGF5lQArjrvRo0en+eefP98fNWpUrn6KaqZ4jR999FGzTciB6UfYBAAA0Ibtscce6Yorrqi7Kt1ee+2VA54rr7wyvfjii/nqdFER1KVLl3TuuefmxwwbNiwPaauIfk/x2Ma9nuabb7603XbbpRNPPDHfX2655XKT8IqoQIphcWussUaDx0VI9M0339RVSV1//fU5BIueThEc7bbbbnVh0+DBg3Nz8Zg+66yzpoMPPjjNPvvsed7zzz+f7rjjjjwvqqHiinfRlwqYsYRNAAAAbVgEQscff/xE0yN0akoEOxHcVK44F37yk5+k888/v8nlN9hgg3xryoILLpj+/Oc/TzQ9hshFmFUJho488shmtz+qn5oTFVNNVU0BM5awCQAAgAZD74499thpukcaVzoBbYs2/QAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFBMp3KrAgAAAKqrq+0E2jWVTQAAAAAUo7IJAACAdmHcuHHpL3/5S3r55ZdT586dU+/evdMBBxzQ7PTGnnjiiXTnnXem8ePH5/vrrrtu2mKLLfLPw4cPT4MHD07vvfdemnfeedNpp53W4LEPPfRQuuOOO1JtbW1afvnl0+677546dZr4lDzW8dZbb+XtqKqqSjvvvHNacsklGyzz0UcfpeOOOy5tsMEGaZdddsnTrr766vy4io8//jjttNNOadNNNy2092DyCZsAAABoF2644YbUoUOHdM455+T/jxw5ssXpjfXq1SsdddRRqUePHum7777Lgc/iiy+elltuudSlS5f0q1/9Kn3//ffpxhtvbPC4zz//PN10003p1FNPTd27d09nn312evDBB9PGG2880XOsttpqae+9906zzDJLev7559P555+fLrjggrr5P/zwQ7ryyivTqquu2uBxEV5VxPYffPDBaY011vjR+wymhmF0AAAAtHk1NTXp4YcfTttvv30OlEKERs1Nb8rSSy9dN2/22WdPCy64YK5oCt26dUvLLLNMmm222SZ63NNPP5369u2bHxvPseGGG+YqqabEchE0haWWWiqNGDGirpIq3HrrrWn11VdP888/f7Ov9dFHH00/+clPmn0dMK2pbAIAAKDNi+qirl27pttvvz395z//ycPUtt122zTnnHM2OX2FFVZocX0ffvhhGjp0aNpjjz0m+dxffvllmnvuuevuzzPPPHnapNxzzz1p5ZVXrguf3n777fycxx57bLrllluafdwjjzyS/ud//meS64dpRWUTAAAAbV5UB33xxRdpoYUWSoMGDUq77rprHp42duzYJqePGjWq2XVFUBRD4SJoiqF108Jjjz2WK6L22muvfH/MmDHpz3/+cx5iV6nAasobb7yRh/Ktssoq02S7YHKobAIAAKDNi1AoQpq11147319sscVyI+8Ijpqa/sEHH6QVV1xxovXEsLbovbTNNttMdk+keO6orKqIoXcthVRPPvlkrlyKnlDR4yl89tlneVsHDhyY70fPqGg2/u2336b999+/7rExJHCdddZJHTuqLWHGcfQBAADQ5sVwuRga99JLL+X7Ef7Erbq6usnpUekULrnkkvTss8/WBU1R/TRgwIAc6Eyufv36pSFDhuTG3REQPfDAA6l///553ldffZUOP/zwumWfeuqp3GA8hsrVH3oXV8i77LLLctVV3OIqc3E1vPpBUwRQUQ0V02FGUtkEAABAuxDD3q644oq6q8/FELWePXs2Oz0MGzYsBzvh5ptvztVF9957b76FTTbZJK233np5mFuERuPGjcuhz4EHHpirpXbcccc033zzpe222y6deOKJ+TFx9bpoEl4Jmyo9mcLFF1+cq5niyngVETzNMccck3x9UREVV8dbYIEFCu85mDLCJgAAANqFCH2OP/74yZ7+9ddfp7nmmistscQS+X70S4pbU+IqdBdddFGzz73BBhvkW2Ovv/562mqrreru/+///u9kvZYIrxqLAKsSYsGMJGwCAACAZobeRVXRtBRD8qCt0bMJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKCYTuVWBTBzuPX6y9M2bxzZ8jLLnJV+udM+022bAAAA2gphE9DuRIg0dOj6qaampsn5VVVV6ZfV1dN9uwAAANoCw+gAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDGdyq0KAACgfdn8/EfTa5+MbnGZ5Rb4JN198DrTbZsAZjRhEwAAwFSKEGno0KGppqamyflVVVWpurra/gXaFcPoAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFdCq3KgAAgPanurp6Rm8CQKsibAIAANqkF154Id10002ptrY2jR8/Pm255ZZpnXXWqZv/6quvplNPPTX95je/SZttttlEj//ggw/SNddck0aNGpVmmWWWtOSSS6bdd989de7cOc+79NJL65b99ttv0/fff5+uuOKKNHr06LzeijFjxqTPP/88DR48OHXr1q3BcwwfPjxPf++999K8886bTjvttLp5EyZMSH/961/Tyy+/nDp27JjmmGOOtNdee6X5558/P+6QQw5JvXv3rls+7s8333xF9yHA1BA2AQAAbU4ETJdcckn6wx/+kAOZCGeOOOKItNpqq6UuXbqk7777Ll1//fVp5ZVXbnYds846a9ptt93y4yP4ueiii9Idd9yRtttuuzytfjB09dVXpw4dOuSfIxSqP++f//xnev311ycKmkJsy69+9ascVN14440N5g0ZMiS99dZbeV2dOnVK//jHP9Lf//73dPDBB9c9tv7zALQWejYBAABtUoQ/UXEUIsyJsCcCpBAVS9tss00OhpqzwAIL1FUORWXREksskb744ouJlhs7dmx6/PHH03rrrdfkeh5++OFm58U2LbPMMmm22WZrcvt/+OGHNG7cuByexWvo2bPnZL56gBlHZRMAANDmRFBz0EEHpXPPPTdVVVXl0CmGmUWF0NNPP53n9+3bNz377LOTtb6ampr00EMPpR133HGiebGOGAK32GKLTTQvKpPiuX/6059O8WuIx7z22mvpt7/9bX4Nc801VzrhhBMaDM87/vjjc9XVqquumrbeeuscigHMaMImAACgzYkeTbfddls69NBD07LLLpveeeeddPbZZ6dTTjklT4+QZnJFddGFF16YfvKTn+RheE1VLq2//vpNPjbm/exnP8s9n6bUu+++m/773//m4XsxZO6GG25IV111VTrggANSjx498vTu3bunb775Jl1wwQXprrvuSgMGDJji5wEoTewNAAC0Oe+//34aMWJEDppCNPeOIWjDhg3L04855pj0u9/9Llc53XrrrbkXUnNBUwQ5Ee7ssssuE82Pxt9vv/12WnPNNZushnrqqafSuuuuO1Wv4d///ndafvnlU9euXXPFUjQ3j0qnEMMBI2iqDMWLYXpvvPHGVD0PQGkqmwAAgDanV69eaeTIkemjjz5KCy20UPr000/TZ599loe6xdXfKuLnRRddtO5qdPfee28Oo2K4XFRHRUVThDlxFbhKA/DGlUsxhC0CocaefPLJ3PMpnr++qFCKIXGbbLJJi68hhua9+OKL+Sp6Mfzv+eefTwsvvHCeF1fIi+eM6dHT6ZlnnmlyGB/AjCBsAgAA2pyo+omAKKqSIiSKBttxZbm55567xcdFOBUhTyUsin5MERgde+yxeVqfPn3S7rvvnn+OXkmPPvpo2n///ZtcV3PD66LqavHFF6/ru3T44YfnwCiukHfggQemtddeO4ddG2+8cfr444/T0UcfnYfhRXXVHnvskR/35ptvpptvvjlXPEUoFhVQ0bMJoDUQNgEAAG1SDG1ranhbffvtt1+D+x988EHaaaed8s8R+sStORH0RN+k5px00kkTTYuAavTo0XW9n+IqdM2tI4bK7b333k3O69evX74BtEbCJgAAgP/PiSeeOE33RQRUAwcOtL+BNk2DcAAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAhE0AAAAAtD4qmwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABTTqdyqAACA9qK6unpGbwIArZTKJgAAAACKUdkEAABtyEsvvZRuvPHG9MMPP6TZZpst7bnnnmnRRRdNtbW16ZZbbklPPPFEmnXWWdMcc8yRjj/++Ike/9Zbb6Wrr746/xzrWHrppdOuu+6aH9PSvPDQQw+lO+64Iz/X8ssvn3bffffUqVPzpxyvvvpqOvXUU9NvfvObtNlmm+Vpn3zySX6Or7/+Oo0fPz798pe/TP3798/z7rvvvvTAAw+kjh075nkbbLBB2nTTTafJfgRg6gmbAACgjfjmm2/SxRdfnE444YS08MILpzfeeCPfP/PMM9M999yT/vvf/+afIwAaOXJkk+uIYOqUU07Jy0yYMCGdd9556f7770+bb755i/M+//zzdNNNN+XwqHv37unss89ODz74YNp4442bfJ7vvvsuXX/99WnllVduMP2yyy5L6667blp//fVz4BSBWIRaPXv2TGuvvXbd+uLxRx11VFpmmWXSYostNg32JgBTyzA6AABoIyLw6datWw6aQgQxX375ZRo2bFi666670o477lhXadSjR48m1xHVUJVlonpp7NixqUOHDpOc9/TTT6e+ffvm9ca0DTfcMFdRNeeaa65J22yzTa6wqu/999+vC6DmnHPO1Lt37/Tkk0/m+7PPPnvdcmPGjMnVTQC0PiqbAACgjZh//vlzdVMMd+vTp08aMmRI+v7773NF06hRo9Jzzz2XnnnmmbxsVCNVhqc1Nnz48FyZ9Nlnn6VVVlklbbTRRpOcF6HW3HPPXbfcPPPMk6c1JYKpCKQinHr22WcbzFt88cXTY489lgYMGJCfI15LrKv+Y2+++eY8b4cddlDVBNAKCZsAAKCNiMqfgw8+ON1www2ppqYmXzFuoYUWyvOiCmjcuHF5GFwERn/84x/TggsumIfGNRbhzumnn57XEcPwIqBac801JzlvcsTwvdtuu63JflFh//33T3/5y1/SMccck8OrFVZYIfdoqlh99dXzLV7DOeeckwOveB0AtB7CJgAAaEOiMXfcQoRLEd5ElVNVVVVaa6216gKjmPbuu+82GTZVxGOi+unxxx+fKFBqPK9Xr155GF9FhEExrbEY0jdixIgcJoXRo0fnCqzozxSVSrFthx56aN3yEWytuOKKE60nlltqqaXSCy+8IGwCaGX0bAIAgDYkgpyKf/zjHzl4iuF1EQy9/PLLeXoMtYugaZFFFsn3oxLq3nvvzT9/+umnuR9TiP/H0LvomzSpef369cuhUVQuxdXo4qpxlWF6X331VTr88MPzz1GJNHjw4HTBBRfkW1QpxRXnImgKMdwvmo9Xrqz30Ucf1YVkH374Yd1ri3AqrmZXeX4AWg+VTQAA0IZEP6O4Cl0ENjGMbp999snTozl4XOktrh4XoidSVAZVmnJHr6QQAU4ETzF0LYbexTC2aOQ9qXnzzTdf2m677dKJJ56Y7y+33HK5SXglbJplllkma/uff/75dMcdd+TnmGuuudLvf//71Llz5zwvrqj35ptv5iblEWhtttlmTVY9ATBjCZsAAKAN2XvvvZucHld9O+KIIyaaHqFUDGVbbbXV8v0IiCohUWMtzQsbbLBBvjX2+uuvp6222qrJx+y3334N7q+//vr51pS99tqr2ecGoPUQNgEAQDsWFUQDBw6cps8RVVQA7dUBfx2S7nrl0xaX2WLFr9PFv+6b2go9mwAAAAAoRmUTAAAAwDRy8a/7pkOGDk01NTVNzo+re0aPvbZEZRMAAAAAxQibAAAAAChG2AQAAABAMXo2AQDAZGprPTUAYFoQNgEAMNO59tpr05AhQ9IXX3yRTj311LTYYovl6Z988kkaPHhwGj16dJp99tnTfvvtlxZeeOFJzqvv2WefTTfffHPq2LFj+uGHH9Kqq66att9++9ShQ4cW540dOzZdeeWV6b333svrmXfeedM+++yT5pxzzomeI7Zj2LBheT2zzDJL2nHHHdMKK6yQ540aNSpdeuml6bPPPkuzzjpr2n333dOyyy6b57399tvpuuuuS+PGjcu3ddddNw0YMGCa7msAmFLCJgAAZjr9+vVLW265ZTrppJMaTL/qqqvSBhtskEOYp59+Ooc6AwcOnOS8+iL06du3b12gdOKJJ6Ylllgirbbaai3Oe+CBB3LgdMYZZ+Tw6Yorrkj//Oc/0//8z/9M9Bw777xz6tq1a/45wqlBgwalyy67LK/3hhtuSEsttVQ6+uij0zvvvJPOPffcdN5556VOnTrl17Dddtvlbfjmm2/SEUcckVZZZZUmQzMAmFH0bAIAYKYTlT69evVqMC0qgqJaaO21164LpL788sv06aeftjivsS5duuTQJ0R4FKHS5MwLY8aMSePHj8+3uMR1z549m9z+StAUvvvuuwbznnrqqfTzn/88/7zkkkumHj16pNdff71u/rffflv3XBFAdevWbTL3GgBMHyqbAABoE72KIjyKYCaGpYWoLopAKqZHSNTcvPnnn3+idb311lu5iijCqAh+YrjcpOZtuOGGaejQoXl4XgRSERRtvPHGzW7v9ddfnyusIjw65JBD8mNiiF8EVbGtFfPMM0/ezrDvvvumc845J910003p66+/TnvuuWeDZQGgNVDZBAAAjfTp0ycPh7vwwgtzRdQbb7wxyXmvvPJKmjBhQrrkkkvyLaqXor9Tc3baaac8PO53v/tdDp4aV0k15Y477kg77LBDfu4zzzwz3XjjjenDDz/0/gHQqqhsAgBo442xm3tMY0888US68847c2VNiN5GW2yxRf55+PDhef3RXygaX5922ml1j4uAJcKSl156KT926aWXTnvssUce4tXYww8/nP7v//4vffTRR+nXv/512myzzermRcVOPE/FBx98kA477LDcn+jqq6/OFUVhr732ykPYYjvqi0qlkSNH5m2ICqba2tpcERTTo7KpuXktiebeK6+8cq5AqjTpbm7egw8+mNZcc83UuXPnPH+ttdZKt99+e5qUFVdcMV1zzTX59Ub/p9i+2NZKxVLsk9jOqGR67rnncjgV5ptvvtzbKfaLnk0AtCYqmwAAZiLRa+iPf/xjmnvuuRtMrzS/jsAmrk4WwdCkHtNYBBpHHXVUrpiJxtf/+te/0muvvZbnRVjzq1/9Kh144IFNBkhR4RNB1p/+9Kc8RC0CpaYsvvjiOSyJUKaxCJYixIrb3nvvnSuDVlpppTwvrshWmde7d+88rfFV3rp3756DtMceeyzff+aZZ3LPpBgm19K8EJVIcaW5EEFYBGjh+++/Ty+88ELdc7Y0L8KvqG6KICtuMa8SAn311Vfp8MMPzz9HBVP9XlFxhbkIkirh2eqrr573fYgG4SNGjMhhVvRmmm222dKrr76a58VjYr6gCYDWRmUTAMBMpHF1Tag0vz7mmGPqwqWolIlAI8KUph7TlKhIqojqqAUXXLCu0iiCjmWWWaYufKrv/fffz1dpq1QyRUB0yy235NCrsUUXXTT/v9JkuzkPPfRQbubdVHVUvN577703XXzxxbkC6PTTT89hWFy1LXoYRdAWFUUxLXocVbQ0L/bfpptuWtegO25RYRTBUuzP9ddff5Lztt1223TllVem3//+9/l+7L94zkrYVOkXFdVVsR3RGDymRYAUPZsqjb5jeF2EX4ceemh+/b/97W/r9kMEdX/961/zc8d6YptjWB8AtCbCJgCAmVxLjbGban49OaIPUDS7juFwkxLVSjGELJphxxCyCGNiyN7UiiFyTz75ZDrhhBOanB8BU4Q4c8wx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XFp88cXTcsstV/f4u+++O80333w5bGpOBEsV7777bt6OlVZaKd+/7bbb0vzzz5+OPPLINGHChHTmmWemRx55JG200Ub5eXfeeeccNr344os/ej8CAEwPhtEBAK3esssum4OfxiphUogwKKqRJmdefV26dMlBUxg7dmwOnCqWXnrpHPiE2WefPS244IINKpQ+/PDDXHG11VZbTfZreeihh9Laa6+dOnX6f3/zi+2qqanJQVM8d2xDz54987x4zUsttVTdsgAAMwP/cgEAZmqXXHJJeu211/LPv//97yd7Xn1vvfVWuuqqq9Knn36afv7zn+ehdI1FsDR06NC0xx575PsRDF1xxRVpn332qQurJiWCpCeffDKdcMIJddO22WabdN5556Xf/va3ef6aa66ZK60AAGZWKpsAgJlahDQXXXRR7rN0/fXXT/a8+vr06ZPOOOOMdOGFF6Zhw4alN954o8H8L7/8Mp199tk5aKpUWN166615qN1CCy002dv69NNP5yFzvXv3rpsW4VOsI4Kxiy++OAdeUf0EADCzEjYBAG3COuusk6uYRo8ePUXz6ptzzjnTyiuvnEOhihEjRuSm5FGBtMYaa9RNf/3119N9992Xe0NFL6nvv/8+/xwNv5vz8MMPp/XXX7/BtPvvvz+ttdZauToqhvRFf6pXX311Cl89AEDrIWwCAGZK3377bQ6CKqIJ+BxzzJG6devW4rwQVUQxLXz00Ue5X1KIwOiFF16oqzyKdcSV5gYMGJADq/r++Mc/pgsuuCDf4ucIiuLnCKxCXDnuq6++qls+KpaiOXj//v0brGfeeedNL730Ut3QvJdffjktssgixfcXAMD0omcTANDqXXnllflqbCNHjsxXcotg59hjj03nn39+GjduXG6yHWHSEUcckX+OhuDNzQsxVG7TTTfNPz/11FP5Nssss+TQKSqLKtVHN998cx5Cd++99+Zb2GSTTdJ6663X4vaOGjUqffPNN3XhVqWqKdYdjcbr22WXXXK/qLjqXTx/dXV12myzzfK8jz/+OFdVjRkzJvdzOvDAA9MvfvGLfKU6AIDWqkNtbW1tamOieWdc1aUpVVVV+R9x7YH9YF84Lnw+fFf43vT7Y2IxzC36OEVYNa1EePXJJ5/koXcAAEPbWU6hsgkAaFdimNu0DJpC/d5OAADtjZ5NAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxXQqtyqgtbvuiWHphDveanGZk7fqlHZZc/Hptk0AAAC0LSqbAAAAAChGZRO0I1Gx1H+eH1JNTU2T86uqqlJ1taomAAAApp7KJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDGdyq0KAJjZ3XDR8WmH4Re2vMw8B6UdDxw43bYJAICZi7AJAKgTIdLQobummpqaJvdKVVVV2rG62h4DAKBZhtEBAAAAUIywCQAAAIBihE0AAAAAFKNnEwD8f6r1IgIAgB9NZRMAAAAAxahsAqDVufbaa9OQIUPSF198kU499dS02GKLNZj/8MMPp8svvzwdeuihabXVVsvTamtr0y233JKeeOKJNOuss6Y55pgjHX/88ROt+9VXX03XX399GjNmTL6/yiqrpB133DF17NgxDR8+PB1yyCGpd+/edcvH/fnmmy//fOedd6Z///vf+bkWWGCBtO+++6auXbtO9Bz33XdfeuCBB/I6x48fnzbYYIO06aab5nn33HNPevDBB1OHDh3y/QEDBqS11147/xzbHs8Rjwnrrrtu2mKLLYrtVwAAmB6ETQC0Ov369UtbbrllOumkkyaaF4HQQw89lJZaaqkG0yPE+e9//5vOPPPM1KlTpzRy5Mgm1x3h0EEHHZQDpLFjx+YwKwKkCHZCly5d0mmnnTbR41555ZX06KOPppNPPjkv849//CPdeOONaffdd59o2QiPNt544/zzd999l4466qi0zDLL5NBs4YUXTieeeGKaffbZ05dffpmOOeaYPHwvtqdXr1552R49euTHHXfccWnxxRdPyy233FTvSwAAmN4MowOg1Vl22WVz8NLYhAkTckXTrrvumquX6rvrrrtyhVIETSECm6ZE4FOpVOrcuXNadNFFc4A1Ke+//37q06dPDprCyiuvnB577LEml40gqSIqqCqVSmGFFVaomx+vMbYzQqew9NJL1213LLPgggtO1rYBAEBrorIJgJnG3XffnQOZJZZYosH0qAIaNWpUeu6559IzzzyTp22++eapf//+La4vqp9i+SOPPLJBOBTD7yLYWnXVVdPWW2+dh8NFhdH999+fH9O9e/f0+OOPp++//z598803qVu3bhOt++mnn04333xz+uyzz9IOO+ww0VDASrXUt99+O9HrCR9++GEaOnRo2mOPPaZoHwEAwIwmbAJgphBD5CIYOuGEEyaaF8FQVA+NGzcunXLKKbka6I9//GOuDIrKpaZEQPWnP/0pD9erhD1RVXTRRRflMClCpAsuuCBXTEVfpeWXXz4ve9ZZZ+XwqdIrKn5uyuqrr55vsS3nnHNO7g0V21PxwQcfpMsuuywP6auqqmrw2Kh0Ovvss3PQ1FSFFwAAtGbCJgBmCm+88UZuGH7YYYfl+1HJdNVVV+VKo4022igHNmuttVaeN8888+Qhb++++26TYVNUJJ1xxhmpb9++DRpwx9C8CJpCVCutt956uYIpwqYQzxO3EFVHPXv2bDBkrimxLdFf6oUXXqgLm6JqKYKuaDAevZzqGzFiRO4jtc0226Q11ljjR+41AACY/vRsAmCmECHPJZdckquN4hYBzp577lkX/sSQuZdffjn/HFVJETQtssgi+f4NN9yQ7r333vxzTU1NDppWWmmlHOjUFwHWDz/8kH+OKqmopKo//C2CoMpQuxgiVwmhwqBBg9Lbb79dFyZVfP311/kKeJUr3H300Ue5iXls+4orrtjg+WP9sZ5Y7zrrrFN0/wEAwPSisgmAVufKK69ML774Yq5aOv3003NT7nPPPbfFx0Rz8BiWFn2VQgQ2lSvWRXPv6LlUuWrdO++8kwOjZ599Nk+L4W7Rm+nNN9/MIVIMjYtheTF0LqZXxLbEkL0IpH72s5/VXXEupsVzVIa8xXPEuqJZeW1tbdpss83qgqVrr702D+GLACxulW2P8CueO4bQRTBWCcc22WSTXGEFAAAziw618a/gNiaGNsRfrpsSwyziEtPtgf1gXzgufD58V/y/ICj6PJ188snN9lf6sSK8euCBB9I+++yT2gK/PwAA/Pvqx1DZBECbFgHTwIEDp+lzLLnkkvkGAADo2QQAAABAQRqEAwAAAFCMsAkAAACAYtpcz6bNz380vfbJ6BaXWW6BT9LdB7ukNAAAAEBpKpsAAAAAKKbNVTZFxZJLNgMAAADMGCqbAAAAACimzVU2AQA/TnV1tV0IAMBUU9kEAAAAQDEqmwCglbr22mvTkCFD0hdffJFOPfXUtNhii+Xpv/vd79Kss86aOnfunO9vtdVWqX///vnnTz75JA0ePDiNHj06zT777Gm//fZLCy+8cJPrf/3119Nf//rXNGbMmHx/7733Tn369EnvvPNOuu6669L777+fVlxxxXT44YfXPea2225LTz/9dN39zz//PK233npp5513nmj9sR1vvfVW3s6qqqq8zJJLLpnnnXPOOWn48OF1y37wwQfpsMMOS3379q2bNmrUqHTUUUflSqv62wAAQOsmbAKAVqpfv35pyy23TCeddNJE8w466KC68Km+q666Km2wwQZp3XXXzaFQBD4DBw6caLkRI0akSy+9NIc5Cy20UBo3blwaO3ZsntejR48cDEXY9OKLLzZ43NZbb51vIR5zwAEHpLXWWqvJ7V9ttdVygDXLLLOk559/Pp1//vnpggsuyPMiWKp499130+mnn55WWmmliV7LT3/60xycAQAw8zCMDgBaqWWXXTb16tVrspePSqBhw4altddeuy6s+vLLL9Onn3460bL3339/WnPNNXPQFKJSqmvXrvnneM6llloqderU8t+knnvuubzsEkss0eT8qFKKoCnE+iLgGj9+/ETLPfTQQ3mb6z9fTJtnnnnS0ksvPdmvHwCA1kHYBAAzoUpV0uWXX56+/vrrPC2CpahKqgQ8HTp0yGFQTG/so48+ypVJgwYNSsccc0y65pprUk1NzRRtw8MPP5yH0E2Oe+65J6288sp121YR1VRPPvlkg/XE0LwHHngg7bDDDlO0PQAAtA6G0QG0Y8effkY65ftTW16my7Fp4NFHTbdtYtJOOOGENPfcc6cffvgh3XTTTXXB05SICqPo2XTsscfmfkqXXXZZuuWWW9Kvf/3ryXp89Ft6880383C+SXnsscfykL7Y7sZi+vzzz5969+6d79fW1uYAbbfddqvrSQUAwMxF2AQAM5kImkIMO9t0003rmmdHFdPIkSNzkBQVRBHcRFVTU0PxYtqiiy6aunXrlu/HkLrbb799srfhkUceycPkKo9vTlQtRYh13HHHpe7duzdZHbX++uvX3f/+++9zs/BKb6eotorqp6jAinUAAND6CZsA2rGoWBo69JfNDp+KipeB1dXTfbtoXrxXESZV+is98cQTdY3CI8yJn6OSKBqEP/PMM6lnz565cihccskluWl33KKp9/XXX5+H0kW/pmgEHuHT5JgwYUJ69NFHc/Pv+r766qscCp199tn5/lNPPZVuvPHGXD1VCcjqi15S0Ry8/pXm4gp6UdlUP9SK3lCuRgcAMPMQNgFAK3XllVfmECiqleJqbV26dElHH310Ou+883LgE+add960//771z1mzz33zFegiyqlWH7fffetmxfNw6MSKvTp0ydf6S2CoI4dO+ZG4fHY8PHHH6dTTz01jRkzJlcVHXjggekXv/hF2mijjfL8//znP7kf1AorrDBR2FS/J9PFF1+cA7Bzzjmnblo83xxzzFFX1RRNzCNgAgCg7ehQGzX2bczQoUNb/Ct9dTv5K739YF84Lnw+fFdQEU3EL7roohz2TCt33nlnmmuuuequhgcAQPs8P1fZBADtwJxzzjlNg6YwYMCAabp+AABmDh1n9AYAAAAA0HYImwAAAAAoRtgEAAAAQDF6NgEAAABMQ9VtrAH4pKhsAgAAAKAYlU0AAABAu3DttdemIUOGpC+++CKdeuqpabHFFsvTf/e736VZZ501de7cOd/faqutUv/+/Rs89uGHH06XX355OvTQQ9Nqq6020bpfffXVdP3116cxY8bk+6usskracccdU8eOHdPw4cPTIYccknr37l23fNyfb7758s933nln+ve//51qa2vTAgsskPbdd9/UtWvXJl/DM888k2655Za8bDjyyCPTPPPMk+6555704IMPpg4dOtRdKXjttdfOPz/yyCPpuuuuS/POO2++H+s+/vjj07QibAIAAADahX79+qUtt9wynXTSSRPNO+igg+rCp8YiLHrooYfSUkst1ey6I8CJdcw333xp7NixOcyKAGndddfN87t06ZJOO+20iR73yiuvpEcffTSdfPLJeZl//OMf6cYbb0y77777RMu+99576e9//3sOiuaaa670/fff5zArLLzwwunEE09Ms88+e/ryyy/TMccck4fvVQKt5ZZbLh1++OFpejCMDgAAAGgXll122dSrV68pesyECRNyRdOuu+6aq5+aE0FVJdjp3LlzWnTRRXNINSnvv/9+6tOnTw6awsorr5wee+yxJpe966670uabb56DphCPmW222fLPK6ywQg6aQrzGHj165NBpRlDZBAAAALR7l156ad4HSy65ZB7+Nuecc+b7d999d1p66aXTEkssMdn7aOTIkXm4Wwxxq4jhdVGRFOHVqquumrbeeutclbT44oun+++/Pz+me/fu6fHHH88VS998803q1q1bg/V+9NFHechcVEHFMjFUb7vttqurbqpfLfXtt9822OY333wzVztFELbZZpulNdZYY5q958ImoN254aLj0w7DL2x5mXkOSjseOHC6bRMAADDjnHDCCWnuuedOP/zwQ7rpppty8HTUUUel//73vzk0ivmT67vvvkt/+tOf8nC9StgTVUYXXXRRDpMiRLrgggtylVL0VVp++eXzsmeddVYOjSr9oBoHSGH8+PF5KN3RRx+dQ6uzzz47B1WbbLJJ3TIffPBBuuyyy/KQvqqqqjwtQqkIl6IKKgKrGM4X1U/T6ip5wiYAAACgXYugKXTq1Cltuummdb2N3njjjdxM/LDDDsv3R40ala666qpchbTRRhtNtJ6oNjrjjDNS37590xZbbFE3PYbfRdAUolppvfXWyxVMETaFWFdlfUOHDk09e/asGxLXeDsjjKo0Mo+fY/lK2PThhx/moCsajC+zzDJ1j6tUaYWFFlooD9V76623hE0ApUTF0tChu6aampom50f6v+M0SvgBAIDWJc4LomKocvW3J554oq5ReP0QKJxyyik5jKpUH91www25f1KEPbGeCJpWWmmltM022zR4jgipYv0RZo0bNy5XS9VvRj5ixIi8nhhqd/PNN9eFUGHQoEFphx12yM3J11xzzXw1vXXWWSdfjS6Gy0W/pxAVS2eeeWbac88904orrtjg+b/66qscYFW2Ja6c1/hqeyWpbAIAAADahSuvvDK9+OKLuTLp9NNPzw22Y0jaeeedl4elhXnnnTftv//+k7W+aO4dPZfCPffck955550cGD377LN52uqrr557M0W/pAiRYmhcBFsxdC6mV8S2xPPHML6f/exnaeONN87TY1o8R6WpeQREMYzu97//fV5X9JKK/kvh2muvzUP4IgCLW4jeUxF+3XfffTmkirAr1hlNxmMbppUOtRGFtTFRQtZSxcK0GpPY2tgP9oXjwufDdwUAAEwbEdpEL6do1t1Uf6USIrx64IEH0j777JNmJiqbAAAAAKZQBEwDB07biwotueSS+TazmTbRGwAAAADtkrAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDGdyq0KgJlRdXX1jN4EAACgDVHZBAAAAEAxKpsAWolrr702DRkyJH3xxRfp1FNPTYsttliePm7cuPSXv/wlvfzyy6lz586pd+/e6YADDsjzPvnkkzR48OA0evToNPvss6f99tsvLbzwwhOt+5FHHknXXXddmnfeefP9rl27puOPP75u/uuvv57++te/pjFjxuT7e++9d+rTp0+67bbb0tNPP1233Oeff57WW2+9tPPOOzf7Ol599dW8/b/5zW/SZpttlqfdcMMN6dlnn02zzjprmmWWWdL222+fVlpppTzvvvvuSw888EDq2LFjGj9+fNpggw3SpptuWmivAgAA05uwCaCV6NevX9pyyy3TSSed1GB6BDUdOnRI55xzTv7/yJEj6+ZdddVVOZxZd911cygUwdPAgQObXP9yyy2XDj/88ImmjxgxIl166aXpqKOOSgsttFAOt8aOHZvnbb311vkWYnqEXGuttVazr+G7775L119/fVp55ZUbTF9mmWXSL3/5yxyWvf/+++nkk09OF198caqqqkprr7122njjjeseH9sRy1fCNgAAYOZiGB1AK7HsssumXr16NZhWU1OTHn744VwJFEFT6NGjR/7/qFGj0rBhw3JYUwmrvvzyy/Tpp59O0fPef//9ac0118xBU4jqo6h8auy5557L27fEEks0u65rrrkmbbPNNmmOOeZoMD3CpwiawiKLLJL/H9VYISqyKqKyKqqbAACAmZfKJoBWLIatRfBz++23p//85z85sNl2223TCiuskIOlCJ5iWFqIMCrCoJg+//zzT7SuN998Mx1zzDF5HTG8bY011sjTP/roozT33HOnQYMGpW+++SYtvfTSaccdd8xVR/VF6BVD6JoTlVWxDX379s1D5poTQ/piOF88Z/3H3nzzzemzzz5LO+ywg6omAACYiQmbAFqxqPKJHk5RdbTTTjul9957L/dDOuuss6ZoPausskoOl2abbbYcLp122mk5mIor0cVzRM+mY489NgdMl112WbrlllvSr3/967rHDx8+PIdVBx10UJPrj6F90d+pfh+opkRgduutt+bQq1KpFVZfffV8i+eJ4YKxvQsuuOAUvUYAAKB1EDYBtGIRCEUoUxkqF32Moirogw8+yI3CI+SJsCiqm2pra3NVU+OheGHOOees+zmCqxjW9tZbb+WwKZZfdNFFU7du3fL8GFIXlVSNq5GiYqmyTGMxnC96P0WIVBkiF83Ov/7661ypFCLQiiDriCOOaDZImmeeedJSSy2VXnjhBWETAADMpPRsAmjFIiSKIXMvvfRS3bC6uEVg1L179xw+PfbYY3neM888k3r27Fk3hO6SSy6pG8721Vdf1a0zej3FFeMqDbij4fdrr72WG4CHF198MYdPFRMmTEiPPvroREPoYp2VhuNRiRTNyS+44IJ8iyqlaAheP2iK7Ynl6687fPjhh3U/RzgV2xZBGgAAMHNS2QTQSlx55ZU56IlqpdNPPz116dIlnXvuuWmPPfZIV1xxRd1V6fbaa68cKoU999wzhzxRiRTL77vvvg2qjTbddNP883333ZcrjTp16pTDo8033zwtv/zyeV6fPn3ST3/60zyMrmPHjjnIivXWH/oWzxuhV+OwqdIvalIuv/zyHGZFZVPF/vvvn0Ole+65Jw/Ri22L6qzoJ7Xiiiv+yL0JAADMKB1q41/2bczQoUPzFZyaEv1IYthIe2A/2BeOi/b7+YgKoYsuuigHSNPKnXfemeaaa666IX4AAABBZRNAGx1+Ny2DpjBgwIBpun4AAGDmpGcTAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQTKdyqwKYeVRXV8/oTQAAAGiTVDYBAAAAUIzKJmCGuvbaa9OQIUPSF198kU499dS02GKLpdGjR+efK8aMGZM+//zzNHjw4NStW7f8/7feeit17tw5VVVVpZ133jktueSSE627pqYmnXvuuWnYsGFpwoQJ6corr6yb98EHH6RrrrkmjRo1Ks0yyyz58bvvvnteZ/jmm2/y/HfffTfP/+lPf5p22mmniZ7j66+/Tpdddlne/vHjx+f17Lnnnnk9t912W3r66afrlo3XsN566+XtDQ899FC64447Um1tbVp++eXz83fq5GsZAACYuTmrAWaofv36pS233DKddNJJddPmmGOOdNppp9Xd/+c//5lef/31HDSF1VZbLe299945BHr++efT+eefny644IKJ1h3zt9pqq9S1a9c0cODABvNmnXXWtNtuu6XevXvnIOqiiy7Kwc92222X519++eWpT58+6cADD8z3R44c2eT2R6A0//zzpyOPPDKv58wzz0yPPPJI2mijjdLWW2+db2HcuHHpgAMOSGuttVZd8HTTTTflUK179+7p7LPPTg8++GDaeOONC+xVAACAGccwOmCGWnbZZVOvXr1aXObhhx/OFUEVffv2zUFSWGqppdKIESNyVVFjEShFxVCETY0tsMACOWgKHTt2TEsssUSuTgqffvpprmjafPPN65bv0aNHk9vWoUOHXEEVQdMPP/yQxo4dm3r27DnRcs8991x+nfE8ISqe4nXEemMdG264YXriiSda3A8AAAAzA5VNQKsWw+W+/fbbPIytKffcc09aeeWV68KnqRFhUQxp23HHHfP9jz76KAdGf/7zn3PoFJVWMYQuhvg1ts0226Tzzjsv/fa3v81B05prrplDpEkFZl9++WWae+656+7PM888eRoAAMDMTmUT0KpFSPOzn/2syTDpscceyxVCe+2111SvP6qRLrzwwvSTn/wkD88LUSX1zjvvpP79++dhbptttlk666yz8rKNPfnkk2mhhRZKl1xySbr44otzVVQEV/UNHz48vfnmm3VD6AAAANoyYRPQakXF0VNPPZXWXXfdJkOeW265JR1zzDG559HUiPAoej3FULZddtmlbnpUHEVlUwzBC1E5FctWhtnVd//99+cQKYbidenSJfegevXVVxssEz2cotqp0nMqxJC6+uuLQGpSwwkBAABmBsImoNWKQCn6KkXlUH0RQN14443p2GOPbTAULdx7773phhtumOS6o3opKpoiAIrKqOibVLH44ovn4CiuWBfefvvtfMW4Shg0aNCgPC3MO++86aWXXso/RyD18ssvp0UWWaRuXdHL6dFHH20whC5EKBVX4YvG47HuBx54IFdSAQAAzOz0bAJmqCuvvDK9+OKLOXQ5/fTTc8hz7rnn1g2hW3/99Sd6TAxXi2qmc845p25aBE/RWyn6LUUAVHHUUUel0aNHp++//z5fWW655ZbL/ZUiyHr22WdzmBWPDXH1ud133z0HT/vtt1+64oorch+maDR+6KGH5v9HePT+++/XBU9REXXVVVfl54l51dXVedhdxX/+85+8vhVWWKHBa5hvvvnyle9OPPHEfD+2K5qEAwAAzOw61Maf1NuYoUOH5uE3Tamqqsong+2B/WBftMfjIsKbCH4itJoWopdTVCHts88+02T9AAAAMzuVTUCbUqkUmlaWXHLJfAMAAKBpejYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxXQqtypgZlBdXT2jNwEAAIA2TGUTAAAAAMUImwAA4P/X3p1A6zXd/+PfmRORRJCYh4iYEjXE0FQpSpWf0tKiakzNU6k5Gloh1RJa1BBzS801t1S1KmpW8zyHEEQjA4mEnP/67O967v9mFGyRPM/rtdZdufeZn51z9jnnfT57HwCgGMPo4CtyySWXpIcffjiNGjUqDR48OC277LL59kceeSRdffXVqaqq9Mknn6Qtt9wybbDBBvm+c845Jz3//POpbdu2qX379mnnnXdOPXv2nOHr//e//02XXXZZmjJlSlp66aXT3nvvneabb76pHhOvd9ddd6XzzjsvdezYMd+24447pqWWWiq1bPl/WfSuu+6aVlpppZl+jzFjxqQjjzwyD8879NBD820PPvhguuaaa/JrfPzxx2mttdZK2223XWrRokW6/vrr0/3339/0/HfeeSdtuOGG+bsAAAAw7xM2wVdknXXWyUHSr371q6bbImA666yz0sCBA3NA9O6776bDDjssrb322qlDhw753z333DO1atUqh0m///3v0+mnnz7da0+cODENHTo0v84SSyyRLrroonTdddeln/zkJ02PeeCBB/LrzMixxx7bFD59mgsuuCCtueaaady4cU239enTJ/Xt27cpbPrlL3+Zlltuufz5v//97+efMHny5LT//vun9dZb7zO1HQAAAHMvw+jgK7LyyiunhRZaaLrbo/rngw8+yL9PmDAhzT///KlNmzb57whwagHR8ssvn0aPHp2rn6b16KOP5kqpCJrCpptumu69996pqpFuuOGGtNNOO32h7/Cvf/0rdevWLa244opT3R7BWK0yatKkSTlwmpGHHnoot0EEUQAAANQHlU0wF4mg6cADD0ynnXZaHiYXodPBBx+cWreeflW99dZb0+qrrz7D6qT33nsvLbzwwk1/RyBUC6bi8TFsLobLRSg0IyeeeGIefte7d+/0ox/9KH+WacXwtzvuuCNXQTUPsmpiuF9UPY0cOTJtsskmeSjdtO688848hA4AAID6obIJ5iIRBsWcRoccckgeHjdgwIB09tlnp7Fjx071uLvvvjvPe7THHnt8rmqkqCaKIGlG4n1jDqkY+hZD4/785z9P95gY7hfD9Hbbbbc8f9SMrLDCCuk3v/lNOuOMM9Irr7ySnn322anujyGCzz33nCF0AAAAdUZlE8xFXnvttVyBFEPsQkz+veCCC+bbV1111XxbVBFde+216ZhjjkldunSZ4etEmPTEE09MFex07do1VzU99dRTOfiJichrjjrqqDy5dwy9q1VERTVTVCRFddK0Ynjf8OHDm+aLijmiYrhcVETF52quc+fOuQIrwrHa9wr//ve/87DAGCYIAABA/RA2wVwkQqL3338/jRgxIs+3FEPQ3n777bTYYovl+++777501VVX5Yqn5sPkwm233ZaDqh122CGtttpq6eKLL256ndtvvz3169cvP+6AAw6Y6nkxnO6kk07KE4KPHz8+zw/Vrl27PIwu3m+ZZZZpemyESdtvv32eLyoqm5oHRzH/Uu1qdPG+8Zlj3qYIpiLYWn/99ZseH68dV8GLyc4BAACoL8Im+Iqcf/75eSLvCJci7In5k2KuphgaFxVDMX9TDFeLoWq1YOkPf/hDrmY69dRTm14ngqdOnTrlgKd79+75tnitCHLicRHsLLnkkmnffff91M/05ptv5kqmeO8Y0tejR4+0yy675PvidaLCakaTmk8rQqr4iUqqeF5ceW+jjTZquv/JJ5/M7xFXrQMAAKC+tKjiaLbOvPDCC3lYz4zE0KBevXqlRqAdGqstYo6lI488cqaTfn9RL730Up4QfK+99vpSXh8AAID6oLIJ6kSETV+mmD8qfgAAAGBWXI0OAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxQibAAAAAChG2AQAAABAMcImAAAAAIoRNgEAAABQjLAJAAAAgGKETQAAAAAUI2wCAAAAoBhhEwAAAADFCJsAAAAAKEbYBAAAAEAxwiYAAAAAihE2AQAAAFCMsAkAAACAYoRNAAAAABQjbAIAAACgGGETAAAAAMUImwAAAAAoRtgEAAAAQDHCJgAAAACKETYBAAAAUIywCQAAAIBihE0AAAAAFCNsAgAAAKAYYRMAAAAAxbQu91Iw9+rVq9dX/REAAACgIahsAgAAAKAYlU3MUZMmTUpnnHFGGjFiRGrbtm3q3Llz6t+/f1p00UXTmDFj0tlnn53efvvt1KZNm7T77runlVdeOT/v+uuvT8OGDUsjR45MBx98cFp77bVn+PrvvvtuOuecc9Krr76aunfvnn79619Pdf+//vWvdOONN6aqqlLv3r3ze7Ru/X+rwfDhw9Mll1ySP0fYbrvt0jrrrDPde8Rzr7322nTPPffkz9mpU6f0i1/8It83q+8waNCgNGrUqDTffPPlv9dff/20xRZbFG1fAAAA+KoJm5jjNt5447T66qunFi1apNtuuy2dd955aeDAgemKK65Iyy+/fDrqqKPSSy+9lE477bT0u9/9LodBffr0Sf369UtDhw6d5Wt36NAh/ehHP0oTJkxIV1111VT3vfPOO+nqq69OgwcPTl26dElDhgxJ//znP9N3vvOd9NFHH+W/991337TSSiulKVOmpPHjx8/wPW699db0+uuvp9/+9rf5s73//vtN983qO4SddtpppkEZAAAA1APD6JijopppjTXWyEFTbS6lqEYK9913X9pkk03y7z179kwLLLBAeuaZZ/LfEeAsssgin/r6888/fw6L2rVrN919999/f+rbt29+3Xj/b3/727k6KcS/8VniuaFly5a56mpGbrnllrTDDjs0BUjxejWz+g4AAADQCFQ28ZWKKqEIgMaNG5c++eSTqYKbbt26pffee6/Ye8VrLbzwwjN8/TfeeCOHRyeffHL63//+l5ZaaqlchTRt4PThhx/moXIPPfRQeuCBB/JtMRQuqq5m5ztE5dM111yTllhiibT99tvPVoAGAAAA8xKVTXxlYh6mmIMpqoS+ajFs7sknn0w//elP8zC7BRdcMF144YUzfFwESpMnT85zMB100EHpT3/6U3rttdc+9T3222+/PFTvpJNOSiuuuGI65ZRTvqRvAwAAAF8dYRNfiZtvvjk9+OCD6cgjj8xD3mKS7VatWk01/1EMr1tooYWKvWe8VkzQPaPXj39XWWWVHDLFELtvfvOb6cUXX5zhML327dun9dZbr6lyaYUVVkgvv/zyp36H2r/x+ptttlmeQyqqoQAAAKCeCJuY42LOo5gj6eijj04dO3Zsun3ddddN//jHP/LvMbn26NGjm67kNisxNC0mGv80cWW5hx9+OIdBcUW5O+64Iw9/C1//+tdzYBTD5MIjjzySll566fx7DKs79NBDm14nnvP444/n32MS8XheDLub1XeIaqjaVe5CDMGLScojoAIAAIB60qKKo+4688ILL6SJEyfO8L6oSomJoBvB3NgOMX/RgQcemLp3756vHBdirqQYkhZhzFlnnZUrfuK23XbbLfXu3Ts/5rrrrsvh0NixY/Nnj4nGY7hbzKn0m9/8Jm244YY56ImrykUwFMPcIjiKQCeqlGpD9eLqczfeeGP+PSqZ+vfv3zTR97Bhw9JNN92UK4+iwmmPPfbI1UhR4RRXwYurz4WoRjr33HPz5wybbrpp/gkz+w7x/xDf8eOP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i0cffTStueaa6T//+U9ul9p9jdYOTz75ZNp6663Teuutl7bZZpt0/vnnp0ZTa4sIpH/2s5+lzTffPA0aNCjdeOONDbddbd4WO+ywQ95+9O3bN/3rX//K9zdKO9T+30ePHp2++c1vphNPPLFp3Yj2aaR2aB409evXLy200EJT3d8obVELH6PPXH/99VP79u3TwgsvnFq3bp0aTfNlIvYp3n333XwSq3Z7I6l95yeeeCJtsskmadNNN019+vRJRx11VG6fRlpHmu9XbLnllnk9WWONNdKll16aXn/99dQoau0QJyruuOOOr/rjzFVi/Xj//ffzqIJG2ueekZbzykYvAoXevXungQMHpgceeKDhdoyn7dyiY4uDxzhDv//++6czzzyz6TGNttGLHeQYNrbWWmul2267LV199dWpEQOFr3/96+nwww9PAwYMSI0q2iLWiW233Taflf7d736Xll122XxfLXQK9d5vNN9B/sY3vpH7zQgV4ueNN95oqJ3kWqAQZ6TjYOHoo4/OO4VxdjrOztaWhXpfJkLt7HwsE3E2duWVV073339/OuCAA9Jvf/vbhtmu1vYromojlovFF188/eQnP8l9xXbbbZdPbDXaGerx48fnNuncuXM+YLrgggtyxXC0QyNUtDTvMyOQjv2qX//611M9plGWifieY8aMSfvuu2/aaaed0tChQ9PSSy+dhg8fnoeQvffee/lx9d5PTBs+/vznP88nvGO7cd5556VGUzvRHUFT7G/G/tUee+yRT1bEie977723IbYfoRawxLHYcsstl/r375+Dt9jHiv2tODapd7XtaFTOx75EhCs333xzakTNj71r24muXbvmtnn99dcbap97hqp5wNtvv12tv/761cYbb1ytsMIK1bbbblvdf//9TfdPmTKlahTDhw+vVl555erII49suu2KK66oOnToUL3wwgtVI3nllVeqHj16VEcffXTTbSeddFL1k5/8pBo/fnz1ySefNMQyEv/vLVu2rE488cT896RJk6qrr766+vWvf11dc8011VNPPVU1kn/84x/VeuutV3388ce5LQ499NBq0003rbbccsvqlFNOaYhlIjz66KNVu3btmtaPYcOGVT179qz+9Kc/5b+brx/17KOPPqp22WWXat9992267W9/+1u1xRZbVG+++Wb18ssvN8wy8eGHH1Y//OEP8zrRvP+I5aJFixbVgAEDqkbx7rvvVhtssEH1s5/9bKrbe/fuXQ0aNKghlodp7bnnntU///nP6sc//nHVr1+/6pJLLsm3P/nkk1UjeOONN6oFFlig2n777fPfsf2IdSL+/ta3vlVdfvnl1YgRI6pGMHr06Lwdjf4h2mHrrbeu1l577apbt27Vt7/97er2229viHXkkUceyfvXtb5x7Nix+Xgk2qOR1P6ff/GLX1TbbbfdVPcddNBBefsRx2f/+c9/qkZpi5NPPjnvWzY3dOjQvHzEdvaZZ56pGqGf+P73v5+3GbvvvnteV2644YaqEcUx6Y033pi3I7Hf+cEHH1Rf//rXq9tuu61puYnjktrvjWSeiNoiFYwzj0OGDEmXXXZZHgoSZ2EbrcIpktNbb701rbLKKunAAw9smnRss802S8sss0zT2aZGEN87KruifPXggw9uuj3OSEcVXAyHiDPV5557bl2fkYyKrlgmYlmIdSTE3DwxKd2FF16Y9tlnn9w+N9xwQ2oUzz33XG6XmLcqhglFJWBUvcU6cuyxx6aDDjqorpeJWBaibDfOOMaw0hhuG6ICsFevXk1VkI1ypiWGfsTZ2LZt2zbdFmdgH3nkkdwmcTYu+tN6XiZqYp2I6oQlllgi/x0VK8svv3zehvzwhz/Mw6eioqURxBnHCRMm5DPyzS+qEGepo6qjEZaHmtr+U6wnURl6xhlnpKWWWipdfPHFuQpw4403TpMmTar7yumoVFhttdXS22+/nR566KG01VZb5XnuOnbsmLp06ZKOOeaY9Pvf/74h9rViXyq2nVEBGf1jrCunnXZaOvXUU9OSSy6Zdtttt6ZKlnp21VVX5X2oGF4ay3+nTp3SL3/5y1xFf/3116dGUft/jjkPo+Ix2iL6hLD66qun733ve2nFFVfM+xcxdKgR2iK2GTGFSW17EWJ+s/iJ26P/jCq4ejZq1Ki8X/njH/84H3NEhVcMS68NzW8U8f8cFeLbb7992mijjXKl1+67754rx2+++eY8/Db2y2vz6daWoUbILrJqHhBnEuIsfS0JjKqm5ZdfPlc43XfffU2PqyWG9SzOJh1//PFT3Rbfe7nllstVLI3k9ddfrx5//PGmvwcOHJhT9dNOO606++yzq3322af62te+NlUVXD2KM61R0dWpU6eqe/fu1TbbbFM999xz+b5YPzbffPNqq622qsaMGVM1ggceeKBadtllc5vEWafXXnst3z558uR8ZjrOzN5yyy1VvXv22Webfo/vHuKs45JLLllddtllVSOI6q04K3/IIYfks/K//OUv8+/zzTdfdeWVV1b//ve/87/zzz9/de6551b13hajRo3K1TxHHXVU3q6GqOxaYoklqgsvvLDaYYcd8nY1trWNcObtj3/8Y9PvsZyEvffee7pqp4kTJ1b1rFblOHjw4Fy5UGuPZZZZpurYseNUFW/1vlzccccd1fe+972qTZs21WabbZYr4GpimxLbj//+979VvYsz81H9GVX0sQ9x9913N90XFRvRRsccc0xDLBPNxXd96623qo022qjaf//9G+bYoyb6h9hexDIwYcKEXMUR+53nnXderpqO31999dWqEcT3jbZ46KGHptrPCjGyYKGFFmqItpi2gmu//fbLx2LXX3/9VNuY999/v6pnsa2I/arYPsS29IQTTsjHZS1atKhWX331atFFF81VsrH/df7550+1bal3aV7YAYpQoaa2MscBZS1wijAhHhshTBw41KNaW/zvf/9ruq22gY8N3SqrrFL95S9/abovfq/XHaJaW8RGrrnDDz+8uummm5r+jiCqc+fO1VVXXVXVczvEjk+UssYwuu9+97vT/b9HWWd0dk888URVr2rrQpSxjhw5Modr66yzTi5hnXZI7korrVTXwUKtj4y2mFZ8/whd+vfvX9W72jLxzjvv5KEQP//5z6uddtqpWnfddas//OEPTY+LIbff+MY3qiOOOKKq97aIwPmCCy7I4doPfvCDPPwhgrcI5sN1111Xde3aNbdZPZl2yGgtWJrR/TGULIYE1Jx66qnVxRdfXBfDTqf9DtOGBJdeemnehoQYErHIIotUm2yyST6wPvPMM6t6Mm1bND9Q/Pvf/57XjQijp31sHEBG6FQvZrVMRLAQB0vt27ef7qTdjjvumLez9WTatphViBT9QrRLvYYJs2qLGFIa24nYl4gweq+99mq6L8LYmMahnsyq74+26NOnT9OxWfN+ZLHFFqvOOOOMql582jaw+f21wCmG1EWbxAmLX/3qV1O1T72orRsvvvjidPdF4LT99tvn4ennnHNO3s+M4Klv374zfHy9mqvDphDhUZxhinlnagty7d9Ik3v16pUDpxg/HQt2Pc8tEHMzRVvEd6wt3NEWsXMQ8yvUxtBH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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from pathlib import Path\n", + "import pandas as pd\n", + "\n", + "from dimelo import cluster\n", + "\n", + "karyotype_chromosome_order = \"natural\" # \"length_desc\" | \"length_asc\" | \"natural\" | custom list\n", + "karyotype_min_visible_bp = 20_000\n", + "karyotype_detect_haplotype_shading = True\n", + "\n", + "matrix_table = association_payload_A[\"matrix_table\"].copy()\n", + "axis_table = association_payload_A[\"region_axis_table\"].copy()\n", + "cluster_columns = [c for c in matrix_table.columns if c != \"region_id\"]\n", + "\n", + "if matrix_table.empty or axis_table.empty or not cluster_columns:\n", + " print(\"No region-association rows available for karyotype plot.\")\n", + "else:\n", + " dominant_cluster = matrix_table.loc[:, cluster_columns].astype(float).idxmax(axis=1).astype(str)\n", + " dominant_by_region = pd.DataFrame({\n", + " \"region_id\": matrix_table[\"region_id\"].astype(str),\n", + " \"cluster\": dominant_cluster,\n", + " })\n", + "\n", + " karyo_table = axis_table.merge(dominant_by_region, on=\"region_id\", how=\"inner\")\n", + " karyo_table[\"name\"] = karyo_table[\"cluster\"].astype(str)\n", + " karyo_table[\"score\"] = 0\n", + " if \"strand\" not in karyo_table.columns:\n", + " karyo_table[\"strand\"] = \".\"\n", + " karyo_table[\"strand\"] = karyo_table[\"strand\"].fillna(\".\")\n", + "\n", + " artifacts_dir = Path(\"artifacts/notebook\")\n", + " artifacts_dir.mkdir(parents=True, exist_ok=True)\n", + "\n", + " karyotype_bed = artifacts_dir / \"association_regions_by_dominant_cluster.bed\"\n", + " karyo_table.loc[:, [\"chrom\", \"start\", \"end\", \"name\", \"score\", \"strand\"]].to_csv(\n", + " karyotype_bed,\n", + " sep=\"\t\",\n", + " header=False,\n", + " index=False,\n", + " )\n", + "\n", + " chrom_sizes_path = None\n", + " if \"ref_genome_file\" in globals():\n", + " ref_path = Path(str(ref_genome_file))\n", + " candidate = ref_path.with_suffix(ref_path.suffix + \".fai\")\n", + " if candidate.exists():\n", + " chrom_sizes_path = candidate\n", + "\n", + " if chrom_sizes_path is None:\n", + " inferred_sizes = (\n", + " karyo_table.groupby(\"chrom\", as_index=False)[\"end\"]\n", + " .max()\n", + " .rename(columns={\"chrom\": \"Chromosome\", \"end\": \"Length\"})\n", + " )\n", + " inferred_sizes[\"Length\"] = (inferred_sizes[\"Length\"] * 1.05).astype(int)\n", + " chrom_sizes_path = artifacts_dir / \"inferred.chrom.sizes\"\n", + " inferred_sizes.to_csv(chrom_sizes_path, sep=\"\t\", header=False, index=False)\n", + "\n", + " fig = cluster.plot_cluster_karyotype(\n", + " region_bed=karyotype_bed,\n", + " chrom_sizes=chrom_sizes_path,\n", + " chromosome_order=karyotype_chromosome_order,\n", + " invert_position_axis=True,\n", + " detect_haplotype_backbone_shading=karyotype_detect_haplotype_shading,\n", + " min_visible_bp=karyotype_min_visible_bp,\n", + " )\n", + "\n", + " print(f\"Wrote karyotype BED to {karyotype_bed}\")\n", + " print(f\"Using chromosome sizes from {chrom_sizes_path}\")\n", + " for chrom in [\"chr9\", \"chr12\", \"chr20\"]:\n", + " n = int((karyo_table[\"chrom\"].astype(str) == chrom).sum())\n", + " print(f\"{chrom}: {n} plotted region(s)\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Association heatmap\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABOgAAAVHCAYAAADvAldmAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3QeYXVW5OO41IQMhDEORFopSRODSAigdKVKU7g+8dAhBFARpIgQEgQtKLiCidGkRUWkCAURQKVIFRUGaFAFB6TVMAsMkc/7Pt7xn/jOTKWfa2TmZ9+U5D5lz9tl7nV3W3vvb31qrrlQqlRIAAAAAUIgRxSwWAAAAAAgCdAAAAABQIAE6AAAAACiQAB0AAAAAFEiADgAAAAAKJEAHAAAAAAUSoAMAAACAAgnQAQAAAECBBOgAAAAAoEA1H6C76667Ul1dXf7/YBg3blxaeumlB2VeDFxs2xNPPHFQV+Umm2ySX0W5+uqr04ILLpiampra3ps+fXo66qij0lJLLZVGjBiRdtxxxzQrmDRpUt4GL7744pAt48knn0wjR45Mjz/+eMXfiWN0sPeLWa0umlXEto/fFfvC7OrZZ59NW265ZZpvvvnyb73hhhvSrKDouori6srBrOOqUY9TvNhfYjtXWqfPbueqWVGl1wXlbffWW29VrWzDzUCuZYZi+wz3+83+XN/095qovO3POOOMVMQ1/6x4zzIrqnSdThrANU3sP6usskoaTLF943guLEBXXiHlV9xUL7HEErlQ//73vwe1YLOr8847b7a+0Z1VRNAnKsNZ7YZkxowZ6YQTTkjf/OY3U0NDQ9v7l156aTr99NPTzjvvnH7605+mww8/vKrl+v73v19YUOK//uu/0jbbbJO++93vFrJ8ijUrHKv77LNPeuyxx9L3vve99LOf/Sx99rOfHVa/HwbilltuGdY3H8P9988O67/IayAYCr/4xS/SWWedZeXCcMmg+5//+Z98E3PBBRekL33pS+mKK65IG2+8cfroo48Gv4SzGQG6vvnwww/Tcccd16+b3pNOOqnLm97f/va3+VWEm266KT399NPpa1/7Wof377jjjhzs/uEPf5j22muvfDxVU3cXp1GW2Aaf+tSnhnT5BxxwQLr++uvTP/7xj1SrPv/5z+d1Ff9ncI7Vaoht9sADD6T99tsvHXzwwWnPPfdMSy65ZNWWP6vWVfRNterKWTVAEvvwcDXcf//ssP4F6JjdCNBB7epXgC6CcnET89WvfjVdfPHF6cgjj8w31jfeeOPgl5BeRfPIjz/+eLZcU6NGjcqZmoNpzjnnzK8iXHbZZWmDDTbIwbj23njjjTT//PPPctt6jjnmyNugkmYzA7H55punBRZYIGcPDrWpU6cOyXyjaXKsq/g/xat0O7/55pv5/5Ucf0O178xqdVX8zueff77qy61l1aorh4tSqZQDnsNZtesbqBb79qwlEmxaW1uLLgYDMDvHAoajQbmT3GijjfL/O2e//P3vf89N9qK/rbhwjWZDnYN477zzTg7wrbrqqrnJX2NjYw4APvroozMt51//+lfum2ueeeZJiyyySG4G2Nzc3Key/uY3v8nZSfPOO29e1uc+97n8lKGv7aG76svgtddeS/vuu2/OvphrrrnSmDFj0g477NCWGRFtlJ944on0hz/8oa2ZcPu29O+991467LDDcj9k8f1Pf/rT6X//9387VJrt29FH6vJyyy2Xp40sjJ6CQptttlleZzFtNCk8//zzZ5ruz3/+c9pqq63SQgstlOaee+60zDLLpPHjx3eY5sorr0xrrbVW2/qL7fajH/2owzRxY/eVr3wlb/fRo0enddddN/3617/u8oQQzRI+85nP5P0j1tf/+3//r8N+1LkPun/+85/pG9/4RlphhRVyGT/xiU/kZbXPPoltEu+FTTfdtG1dl7dhV30YRIAsMmgWXXTRXJbVV199pmBR+3X/k5/8pG3dxz70pz/9qdv13/733nrrrTkY1Xmed955Z9432pe1p20dlXA0CY1tEf1mxTERx2HMp7PYf2IbxbaK37bwwgunL37xi3l7l9dxXCzF7y0vv9yWvrt2/pEJuvLKK+fyLL744umggw7K+29X7fyjvLEdYl+IwORpp502Uxnr6+vz9JMnT06DKX5H1CuxT2299dZ5v91jjz3a1kus1/gdsV5i23/9619P77777kzrL/bB+J3xG+K3xG/q3OdAd3XFNddck7dT7K9xbMXDjc5dApTLGe9HHRf/ju0UdWM0i+7rMRi/t9JsxNhuUZfG74ntGfXX3nvv3WNfK931A9JVnyo9lbe3Y7VcZ8e+Hft4zCOaQ8exUul27kls13LG07e//e287HL5y33OxLbefffdcwB5ww03zJ/97W9/y8tcdtll876z2GKL5bry7bffnmkZsU2jbon9J9Zv1KsHHnhgPoZn1boqgpZx/onzRpwfB5odH+slMsxi+0cgNJoUxzm+q/6AKrluKNdL9913XzriiCPysRL7x5e//OW2gGt/66vYtnGNEMd6rINrr702fx7n7XXWWScfx3H++f3vf99lmTrXlX295uiPWGf//d//nddDuXzf+c53+tW/a+d6raWlJWcnLb/88nl7xDk3joPf/e53+fOY9txzz22bZ/lVVmk9G8vddttt02233Za3efyOCy+8MH8Wy4plxr4Tx3n8vmOPPbbD91966aW8HnpTrqejL9ho0h71XZTrC1/4Qnruuec6THvPPffk4/OTn/xk3nfi2izqyvaBw55+f1+uH3uqwyopx1CJc3LUueX6K+qSk08+eabzUqXn+76s/0rOn73tf1EHrr/++nm/jXnEvMrHdFlP10Ahlhf1e+y7sQ5iX45uSYbiHiXOu3EsR10RZT700EM71L9Rl0Sd35U4LuIavieVXOfHuvjWt77Vdi8S8431GEHzSvpz61y39HQuDdEKa+211877THwWrRA6Z45Xch3QH305l1eyfdr/pvJ+G+eyXXfdNb388stpsHzwwQf5frF83Rb72xZbbJH+8pe/tB2Pcd8V90zlfbp8bVM+BuPaLFooxXEa637KlCkV35P39TguX4fE+ohtHXXaYOjLvVBZtFSK674oSxxPXfV/Xcl1yFCJOi/u02O5UadG66LO19a9xQKiVVb5eInzZsQinnrqqYr6QOyqL9P4O1qY/PznP8/1QZQt1vndd9890/erWV/251xV9vDDD+dzQ7kejFahnUV5oluquBYsn3ujv/jeytnbdVMlBiU1qXxBGhVrWVSc5UyhCRMm5A0QB3JsjF/96lf5QroczImmdXHxESvo9ddfzxdlcdDEjhYrOsSFSBz4cRF2yCGH5PejmW3shJWKE0nsNLGzHHPMMXmn/etf/5qDJnHSGKiddtop/+7oXyx2+riRio0RZY6/4yAq9z1WvniOHThMmzYt/+bYsePiNS7E7r///lzOV199daZ+BCLoFieFaCoZO01UIt2JYFz85u233z5no0UzywhyxYVz3KiEKGt0kh4X+LG9Yt3Edr3uuuva5hO/ZbfddsvbIQKHIQ74uEmKk1SI7Rc7fPye2E6xU8ZFTyw7LorK2z0OmLgYv/322/OJK74fJ5xYRlSWcWB1JW4uY73Ed+KEEGWM3xcno9hf4iQTJ/dY9o9//ON8Eb/SSivl75b/31nsW/H9OKlEBRT7YVSQUXnFTVz5t5XFzVWUNbZTVFpxARqBxdiXI9DUnagM4mSy5pprtr0X6zv24zjBxaARp556altZyxffXW3rOJFG9mpsj/333z+X55JLLskXXg899FAaO3Zs2zLiZj72/TjJRtZrPGWJk+Mf//jHfNKJ5cf7cdIsN73tbv2XK++oeCLQGIGGaLIb2yC2TewL7ddB3IRFMDDWT1zUxD5w9NFH55N/lKe9qOyjgo3fFhcFgyV+b6yXqBzjZBb7SIjtF+slguqxv7zwwgvpnHPOyXVC+98Rx2Bs4+222y7PJy5U4v+VBC3K84+b8ti2cXxEcCrmH8tpn7UVx0TMN4IAUc4IAPzgBz/I2yLWc6XHYIjPQ2/NRmOfi5N4zCPqxtg34wI0LkTi5BkX8QPRW3l7O1Zj34xgTqyX+H7UK7GvxbaM9df+4qK77dyT2C9jG8SFQZQzbozb9w0Z4twUJ9loAlW+QYnfFcd7bNu4oI96Py5A4/9xXJUvbl555ZV8XEU9EsfWiiuumOv4OA7it8yqdVU8LIl1GHVPBAliHcX/oy5ZY401Ul/EeSaOnaiXYj+OdRDHeWzXziq9biiL82lcd8QFVOzrcZ6M9XLVVVf1u76K81KcX2K7x3Tx77gojZuhaIof1wrl/kLjhituFou65ijfYMYxHL8j9rE4JiLIE+f5OK8MVKy/qLvK54ion+MmP24E44Yw9q3Yz+OYiOO1s0rr2RDbJo7D+E6c1+JGIPaJ2CarrbZa7l4lzoGx/8d324uHChFEbR9E6MnEiRNztnPcjL7//vv52Ih9/MEHH2ybJo6tOE5jv4lrmdiHzz777Fw3xmfl39fT7++L7uqwSsoxVGLbRZ0YgfD4f1xzxw1x7AdxHLTXl/N9Jeu/kvNnb+s/po/rz5h3XH9FUCKO7ZtvvjnfzIWeroFimfGQuXyDGtdsESyKujDWQdQLg3WPEmK9xTEcvzfOJXFuiPV6+eWX58/jQUccG3Gd3L6j86jPnnnmmR67hKnkOj+On1hfEeCI3xjXkhE0jwdYce6K4EZ/dXUujbo56pi4b4jjOzLGYx+I9RZl7et1QF9Vei6vdPuEqHePP/74PG3sV/HQKI7XON93vu7rrzgXxfEV+2QEcyKgeO+99+brq7iOi3vMOK6ijihvs87XNhG8iPUdx2AEHOLfcR9VyT15X47juDeJ4zS2cRwvsb5jH4t7mQh4DERf7oVCbKeYJu594xo+6od4EBl9EJfvx/t6HTKYIqi6yy675Doz9rHYt+I47Nzqqqyr+8O4d4j6NoLOcWxF3RT7X/ymOG/393iJ82tcW0X9FsuKB59R38d6LtdF1a4vB3Kuiuv9OEZj34ntG+fXOAbKDyzi2jX20ziuYv3GdXnsJ3E8RV3bU5+lvV03VaTUB5dddlnUqKXf//73pTfffLP08ssvl6699trSwgsvXJprrrny32Vf+MIXSquuumrpo48+anuvtbW1tP7665eWX375tvfi8xkzZnRYzgsvvJDn9z//8z9t75111ll52VdffXXbe1OnTi19+tOfzu/feeedPZb9vffeK80777ylddZZp/Thhx92+CzKVbbPPvuUPvWpT7X9HfPtav5Rxng/1kl4991389+nn356j+VYeeWVSxtvvPFM75988smleeaZp/TMM890eH/ChAmlOeaYo/TSSy91WG5jY2PpjTfeKFVi2rRpM7231VZblZZddtm2v6+//vo83z/96U/dzufQQw/Ny50+fXq30xx22GF5Pvfcc0/bex988EFpmWWWKS299NJt2/rSSy/N05155pkzzaP99ohpTjjhhB5/ywMPPJCnu/zyy9veu+aaa7rdL2L9t98G5X3riiuuaHvv448/Lq233nqlhoaG0pQpUzqs+0984hOld955p23ayZMn5/dvuummUk8uvvjiPN1jjz3WZZli32ivp20d26C5ubnDe7EPLrrooqXx48e3vXfHHXfkeRxyyCE9rufY92Lf7+6Yj7KEKMecc85Z2nLLLTsct+ecc06eLrZr+9/UebtEmRdbbLHSTjvtNNOyfvGLX+TpH3zwwVJv4hhtv190J35TzDOOo/Zi/4z3f/7zn3d4/9Zbb+3w/muvvVYaOXJkaccdd+ww3Yknnpina7/OOtcVsQ8tssgipVVWWaVDnXPzzTfn6b773e/OVM72dV5YY401SmuttVafjsHy+mlfj3UnyhDLve6667rdPzrXdV0dQ+1/R/vlVlLe7o7VqDfmn3/+0v7779/h/dgm8803X4f3u9vOlSj/vs51d+xf8f5uu+0203e6qod++ctf5unvvvvutvf23nvv0ogRI7qsV8vrd1asq9p76KGHSgcccEDeFvHd2CfPPffcXN9U4le/+lX+XpS9LOqOzTbbbKb9qtLrhnK9tPnmm3eoxw4//PB8vozzfX/rq6iHyv7+97/n92Ib/vGPf2x7/7bbbpup7J3rykqvOQZax33+85/Py/nnP//Z7TI6l62rc2v75bav11ZfffXSNtts02MZDjrooDy/ziqtZ8vLjffis/Z++MMf5vfjmrMn5e3Xm3I9vdJKK3U4h/7oRz+a6fzc1XF+6qmnlurq6jqs7+5+f6XXj73VYZWWo1xn9aa8/N6um7tb9te//vXS6NGjOxynlZ7vK13/fTl/drf+uyp/zDfmGfVPe91dA+23336lMWPGlN56660O7++66675PFSe/0DvUcrbbvvtt+/w/je+8Y38/qOPPtpWr4waNap09NFHd5gurvHiNzQ1NXW7jEqu82+44YY8zSmnnNLh/Z133jnvb88991y3+3B3dUt359Jnn302161f/vKXZ7oPLNdffbkO6E1XZa70XF7p9nnxxRfzOeh73/teh+liv45ryfbvd75e6ov47bHf9yTq7a7mXz4G4x6w8++v9J68r8fx2LFjO0z3k5/8JE/X1XVkTzpfE1V6L1Te9nPPPXfpX//6V9v7cb8R78e1Q1+vQ7qr3wdyPo/lLrnkknm/L7vrrrvyctpvy57uD2Ndxzp/++23296L/TOOtbgm7W3/6+o8En/H689//nPbe3Huiboojt9q15eXdXFN09dz1Q9+8IO292IfKq+32GfDz372s7zO2sczwgUXXJC/f9999w3ouqk3/WriGk+iIyoake94ihzR5ci4KHesHSmyEbmMyGREqiMjI14R4Y+o9rPPPtuWoh5R2HKfTZFBEtOUmzCUU3XLncDGU/1YXlk8Xezc2X5PT0miLBENj3TD9gajz5hIkYzIa6T9dm66UYl4ChpPwSMboLy+4hXrOtZL5zTSyNaLbVBp2criCUfMN56GxFOM+DuUn+jEU8VIzexKTBOp7z2laMZ2imhx+/T12J6xneJJXTn9Np5CRHZOZEB01tP2aP9bopyxv0TqaZSt/f7SF1HmeHIWUfSyeKofUf3IMIqnBu3F04322aLlJt699dlUTplv/91KdLWto7+jct9UEeWPYy6evkdGXPv1EOs51mdk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rroqb5/YTgsuuGBpjz326DDMe0/l7DzceaXHYKyfroZP70oMxX7wwQfn+jnmGUO8R3nKw6SX98POx8MVV1yRj6/4TgxPftttt820bSstb3fHanm9xnEedWrUJ3GOGTduXIe6qbft3JPujrPyuo/6v7PYfjG0fNRxUa6vfOUreZ/pqh6I81acB+O4i30gfmecK2JY91m1rorzTfvyDVSsw9133z0fY7G+YvvFEPVRliuvvLLDtJVcN3R3LdLdMTiQ+ir25zhndxbLie3YuUyd68rerjkGWseFuB4p749xjKywwgq5zuqpbHHNdvTRR+f9KK5t4hh77rnnZjqXnnLKKbnMMe/4DbEev/e977Vdk5TryG9+85t5H4/zW+dLy0rq2e7W8+23317aYYcdSosvvnje3+P/ca3yzDPPzLT9KrmkLe8j11xzTYf3u6rnnnzyydLmm2+erzViPe2///6lRx99dKbpevr9lVw/9laHVVqOzueL7pR/ayXXzXGcrrvuunnbxbqP7RZ1fefvV3q+78v6r/T82dP6v+SSS0rLL798/n7suzH/rtZTd9dA5To3jvWlllqq7Vr1C1/4Qt6vB+sepVym2NZxHRzHSewvcW7+8MMPu/zOaaedlr8T14KVqOQ6v3wvcvjhh+ftHb831l+cS+K6s/M1/H777Zfr9Cjvf//3f5feeOONmc4xPZ1LQ9yPlLdx/ObYl373u991mKaS64DedLWPVXou7+v2+dWvflXacMMN8zEdr9j3Yh96+umne7wWjroi9sHO56f24tz87W9/u7T66qvncsT849/nnXdeh+mampryeTd+W5S9vKzujsG+3JP39TiOssW9Xmzjz372s6W77767y+ub3nT+TqX3Qu2viX7wgx/kYzmm32ijjXJd2lkl1yHdXW8M9Hwe10Sxv0T5VllllXwNEftFvNfV7+nK73//+7ztytcd2223Xd53O/vtb3+blxHn1rhuiOv6rurH8vVOfF6uT2Ndd/Xbq1FfXtbFNU1fz1VRd6y33nq5PontE9eJncV1TsRMYvpy/RTXMieddFLp/fffb5uuP9dNvan7vxUPVEFE3yOyHpmWXTX7Ga7iKWM88egutbqzeLIYTxrbPw2qpkgdj6c18WQwngADfRNNTeLJbWQSlruiYNap45h9RdZAZIhFVkA85ac2RTZhZBLG9uw8mia1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K62UVl999XT00UenhRZaqC3zLUYe2mabbXKnnVHec845py0wFjbaaKO83DfeeKNP6yA6Qo1hw2OZMd8oQ1kMMx4djUYnp6ecckqHjLxYD/FZrJOw+eab52G6KxGjDUW/c+1fAAAAUMv0QQf9EIGs3XffPd111105QBZDaUczzSeeeCKPchNNNWMEnBgdJ3T+u7Ott946D7sdQbLDDjssvxdBvZtuuik1NDTkDLkYJvvqq69Ou+66a/48svFidJ8VVlghlyeGt77vvvtyU9oYwac8wk98N4b4vuKKK/JnMRrPuuuum5vbXnDBBenCCy/ssWzdeeedd9KOO+6YRxSKAF8ED9s3T41/RwDvrbfeSsstt1we4WeJJZbInz3zzDN5qO76+vo+r/tTTz01Z/0BAADA7EIGHfRDBMciMBevsMcee6RXXnkl/fvf/x609RkBr8iEi+y0NdZYI/35z3/u0Aw0MvMiOBeiaWxksEUALuyzzz556O/w9NNP58BhBPYiABjf++CDD9oy/vorgm+x/AjOhRh2uzzEeIgAZohMvmWXXTa98MILbZ/tueee/QrOhWOOOSa9//77ba+XX355QL8DAAAAiiaDDmZRZ555Zs6Mi2DgqFGj0hFHHNGhr7jIrKtEqVTKgbNq9/EWZS6bY4452vqb60vZuzLXXHPlFwAAAMwuZNBBP0QT0cceeyw9/vjj+e8rr7wyN98sN+Hsj8bGxpwRVvbuu++mxRZbLAe6oj+6a665psfy/O1vf8vZciGas3788cf535HlFvOOZq9lzz33XG6iGuadd94Oy61UZOJF33bRPLac8VeeJwAAAFA5ATroh4UXXjj3O7f33nvnpqXnn39+DqBFX3P9tddee+U+5qI5awwSceihh+bsuRggIj6LgRS6EwMsxHeiT7hoxhrBw8hSi37lRo4cmW6++eY8aEOUNea33377pQ8//DB/91vf+lbaYost+jxIxAILLJCuv/76NGHChDzfGDAj+sDrjxjAYskll8z/jvLFQBIAAAAwXNSVov0bDLIYWXO++eZLY849LI2Yu/fmiK0fNqdXDzorZ3JFtlfn+Wx1+KWpfq7Rvc6npXlauu2H42eaz3AQ/cpFNly44YYbcl9tTz311IDnG8GyGDV26aWXTrOi8j7y7jPLpsZ5PXMAhq+tFh9bdBEACjO91JLuSpOH5X0Aw0MkhRx++OGpubk5t7R69dVXu3yP2qUPOphNnH322emqq67Ko7bGRUlk+AEAAAx3S142oarL+9e+Ewd9nt/+9rfT4osvnu/5ogVVd+9156yzzsrBvBdffDF96lOfSrOCWbFMRRKggyq65ZZb0rHHHjvT+5Httssuuwxo3jHfruY9UOPGjctNZQEAACjGtGnTcuumz33ucz2+V62yjB7dews3+kZ7MGrCpIYJ6ap5D+v1FdPNyrbeeus8mmrn10CDc0NJgA4AAGDou+5ZffXV04gRI3Lf5tEq6qc//Wm6995789/RO1kM/Bf//upXv9rle92JeUSmWoiui2L6T3/60/nvU045JS8r3otlL7rooumOO+7o8N34LPpIj8SN+PchhxySPvroo1ze8vfWWWedtNxyy6UxY8a0fXf69Olpq622SvX19Xm6ueeeO/eB3luZhisBOgAAAIACff7zn0+PP/54OvHEE9PkyZNzk9V99903B8weffTRPM2Xv/zl/O/vf//7M7132mmndTvvtddeOzeHDbfeemuevhyEi34bDzjggPT73/8+/fKXv8yBshjEL4Jr7Z133nnpa1/7WvrDH/6QDj744LTddtvlwQmPOuqoPChhU1NTev755zt850tf+lK666678m+688470x577JHOPPPM9KMf/ajHMg1XmrgCAAAAFOSNN97IAapvfOMb6bvf/W5+b/PNN8+DAB522GHp17/+dX5vwQUXTKuttlr+d7nPufbvdWfOOefMfdWFFVdcsUN/b6effnqHaceOHZunuemmm3LwrywCcu2DgBFMixZi//u//9tW3vbNXiMjMIJ+F154YQ7shWiOe88996Qf/vCHOSOvuzINVzLoAAAAAApy99135//vvvvube9FsCuy55599tkhXfbvfve7HBwrN0ONYFn429/+1mG6LbbYou3fL730Umptbc3NV9sHAdsPVBEZc+HrX/96nm/59cwzz6S33nprSH9TrZJBBwAAADAMbbvttjlTL5rNrrTSSmnGjBlpxx13zH3MtReZen1RDsLFSK2rrLJKh88aGhoGoeSzHwE6AAAAgAL7nwu/+MUv0gYbbNA2Uurrr7+e1lxzzUFZRgzQED7++OO29yI7L/6OPuKiX7lyX3O9+eQnP5kHhvjtb3+bvvnNb7bNN5rqLrzwwm39z4WnnnoqN2ettEzDmQAdAAAAQEGiaWj0/XbBBRfkUVCjT7kjjjgij9J69tlnD8oy1lprrfz/6Ecu5r3AAgukZZZZJjc7jYEbInvukUceSSeccEJF89tss81y33jHHnts2mijjdLRRx+ds+9ifiH6l4tg409+8pP8/m677ZZeffXVdP311+dlX3TRRV2WabHFFkvDlQAdAAAAMNv6174T06wu+mzbeOON8yAREZiLZqeXXXZZDqINhs9+9rM5qHbppZemiy++OC233HLpueeeSxMnTkzHH398HuRh1KhR+e8YmKI3MYhEjMR66qmn5u/Ev5dccsmcWde+b72dd945XX755XmZEbyLprLlIGB3ZRqu6kqx5WGQxYgt8803Xxpz7mFpxNxz9Tp964fN6dWDzspDPDc2Ns40n60OvzTVz/X/jwjTnZbmaem2H46faT7Mvsr7yLvPLJsa5zXuDTB8bbX42KKLAFCY6aWWdFea7D4AijoGp0/PA1uss846eaRW+s7dLAAAAAAVu++++9Lee++dbrvttvSrX/0qrbrqqqmlpSU3daV/NHEFAAAAqGH/9V//lQdk6Er0L/fkk08O6vJGjhyZbrjhhvSzn/2sbWTWH//4x3lUWPpHgA4AAACghl1zzTXptdde6/KzoRh4IZqyRndDDB4BOmrCpIYJqXFU7y2yp9S3pjFVKREAAADMGlZeeeX8onbpgw4AAAAACiRABwAAAAAFEqADAAAAgAIJ0AEAAABAgQToAAAAAKBAAnQAAAAAUKCRRS4cAAAAYCg9M26Oqq7gz0yakWYlm2yySXrwwQfThx9+mGYVs2KZiiZAR00Y1zQx1beM7nW6luZpKaXxVSkTAAAADBcff/xxGjFiRBo5UihpKFirwGyhboGLUl3jPEUXA6AwZ9zRYO0Dw1bT1Ka04baTiy4G9NuUKVPSRhttlB577LFUKpXSvPPOm84+++y0zz77pLPOOisdfvjh6bTTTksnnnhimjZtWmpoaEhXX311+tKXvtTjfL/61a+mP/zhD/nfdXV1+f/77bdfuvjii9MOO+yQbrvtttTc3JzmmGOOtOKKK6bf//73abHFFmv77iWXXJKOPfbYdMYZZ+QA3T333JOXveWWW6Y333wzB+u+9rWvpQsvvDBtt9126frrr8/f/ec//5mnefbZZ/PvmX/++fM0//3f/91jmYYzATpgtlB6d/9Umq5bTWD4OnKzsUUXAaAw00st1j417fOf/3x6/PHHcwBu7Nix6Ygjjkj77rtv2nDDDdumOemkk9L3v//9tMwyy6S99tor7bLLLjmw15MI6j3yyCN53g899FB+b8kll8z/j2y4mN/aa6+dm5tGIG7zzTfP07YXgcIf//jH6ZOf/GQO4q2wwgq5aWoE1EaNGpUOOuigNGNGx2a9a665Zqqvr0+TJk1KY8aMScccc0zadddd01prrdVjmYYzAToAAACAgrzxxhvp0UcfTd/4xjfSd7/73fxeBMoii+6QQw5JW2yxRX7vhBNOSIceemj+dwTwIpj33nvv5ey07iy44II54y0y1VZbbbUOn5Wz3UIEAl955ZX0wx/+cKZ5RCAuMt/CLbfckt55553005/+NO299975vci4i/KWnXfeeXma999/PzU2Nub34jdEwC5+wxVXXNFtmYYz6SYAAAAABbn77rvz/3ffffe290aPHp0WXXTR3ES0bNttt23792c+85n8/6eeeqrfy41MtgjgRfPWCJadeeaZuTnqW2+91WG6nXfeue3f995770xl/cIXvtDWVDXcdddd+f/zzTdffr/8mj59eoffQ0cy6AAAAABmcRG0K4vmqaFz09JKRaDt6KOPTmussUbO3FtqqaXSlVdemZukTp06NS200EIzLatSH3zwQf7Ob3/725k+05S1ewJ0AAAAAAX2Pxd+8YtfpA022CD/OwaCeP3113NfbgM155xz5sy49iZP/s+gKtEHXHlU1tNPP73XeZX7xItg3p577pn/ffvtt3eY/8Ybb5xuvfXWNNdcc3XoQ6+3Mg13mrgCAAAAFGSRRRbJA0NccMEF6ZRTTkk33nhj7pstAlgxQMNALb/88nmk1quuuio9/fTTeWCJz33uc/mz6FsumqQecMAB6c477+x1XltvvXVuFhsjt1522WXpl7/8ZVsT2HIz16OOOir3PRf9zp166qk5Wy9GcI3g4+WXX95tmYY7AToAAACAAkVwbJVVVsmDROywww554IgIgMWIrQMVQbIlllgi7bbbbnkU1hhgIgJzsZzIpNt0003Ttddem/bff/+K5hcZczHIw/jx4/NospFJF8G5ueeeO38ezVujb7zoJ++4445LG220UR7pNQahiBFguyvTcFdXklPIEIjod3QIOebcw9KIuefqdfrWD5vTqwed1WGU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Vl999XT00UenhRZaqC3z7dlnn03bbLNN+tznPpfLe8455+T3DzjggPz/jTbaKC/3jTfe6NM6eOCBB9KGG26YlxnzjTKUXXfddWm99dZLyyyzTDrllFM6ZOTFeojPYp20F+vp5z//edpvv/26XWZzc3OaMmVKhxcAAADUMhl00A8RyNp9993TXXfdlQNkEVTaeeed0xNPPJHuv//+nAF2zz33pPnnnz9P3/nvzrbeeuu0/fbb5yBZZLOVg1U33XRTamhoyBlyO+ywQ7r66qvTrrvumj9/8MEH01//+te0wgor5PJsu+226b777stNaS+77LL09ttv5+niu7vttlu64oor8mfTpk3LWXrR3PaCCy5IF154YY9l684777yTdtxxx3TttdfmAF8ED9977722z+PfEcB766230nLLLZf23XfftMQSS+TPnnnmmXT33Xen+vr6DvOMoN6yyy6b10N3Tj311Jz1BwAAALMLGXTQDxEci8BcvMIee+yRXnnllfTvf/970NZnBLwiEy6y09ZYY4305z//uUNT0MjMi+BciL7rIoMtAnBhn332SXPOOWf+99NPP50DhxHYi8BXfO+DDz5oy/jrrwi+xfIjOBdGjBjRoe+4CGCGyOSLoNsLL7zQ9tmee+45U3AuXHLJJT1mz4Vjjjkmvf/++22vl19+eUC/AwAAAIomgw5mUWeeeWbOjItgYPQPd8QRR3QYzCEy6ypRKpVy4KzSQRsGS/s+7aJ/vOhvrqeyRwAvAo2/+tWvepzvXHPNlV8AAAAwu5BBB/0QTURj9NHHH388/33llVfm5pvlJpz90djYmDPCyt5999202GKL5UBX9Ed3zTXX9Fiev/3tbzlbLkRz1o8//jj/O7LcYt7R7LXsueeey01Uw7zzztthuZWKTLzo2y6ax5Yz/srz7I9LL700ffnLX+5zU1sAAACodTLoqAmTGiakxlG9x5On1Lem7sdJHTwLL7xw7ndu7733zplhCyywQA6gDWT00b322iuNGzcuj+Z60EEHpUMPPTT3axcDRCy++OJp88037/a7iyyySB5YIvqEi+yyLbbYImepRbBr5MiR6eabb8592/3whz/MfdJFs9Nf/OIX+bvf+ta38vSjR49Ov/3tb/O8KhG/+frrr8/fjyaz0cT15JNPTtttt12ff3sE92Lgi8svv7zP3wUAAIBaV1eK9m8wyGJkzfnmmy+NOfewNGLu3psjtn7YnF496KycyRXZXp3n8+rxi1QWoPuoNY05+Y2Z5jMcRJAssuFCBPmir7annnpqwPONUVcjeLb00kunWVF5H3n3mWVT47ySgoHha6vFux9gB2B2N73Uku5Kk4flfQAwe5BBB7OJs88+O1111VU5Qy4uSiLDDwAAAJj1CdBBFd1yyy3p2GOPnen9yHbbZZddBjTvmG9X8x6oaHarXzgAAAAYOgJ01IRxTRNTfcvoXqdraZ6WUhqfZlVbb711ftWSCNABAAAAQ0eHTQAAAABQIAE6AAAAACiQAB0AAAAAFEgfdNSESQ0TUuOo3uPJU+pb05iqlAgAAABgcMigAwAAAIACCdABAAAAQIEE6AAAAACgQPqgoyaMa5qY6ltG9zpdS/O0lNL4qpSJWUvdAhelusZ5ii4GQGHOuKPB2geGraapTWnDbScXXQyAfhOgA2YLpXf3T6XpkoKB4evIzcYWXQSAwkwvtVj7QE1zNwsAAAAABRKgAwAAAIACaeJKTZjUMCE1juo9njylvjWNqUqJAAAAAAaHDDoAAAAAKJAAHQAAAAAUSIAOAAAAAAqkDzpqwrimiam+ZXSv07U0T0spja9KmQAAAAAGgww6AAAAACiQAB0AAAAAFEiADgAAAAAKpA86YLZQt8BFqa5xnqKLAVCYM+5osPaBYatpalPacNvJRRcDoN8E6IDZQund/VNpuqRgYPg6crOxRRcBoDDTSy3WPlDT3M0CAAAAQIEE6AAAAACgQAJ0AAAAAFAgfdBREyY1TEiNo3qPJ0+pb01jqlIiAAAAgMEhgw4AAAAACiRABwAAAAAFEqADAAAAgAIJ0AEAAABAgQToAAAAAKBAAnQAAAAAUCABOgAAAAAokAAdAAAAABRIgA4AAAAACiRABwAAAAAFEqADAAAAgAKNLHLhUKlxTRNTfcvoXqdraZ6WUhpvxQIAAAA1QwYdAAAAABRIgA4AAAAACiRABwAAAAAF0gcdNWFSw4TUOKr3ePKU+tY0piolAgAAABgcMugAAAAAoEACdAAAAABQIAE6AAAAACiQPuiA2ULdAhelusZ5ii4GQGHOuKPB2geGraapTWnDbScXXQyAfhOgA2YLpXf3T6XpkoKB4evIzcYWXQSAwkwvtVj7QE1zNwsAAAAABRKgAwAAAIACCdABAAAAQIEE6AAAAACgQAJ0AAAAAFAgAToAAAAAKJAAHQAAAAAUSIAOAAAAAAokQAcAAAAABRKgAwAAAIACCdABAAAAQIEE6AAAAACgQAJ0AAAAAFAgAToAAAAAKNDIIhcOlRrXNDHVt4zudbqW5mkppfFWLAAAAFAzBOiA2ULdAhelusZ5ii4GQGHOuKPB2geGraapTWnDbScXXQyAfhOgA2YLpXf3T6XpWu0Dw9eRm40tuggAhZlearH2gZrmbhYAAAAACiRABwAAAAAFEqADAAAAgAIJ0AEAAABAgQToAAAAAKBAAnQAAAAAUCABOgAAAAAokAAdAAAAABRIgA4AAAAACiRABwAAAAAFGlnkwqFSkxompMZRvceTp9S3pjFW67BUt8BFqa5xnqKLAVCYM+5osPaBYatpalPacNvJRRcDoN8E6IDZQund/VNpuqRgYPg6crOxRRcBoDDTSy3WPlDT3M0CAAAAQIEE6AAAAACgQJq4UhPGNU1M9S2je52upXlaSml8VcoEAAAAMBhk0AEAAABAgQToAAAAAGYhX/3qV1NdXV3RxaCKBOgAAACAYeOpp55KK6+8cho5cmQOgs0xxxxpoYUWSueff37+PN475phjii4mw4w+6KgJkxompMZRvceTp9S3pjFVKREAAAC1aN11100zZsxIp512WlpnnXXSM888k6688sr0r3/9q+J5NDU1pYaGhiEtJ8OLDDropxNOOCGtuOKKuULv6u+u3HDDDemPf/xj1df5iSeemD766KNuP99kk03Siy++2Kd5Lr300umRRx6pePqdd945Lb744vlp1Hvvvdfhs3hv1VVXTWPHjs2ve+65p09lAQAAqMQ///nPNGXKlPS9730vHXHEEWmDDTZI++67b7rtttvye5FVFyZOnJjvU8p/xz3T3HPPnfbZZ59UX1+f5p133vz+KaeckhobG/O0I0aMSIsuumi644472pZ377335s+OOuqotMACC+R/x3x+8pOfzNSktZzRF/dNr7/+ug06zAjQQT/F05Y777wzPfjgg13+PdgBunjC018nnXRSjwG6oTR9+vT8/wMOOKDHgF4E5eLzeG200UZVLCEAADBcRAAt/PSnP82Bus4ee+yx/P/99tsvPfroo21/h7inuuWWW9Lll1+errnmmvze+++/n+91fv/736df/vKXOcC2zTbbtN0Hlf3o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0AEAAABAgTRxBQAAgGHs7rvvTqeffnp6+OGH06uvvpquv/76tOOOO/b4nbvuuisdccQR6YknnkhLLbVUOu6449K4cePSrGi7Y66q6vJuOnWXqi6P2YMMOgAAABjGpk6dmlZfffV07rnnVjT9Cy+8kLbZZpu06aabpkceeSQddthh6atf/Wq67bbbhrysw8X888+f1ynDhww6AAAAGMa+9KUv5VelLrjggrTMMsukH/zgB/nvlVZaKd17773phz/8Ydpqq62GsKQw+xKgAwAAgEH20UcfpY8//riw9VoqlVJdXV2H9+aaa678GqgHHnggbb755h3ei8BcZNIB/SNABwAAAIMcnFvmUw3ptTdmFLZeGxoaUlNTU4f3TjjhhHTiiScOeN6vvfZaWnTRRTu8F39PmTIlffjhh2nuuece8DJguBGgoyZMapiQGkf13mXilPrWNKYqJQIAAOhaZM5FcO6fDy+dGuetftfvUz5oTZ9a68X08ssvp8bGxrb3ByN7jqGx5ZZbpt/97ncd3rvkkkvyq+z+++9P6623nk0wmxKgAwAAgCEQwbnGeecobN1GcK59gG6wLLbYYun111/v8F78HcuSPdc/559/fnrxxRfb/t5hhx3S5z//+fStb32r7b011lij39uMWZ8AHQAAAAyB1lRKram1kOUOpcjiuuWWWzq8F9lfsrv6b7nllsuvspEjR6bFF188feELXxjAXKkl1c+1BQAAAGYZ0VfdI488kl/hhRdeyP9+6aWX8t/HHHNM2nvvvdumP+CAA9Lzzz+fjjrqqPT3v/89nXfeeenqq69Ohx9+eGG/AWqdDDpqwrimiam+ZXSv07U0T0spja9KmQAAAHoyo9SaZpSKWW5f/PnPf06bbrpp299HHHFE/v8+++yTJk2alF599dW2YF1YZpll0q9//esckPvRj36UllxyyXTxxRfnkVxnRTedukvRRYBeCdABAADAMLbJJpukUqn7SGIE6br6zl//+tchLtnw9d577xVdBKpME1cAAAAAKJAMOgAAABiyQSKq38a1iGUCAyNAR02Y1DAhNY7qPeFzSn1rGlOVEgEAAAAMDgE6AAAAGAKt+b/qK2apwEDogw4AAAAACiRABwAAAAAF0sSVmjCuaWKqbxnd63QtzdNSSuOrUiYAAICezCiV8qvailgmMDAy6AAAAACgQDLoAAAA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6eyzz05rr712t9OfddZZ6fzzz08vvfRSWmihhdLOO++cTj311DRq1KiqlhtmBwJ0wGyh9O7+qTRdq31g+Dpys7FFFwGgMNNLLbPk2q+lQSKuuuqqdMQRR6QLLrggrbPOOjn4ttVWW6Wnn346LbLIIjNN/4tf/CJNmDAhXXrppWn99ddPzzzzTBo3blyqq6tLZ5555iD9Ehg+3M0CAADAMBdBtf333z/tu+++6b/+679yoG706NE5ANeV+++/P22wwQZp9913T0svvXTacsst02677ZYeeuihqpcdZgcCdAAAADAbmjJlSodXc3Nzl9N9/PHH6eGHH06bb75523sjRozIfz/wwANdfiey5uI75YDc888/n2655Za09dZbD9GvgdmbJq7UhHFNE1N9y+hep2tpnpZSGl+VMgEAAPSktVSXX9VWXuZSSy3V4f0TTjghnXjiiTNN/9Zbb6UZM2akRRddtMP78fff//73LpcRmXPxvQ033DCVSqU0ffr0dMABB6Rjjz12UH8LDBcCdAAAADAbevnll1NjY2Pb33PNNdegzfuuu+5K3//+99N5552X+6x77rnn0qGHHppOPvnkdPzxxw/acmC4EKADAACAITAjjcivaisvM4Jz7QN03YkRWOeYY470+uuvd3g//l5sscW6/E4E4fbaa6/01a9+Nf+96qqrpqlTp6avfe1r6Tvf+U5uIgtUzhEDAAAAw9icc86Z1lprrXT77be3vdfa2pr/Xm+99br8zrRp02YKwkWQL0STV6BvZNABAADAMHfEEUekffbZJ332s59Na6+9djrrrLNyRlyM6hr23nvvtMQSS6RTTz01/73ddtvlkV/XWGONtiaukVUX75cDdUDlBOgAAABgNhwkoi922WWX9Oabb6bvfve76bXXXktjx45Nt956a9vAES+99FKHjLnjjjsu1dXV5f//+9//TgsvvHAOzn3ve98b1N8Cw4UAHQAAAJAOPvjg/OpuUIgOwYSRI/OosPECBk6ADgAAAIZAaxqRX9VWxDKBgRGgoyZMapiQGkf1fpKZUt+axlSlRAAAAACDQ1gdAAAAAAokgw4AAACGwIxSXX5VWxHLBAZGBh0AAAAAFEgGHQAAAAyB1lJdflVbEcsEBkYGHQAAAAAUSIAOAAAAAAqkiSsAAAAMgVJpRGotjShkuUBtcdQCAAAAQIFk0AEA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0cWoaWeeeWbaf//907777pv/jkDdr3/963TppZemCRMmzDR9vP/OO++k+++/P9XX1+f3IvsO6B8BOmC2UHp3/1SartU+MHwdudnYoosAUJjppZZZcu2XChokorzMKVOmdHg/mqHGq6tsuIcffjgdc8wxbe+NGDEibb755umBBx7ochk33nhjWm+99XIT18mTJ6eFF1447b777unoo49Oc8wxx6D/JpjduZuFfjrhhBPSiiuumNO/u/q7KzfccEP64x//WPV1fuKJJ6aPPvqo28832WST9OKLL/ZpnvF07JFHHqlo2ldeeSWnx6+wwgpptdVWSzvttFN688032z4/5JBD8vzq6uoqnicAANCzpZZaKs0333xtr1NPPbXL6d566600Y8aMtOiii3Z4P/5+7bXXuvzO888/n5u2xvei37njjz8+/eAHP0innHKKzQL9IEAH/XTaaaelO++8Mz344INd/j3YAbo48fXXSSed1GOAbihNnz49P0GLE3b0X/G3v/0tLbvssunb3/522zQ777xzuvfee9OnPvWpXufX3NycnwS2fwEAwCwpMtmKeqWUXn755fT++++3vdpnyA1Ua2tr7n/uJz/5SVprrbXSLrvskr7zne/kprFA32niSk0Y1zQx1beM7nW6luZpKaXxg7bc2267LZ/EIsi0wAILpPPPPz/913/9V1p//fVzwGvLLbdMm266afrzn//c4e8f//jHM80rnipFGvjvfve7NGnSpHTwwQenbbfdNu222245yBTfL3830sljmp/+9KdpwQUXTM8880w+8b3xxhu5/4c555wzffGLX0yXXHJJXnZknz377LPpsMMOy9NEEOtrX/taXsYBBxyQl7/RRhvlQNlvf/vbLjt57U6ktEcw7YMPPkilUimdfPLJaYcddsifXXfddenAAw/MT9X222+/dNxxx7Vl5EWm3J/+9Kc099xzpzvuuKPD07jIMjznnHPa/v785z9fcXniqV8EHAEAgJ41NjbmV28WWmihfK/w+uuvd3g//l5sscW6/E6M3Bp9z7VvzrrSSivle4NoMhv3LEDlBOigGxHoij4U7rrrrrTqqqumn//85znT64knnsgdoUZzzHvuuadtlKLOf3e29dZbp+233z6NHTs2B9JCBOVuuumm1NDQkDPkIvB19dVXp1133TV/Htl4f/3rX3PT0ChPBPTu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d95553T//fenV199Nb377rtp/vnnn2maWIf/8z//k/uk6GunsAsudEVqbJynT98BmJ2cdvMnii4CQGGapjal2794hS1QY+K+6cwzz+zy/uQ73/lOIWWCogjQQT+ddtpp6fnnn09jxozp8u+ulANa/QnQxWhGc8wxR7/KetJJJ6XDDjuskKHKI2V95MiR6YADDkjnnXdeWnTRRbuc7qGHHkp/+tOf0qc+9ake59fc3JxfZeXU9zff2jN91CwpGBi+jtp+m6KLAFCY6aWPZ821X1Q2W41k0MV1fVdNXFtaWtKHH35YSJmgKO5moQe33XZbWnPNNdNqq62WNt544/Tkk0/m99dff/300UcfpS233DIdcsghM/3dlVtuuSXdeOON6fTTT89Buosvvji99tpradNNN01rrbVWWnnlldPBBx+cWltb8/SR0Raf7bTTTmnVVVfNAazJkyenlVZaKY9qdPTRR6eFFloovfjii3n6Z599Nm2zzTbpc5/7XC7vOeeck9+PwFjYaKON8nLfeOONPm3zBx54IG244YZ5mTHfKEPZddddl9Zbb720zDLLpFNOOaXDE69YD/FZrJOw+eabp0UWWaTLZUybNi3/9gsvvLDX8px66qk5S7H8Wmqppfr0ewAAgFnD2muvnX7yk5/M9P4FF1yQ75FgOJFBB92IQNbuu++e7rrrrhwg+/nPf56baT7xxBO5qWZdXV2655572ppqdv67s6233jptv/32OUgW2Wwhgno33XRTamhoyBlyO+ywQ4f+FiLlO5p7rrDCCrk82267bbrvvvvSiiuumC677LL09ttv5+niu7vttlu64oor8mcR8IosvXXWWSef3CLw1VPZuhOds+64447p2muvzQG+CB6+9957bZ/HvyOA99Zbb6Xlllsuj8K0xBJL5M+eeeaZdPfdd6f6+vpel3PUUUelAw88sKJg2zHHHJOOOOKIDhl0gnQAAFB74iF/PMh/9NFH0xe+8IX83u23355b1vz2t78tunhQVQJ00I0IjkVgLl5hjz32SAcddFD697//nZZccslBWW8R8IpMuHvvvTeVSqUchFtllVXaAnSRmRfBufDHP/4xZ7BFAC7ss88+bdlxTz/9dA4ctu9I9YMPPsgZf5FR118RfIvlR3AujBgxIi244IJtn0cAM0Qm37LLLpteeOGFtgDdnnvuWVFw7ne/+1365z//2Zbx15u55porvzpbWB90wDB32o36oAOGex90P0uzmrrSf15FLLcWbLDBBvmeI1oZRaLC3HPPne95LrnkkrT88ssXXTyoKgE6KFB0iBpBuQgGRv9wkRkWWXVlkVlXiQjuReDskUceSdXUvk+76B+vff8RlZb9jjvuSH/5y1/aBqj417/+lbMNI+tvu+22q7gs+qADhjt90AHD2SzbBx29ihZG0VoJhjt90EE3oonoY489lh5//PH8dwz/Hdlh5Qyx/mhsbEzvv/9+298xmmmM6hqBruiP7pprrumxPH/7299ytlyI5qwff/yfC5HIcot5R7PXsueeey43UQ3zzjtvh+VWKjL4om+7aB5bzvgrz3OwRJ9ykZUYfenFK7ITo7++vgTnAABglh4koohXjYlEhei+pv0LhhMBOujGwgsvnJ/k7L333jnN+vzzz88BtOhrrr/22muvnLq9xhpr5EEiDj300Jw9FwNExGfR/0J3YoCF+E70CRdPmSJ4GFlq0a9cjJJ6880350Eboqwxv/32269t5KNvfetbaYsttujzIBELLLBAuv7669OECRPyfGPAjOgDrz9iAIty0+AoXwwkAQAADF/lweLiXmeeeebJ9x/tXzCc1JWibRwMsnjaESNsjjn3sDRi7pn7C+us9cPm9OpBZ+Usr8gE6zyfrQ6/NNXPNbrX+bQ0T0u3/XD8TPOZXUS/cpENF2644YY8YMJTTz014PlGsCxGjS03M60l5X3k8ScXSfPO65kDMHx9bbVtii4CQKFNXG9/72ezzH1A+Rp16VO+l0a06xamWlo/+ii9eNx3Zpn10Z3o4/vOO+9MJ598ck5YOPfcc3PrmujuZuLEibkfcBgu9EEHNeTss89OV111VR61NU60+moAAIBZWFHNTWskDeemm25Kl19+eU4Y2HffffPgdJ/+9KfTpz71qXyvI0DHcCJAB4Ms+k879thjZ3o/st122WWXAc075tvVvAdq3LhxuaksAABAtUT/1ssuu2z+dyQglPu73nDDDdOBBx5oQzCsCNDBIIsRSONVSyJABwAADK660n9e1VbEMvsjgnMvvPBC+uQnP5lWXHHF3F/32muvnTPrJBAw3AjQURMmNUxIjaN6719sSn1rGlOVEjGrWXihK1Jj4zxFFwOgMKfd+AlrHxi2mqY2pdu/+LOii0EfRbPWRx99NG288cZ5YLrtttsunXPOOamlpSWdeeaZ1ifDigAdMFt4860900fNBokAhq+jtjdIBDC8B4mg9hx++OFt/958883T3//+9/Twww/nfuhWW221QssG1SZABwAAAEOhVPefV7UVscw+iiy5L37xi+mCCy5Iyy+/fH4vBoeIFwxH0k0AAACAqqqvr09/+9vfrHX4PwJ0AAAAMBRKBb5qwJ577pkuueSSoosBswRNXKkJ45ompvqW0b1O19I8LaU0viplAgAAoP+mT5+eLr300vT73/8+rbXWWmmeeToO+magCIYTAToAAACgap5//vm09NJLp8cffzytueaa+b1nnnmmwzR1dbN+P3owmAToAAAAYAjUlf7zqrYiltkXMSjEq6++mu6888789y677JJ+/OMfp0UXXbTookFh9EEHAAAAVE2p1DGC+Jvf/CZNnTrVFmBYk0FHTZjUMCE1juo9njylvjWNqUqJmNW0/t8LYNiq89wVGM5m0TqwqAEbZvEMut4CdjAczaK1GAAAADA7iv7lOvcxp885hjsZdAAAAEBVM+bGjRuX5pprrvz3Rx99lA444ICZRnG97rrrbBWGDQE6AAAAGAoFDRIxqzdx3WeffTr8veeeexZWFphVCNBRE8Y1TUz1LaN7na6leVpKaXxVygQAAEDfXXbZZVYbdCJABwAAAEPBIBFAhQwSAQAAAAAFEqADAAAAgAJp4kpNmNQwITWO6j2ePKW+NY2pSomY1SzyiStSY2PHUZ8AhpPTbvxE0UUAKEzT1KZ0+1Y/nfW2gCauQIUE6IDZwqtv7ZmamiUFA8PXUTtuX3QRAAozvfVjax+oaQJ0AAAAMATqSv95VVsRywQGRroJAAAAABRIBh01YVzTxFTfMrrX6Vqap6WUxlelTAAAAACDQQYdAAAAABRIgA4AAAAACqSJKwAAAAyFGKyhiAEbDBIBNUeADpgtjFnoitTYOE/RxQAozGk3fMLaB4atpqlN6fatJhVdDIB+E6ADZguvvrVnamrWah8Yvo7acfuiiwBQmOmtH1v7QE0ToAMAAIAhUFf6z6vailgmMDDSTQAAAACgQDLoqAmTGiakxlG9x5On1LemMVUpEQAAQAVkswEVkEEHAAAAAAUSoAMAAACAAmniCgAAAEPVvLWIJq6a1ULNEaCjJoxrmpjqW0b3Ol1L87SU0viqlAkAAABgMAjQAQAAwBCoK/3nVW1FLBMYGH3QAQAAAECBBOgAAAAAoECauFITJjVMSI2jeo8nT6lvTWOqUiIAAIBeGCQCqJAMOgAAAAAokAw6AAAAGAIGiQAqJYMOAAAAAAokQAcAAAAABdLEFQAAAIaCQSKACsmgAwAAAIACCdABAADAUGbQFfHqh3PPPTctvfTSadSoUWmdddZJDz30UEXfu/LKK1NdXV3acccd+7dgQIAOAAAAhrurrroqHXHEEemEE05If/nLX9Lqq6+ettpqq/TGG2/0+L0XX3wxHXnkkWmjjTaqWllhdqQPOmrCuKaJqb5ldK/TtTRPSymNr0qZAAAAZhdnnnlm2n///dO+++6b/77gggvSr3/963TppZemCRMmdPmdGTNmpD322COddNJJ6Z577knvvfdelUsNsw9NXAEAAGAI1JWKe4UpU6Z0eDU3N3dZzo8//jg9/PDDafPNN297b8SIEfnvBx54oNvf9z//8z9pkUUWSfvtt9/grzwYZgToAAAAYDa01FJLpfnmm6/tdeqpp3Y53VtvvZWz4RZddNEO78ffr732Wpffuffee9Mll1ySLrrooiEpOww3mrgCAADAUBjAgA0DXm5K6eWXX06NjY1tb88111yDMvsPPvgg7bXXXjk4t9BCCw3KPGG4E6ADZgtjFroiNTbOU3QxAApz2g2fsPaBYatpalO6fatJRRdjlhPBufYBuu5EkG2OOeZIr7/+eof34+/FFltspun/8Y9/5MEhtttuu7b3Wltb8/9HjhyZnn766bTccssNym+A4UKADvopRjeKkY4iVfzBBx+c6e+u3HDDDfkEt+6661Z1vZ944om5Y9cYLr0rm2yySZo0aVIeUr1SMW38nrFjx1Y0/c4775zuv//+9Oqrr6Z33303zT///Pn9xx57LD99K4uOZaN/jHfeeSf1xatv7ZmamrXaB4avo3bcvugiABRmeuvH1v4AzDnnnGmttdZKt99+e9pxxx3bAm7x98EHHzzT9CuuuGK+jm/vuOOOy5l1P/rRj3LTWqBvBOign0477bT0/PPPpzFjxnT5d1fKAa3+BOiiT4h4qtUfMarSYYcd1m2AbihNnz49P0U74IAD0nnnnTdTvxarrrpqeuSRR9r+jguAurq6qpcTAABmtyaufXHEEUekffbZJ332s59Na6+9djrrrLPS1KlT20Z13XvvvdMSSyyR+7GL+4pVVlmlw/fLD+A7vw9URroJ9OC2225La665ZlpttdXSxhtvnJ588sn8/vrrr58++uijtOWWW6ZDDjlkpr+7csstt6Qbb7wxnX766TlId/HFF+cOVzfddNP8tGrllVfOwalyanhktMVnO+20Uw5iPfTQQ2ny5MlppZVWSquvvno6+uijcyp6pJaHZ599Nm2zzTbpc5/7XC7vOeeck9+PwFjYaKON8nLfeOONPm3zGLVpww03zMuM+UYZyq677rq03nrrpWWWWSadcsopHTLyYj3EZ7FOQowAFSM89STW4c9//vMeR4GKkac6j0YFAAAMzC677JLOOOOM9N3vfjffN8RD9FtvvbXtAftLL72UW8MAQ0MGHTVhUsOE1Diq93jylPrW1H3+Wt9EIGv33XdPd911Vw6QReAommk+8cQTualmZHndc889bU+KOv/d2dZbb5223377fLKLbLZyQOqmm25KDQ0NOUNuhx12SFdffXXadddd8+fRVPavf/1rWmGFFXJ5tt1223TffffllPLLLrssvf3223m6+O5uu+2WrrjiivzZtGnTcpbeOuusky644IJ04YUX9li27kQz00hxv/baa3OAL4KH0QS1LP4dAbwY9Sn6mIina/FULTzzzDPp7rvvTvX19RUvLwJ+yy67bI/NZuOJXWQEAgDArK6u9J9XEcvtj0gY6KpJa4j7op5EggHQfzLooBsRHIvAXLz+P/buAzzKMvv//0lCSAhJKILSFymL0ptSVtoqUnQhsCACQmIQRDr6UwO4iOhCVBQEFVCRINJEBFzhD640RTpLleLCAiL1CwJJCAkJmf91bndmKWkzSeaZybxf1/VspjzlnieskE/OfW7Vu3dvOX36tJw6dSrP7pkGXloJp9VpDRo0kB07dtwy3VMr8zScU1u2bDEVbBrAKS0/114RSpuwanCowZ6GW3qc9n+wV/y5SsM3vb6Gc8rf319KlizpeF8DTKWVfBqsHTt2zPHeU0895VQ4p3SZ9qyq59SoUaPkypUrjk1XpgIAAAAAwJtRQQdY6N133zWVcRoGah8H7fugVXV2WlmXEzabzQRnN4d77nBzTzvtj6f95pwdu52GexpCLlmyJMv9dGn4vFoeHgAAAAAAT0AFHZAJnSKqKxPt37/fPF+4cKGZvmmfwukKXeJcq77sdDVTXdVV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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "fig, ax = plotting_matplotlib.plot_read_cluster_region_association_heatmap_matplotlib(\n", + " association_payload_A,\n", + " region_label_mode=\"chromosome\",\n", + " max_region_labels=24,\n", + " row_annotation_columns=[\"source_label\", \"strand_label\"],\n", + " row_annotation_titles={\n", + " \"source_label\": \"Source bed\",\n", + " \"strand_label\": \"Strand\",\n", + " },\n", + " row_annotation_palettes={\n", + " \"source_label\": {\n", + " \"on_target\": \"#D95F02\",\n", + " \"off_target\": \"#1B9E77\",\n", + " \"unlabeled\": \"#7570B3\",\n", + " \"unknown\": \"#999999\",\n", + " },\n", + " \"strand_label\": {\"+\": \"#4C78A8\", \"-\": \"#F58518\", \".\": \"#999999\", \"unknown\": \"#999999\"},\n", + " },\n", + " cluster_sort=cluster_sort_mode,\n", + " region_sort=['cluster_fraction','genomic'],\n", + " group_region_labels=True,\n", + " group_label_columns=[\"source_label\", \"chrom\"]\n", + ")\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Two-site raster demo\n", + "## What it does:\n", + " - selects two sites from the same read at a strict fixed offset and displays a window around each site,\n", + " - keeps row sorting matched across motifs,\n", + " - shows either read-coordinate spacing or local window-centered coordinates,\n", + " - supports uniform, random, and heatmap bin-mean downsampling.\n", + "\n", + "The default two-site view keeps Site 1 centered at 0 and Site 2 at the requested offset in read coordinates. Use `coordinate_mode=\"local_window\"` when you want each panel centered on its own selected site.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Two-site fixed-offset raster stats: {'pairs': 56, 'rows_are': 'reads', 'unique_reads': 56, 'site_sets': 56, 'rows_before_downsample': 56, 'rows_after_downsample': 56, 'downsampled': False, 'downsample_method': 'none', 'coordinate_mode': 'relative_to_primary', 'selection_mode': 'fixed_offsets', 'window_offsets_bp': [0.0, 2000.0], 'window_widths_bp': [2000, 2000], 'ml_score_thresholds': None, 'observed_offsets_bp': [{'n': 56, 'min': 0.0, 'median': 0.0, 'max': 0.0, 'unique': 1}, {'n': 56, 'min': 2000.0, 'median': 2000.0, 'max': 2000.0, 'unique': 1}], 'site_selection': {'mode': 'fixed_offsets', 'n_windows': 2, 'min_distance_bp': 2000, 'max_distance_bp': None, 'selection_multiplicity': 'one_per_read', 'choose': 'first', 'selection_seed': None, 'anchor': {'mode': 'first'}, 'strand_relation': 'any', 'orientation': 'genomic', 'excluded_sites': 0}}\n", + "Vertical-axis scatter stats: {'pairs': 56, 'rows_are': 'reads', 'unique_reads': 56, 'site_sets': 56, 'rows_before_downsample': 56, 'rows_after_downsample': 56, 'downsampled': False, 'downsample_method': 'none', 'coordinate_mode': 'relative_to_primary', 'selection_mode': 'fixed_offsets', 'window_offsets_bp': [0.0, 2000.0], 'window_widths_bp': [2000, 2000], 'ml_score_thresholds': None, 'observed_offsets_bp': [{'n': 56, 'min': 0.0, 'median': 0.0, 'max': 0.0, 'unique': 1}, {'n': 56, 'min': 2000.0, 'median': 2000.0, 'max': 2000.0, 'unique': 1}], 'site_selection': {'mode': 'fixed_offsets', 'n_windows': 2, 'min_distance_bp': 2000, 'max_distance_bp': None, 'selection_multiplicity': 'one_per_read', 'choose': 'first', 'selection_seed': None, 'anchor': {'mode': 'first'}, 'strand_relation': 'any', 'orientation': 'genomic', 'excluded_sites': 0}}\n", + "Heatmap stats: {'pairs': 56, 'rows_are': 'reads', 'unique_reads': 56, 'site_sets': 56, 'rows_before_downsample': 56, 'rows_after_downsample': 56, 'downsampled': False, 'downsample_method': 'none', 'coordinate_mode': 'relative_to_primary', 'selection_mode': 'fixed_offsets', 'window_offsets_bp': [0.0, 2000.0], 'window_widths_bp': [2000, 2000], 'ml_score_thresholds': None, 'observed_offsets_bp': [{'n': 56, 'min': 0.0, 'median': 0.0, 'max': 0.0, 'unique': 1}, {'n': 56, 'min': 2000.0, 'median': 2000.0, 'max': 2000.0, 'unique': 1}], 'site_selection': {'mode': 'fixed_offsets', 'n_windows': 2, 'min_distance_bp': 2000, 'max_distance_bp': None, 'selection_multiplicity': 'one_per_read', 'choose': 'first', 'selection_seed': None, 'anchor': {'mode': 'first'}, 'strand_relation': 'any', 'orientation': 'genomic', 'excluded_sites': 0}}\n" + ] + }, + { + "data": { + "image/png": "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4tv4uLF2gCpVqsi+ffukTp06hdYvW7ZM6tWrZ/d7tOf6h84vIYQQtxHw7z9HtzWiOijEsAYPHqwaWg888IDceeedEh3tWPonQgjxKQyidwU7/OCDD8pXX32lMg0cP35c6To89thj8txzz9n9Hu25/qHzSwghxL34eCPIE+qgV1xxhfq7fv161eCi80sI0SfG8H6Rtu7ChQvSt29fyczMVCHQoaGhyvm9//77i/0u7bm+KeftAhBCCDEu5QKcW3wZzOvt37+/hIeH21QHdYSvv/6aji8hRL8ElHN88WEw2vvss8/KuXPnZNu2bbJq1So5ffq0vPzyyw7vg/Zcn/j2lUkIIcQQYwSOLnpQB4Xic3BwcCF1UDjDhBBieAxi0L/99lvZuXOnhISESLNmzaRTp04SFRUl2dnZ6jNiXOj8EkIIcbvglaOLEdVBCSHEOBjD+73tttuUw2ut1I9InlGjRnmtXMT90PklhBDiZvTfULJUB7WmJHVQQggxDEbpzRSRcePGya233iovvviit4tCPAidX0IIIW7DSGHPmjro6tWrzeqgP/zwgxJIGTNmjLeLRwghHsAoFl3klltukX/++UeJGF5//fWSlZXl7SIRD0C1Z0IIIW7DSHl+y6IOSgghhsAYYs+qAxN06dJFdWhec8010q1bN6XrQIwNR34JIYS4DSON/LpCHZQQQvSNMSy6yWQyv65Vq5asWLFC6tSpI5dddplXy0XcD51fQgghbsNIgldUByWE+D0GMegvvPCCUnfWiIiIkFmzZsnDDz9sU9iQGIcAk2XXByGEEOICUlNTlQry31sOSmR0jEPfyUhLlctb1VVqmzExjn3Hk5QrV04iIyNl8uTJMmTIEPP6pKQkqVatmhQUFHi1fIQQ4m6bfn7ZdxITFVHy9umZUr77rT5rz4n/wpFfQgghbg0VdmZxlokTJ6pQtbCwMOncubOsWbPGoe9NnTpVHe+6665z6nhUByWE+DVujnp2p03/7bffJC8vz/za3vL777+XrvBEF1DwihBCiNtwpg3kbFtp2rRp8sgjjyiBEjSS3nvvPenfv7/Kx1upUiW73zt06JASqerRo0ep1EEhijJo0CA17/e7775zeh+EEKJf3Kd45W6bDsf45MmTal/FOclwohnJY1w48ksIIUSXc37fffddlX5o1KhR0qxZM9Vgwrytr776yu53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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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ffbZJ9Ux3G+//TQnJkQGzjjjDHnvvfc0Rcjll1+e95nAtjBPrLm5WedswAhHpxSfMYRueFhX+VPvdYWOADpK1WTUlRq0bWgH0bb94Ac/0M8wIHj66afLKaecIlaC7XntU+9tVCHUe10VtT2vEesXmgPo34I5c+aosQtBw1133VXFsEgNU0opaVJdPPXUUypLn7kcfPDBKfn+s846y5gwYYKmxNh+++2NuXPnDtjG8uXLjX333ddoamoyWlpajEMPPdTo7e0dsM7bb79tbLPNNrqNKVOmGH/4wx8qLuFfL7CuWFflvtYWLl1u+MPRvBasa+V7GW3g6aefbng8HsNms+ni8/mM8847z7AabM9rH7bnrKtKXG9di78xEsGenAvWs3J7vsYaaxh33nmn0dfXZ4wbN8544okn9PO33npL0x2R2qUB/1XaACdktCO6COPBKF49jugWAuuKdVXua23h0uV535f4zaRxYyx/L8OrClEUpDhCJAxEnEhxYBvFuioFvK6KV4ddi7/Jq33G+m0Tplq2Pb/22mvlhBNOUMGr6dOnyxtvvKEp1P785z/LPffcI0899VSli0hKBMOeCSGElJBamvU7UB0UnTuog0KzANM+CCGEVAfI7ws9mq+//lp22GEHNXwB8v1iih+pXWj8kqoHXpdzzjmH3hfWFa8rUlKoDlp62J6zrnhdkXIBcVZToNUEc35JbcOwZ0IIISUMe+4sMOy5w7JhclC8f/TRR2WjjTaS2267TQfd3n77bVUHveGGG+TNN9+sdBEJIaQk1FrYM6lfkj5+QgghhIxYHfSTTz5h7RFCCCEWh8YvIYQQkgdIlfLSSy+J3+9X43f27Nn6+YoVK8Tj8bAOCSF1pOOQz0KI9aDxSwghpGq55pprZOWVV1bjE+Ilr7766rDrd3V1yTHHHCOTJk3S+aVrrrmmPPzww3ntCzl+999/f5k6darmy/ze976nnz/77LOywQYbFOV4CCGknilnmz7U9jCthdQuFLwihBBSldx5551y8skny/XXX6+dpCuuuEJ23HFHmTt3rowfP37Q+pFIRFU98d2///1vmTJlinz11VfS1taW1/6oDkoIIbXTpmcDvz/wwANlv/32G+XREKtCwStCCCFVKXiFzhFSDl199dX6PpFIaGjycccdJ7/61a8GrY8O1aWXXiofffSROJ3OERwVIYTU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+CWEEEIIIYQQUvNQ/BJCCCGEEEIIqXkofgkhfTJjxgw56KCD8tpDF154oRx//PFVs5fvvvtuaW5uTjy+7bbb5IADDijrOhFCSL5wPOd4Tgih+CWkKk5Y6urqdLLb7bLeeuvJOeecI4FAQCqdZcuWyY033ii/+c1vSipYC8kxxxwj8+bNk1deeaUoyyeEDB04ng8Mx3NCSDFh5JeQKmDfffeVpUuXypdffinXX3+9/OEPf5CLL75YKp0//elPstNOO8m6665btM8Ih8NSTBwOhxx55JFy0003FfVzCCFDA47nfcPxnBBSbCh+CakCnE6njB49WsaPH69pyHvvvbc888wziddjsZjMnj1bo8Jut1u23HJLefjhhxOvR6NROfbYYxOvb7zxxhqRTQbzzJo1SyOow4YN0+iyYRgp82CZm2++uS4D82A9uru7+1zvBx54oFfKcH/LwHZcdtllMm7cON3myZMny9y5cxPv/frrrzUC/uCDD8puu+0mLpdL7r33XvnFL34h7e3tiQj5JZdcovMHg0E566yzZOzYseL1emWHHXaQF198sVeUYcKECeLxeOTHP/6xrF69utd2YBsee+wx8fv9A35XhBDSHxzPOZ4TQsqIQQipaI4++mjjwAMPTDz+6KOPjNGjRxs77LBD4rnf/va3xqRJk4y5c+caX3zxhXHXXXcZTqfTePHFF/X1UChkXHTRRcY777xjfPnll8Zf//pXw+PxGA8++GBiGVdddZXR0tJi/P3vfzcWLFhgHHvssUZDQ0Pis5csWWLYbDbjuuuuM7766ivjww8/NObMmWN0dnZmXO/Vq1cbdXV1xptvvpl4bqBl4PnGxkbj/vvvNxYuXGicc845ht1uNz777DN9He/BsDVx4kRdT2zL119/bdxwww36vqVLl+pkLu+4444zdtppJ+Pll182Pv/8c+Pqq6/W/WIuD+tmsVh02z/99FPjxhtvNJqbm42mpqaUbenu7tb5XnjhhQJ8o4SQoQrHc47nhJDyQvFLSBWcLFmtVsPr9apwg/iDEHv44Yf19UAgoEL29ddfT3kfxOsRRxzR53JPPvlk4+CDD048HjNmjPG73/0u8TgcDhvjxo1LiN93331XPxtiMxvee+89nX/x4sWJ5wZaxjrrrGNcfvnlKc9tt912xkknnZQifiF2k4HYTxesixYt0v327bffpjy/1157Geedd57ex/7Zb7/9Ul4/7LDDei0L4MLA3XffndW2E0JIJjieczwnhJQXWzmjzoSQ7Nhjjz3k1ltv1fRg1PzabDY5+OCD9bXPP/9cfD6f7LPPPinvCYVCstVWWyUez5kzR+68805ZvHixpu/idaQVA6QMo6YYacEm+Ixtt902kfqMVOq99tpLU5anTZsmU6dOlUMOOURaWloyrrOZIozUZJP+ltHR0SFLliyRnXfeOWU5ePzBBx+kPIf1GoiPPvpIU7k32mijlOeRCo10a/DJJ59oqnMyU6ZMSUm1NkGaNvYzIYTkA8dzjueEkPJB8UtIFYB61Q022EDvQ8BCRN5xxx1ax9vV1aXPP/7441rbml5bZtbeovb12muvVXHX0NAgV199tbz11ltZr4PVatU649dff12efvppufnmm9XFGctALXE6w4cP19u1a9fKiBEjBlyGKUiz3R8Dgf2Cz3v33Xf1Npn6+nrJlTVr1iS2gxBCBgvH8977YyA4nhNCCgUNrwipMiwWi5x//vlywQUXaHR10003VZGLiC4EcvIEgyzw2muvqevySSedpNFgvPbFF18kltnU1CRjxoxJEcORSESFYzIwk0Ik9tJLL5X33ntPnZAfeeSRjOv5ve99TxobG2XBggVZLQPzrrPOOrquyeAxtrE/sAxEeZPBduK5FStW9NovMA8Dm2yySa8LAG+++Wav5WNfobVUciSdEELyheN5bzieE0KKCSO/hFQhhx56qJx99tmayoyILqYzzjhD3ZJ32WUXTWOGaISgPProo2XDDTeUe+65R5566imN0v7lL3+Rd955JyViO3PmTLnyyit13kmTJsl1110nbW1tidchEp977jlNVR45cqQ+XrlypQrIvk7q4OT86quvqkN1NsvANqGFE4QzUrLvuusuef/999XRuT8mTpyokQEsG1FxODcj3fmnP/2pHHXUURrxhnDFZ2GeLbbYQqZPny6nnXaaCvFrrrlGDjzwQN0/mVKe0eN3/fXX1/UihJBCwvGc4zkhpISUueaYEJKjO6jJ7NmzjREjRhhdXV1GLBZTE6iNN95Y3ZHx/LRp04yXXnopYYo1Y8YMNXKCm/GJJ55onHvuucaWW26ZYnA1c+ZMdU3GPLNmzTKOOuqoxGfDARrLxLJhvLXRRhsZN998c7/r/sQTTxhjx441otFoVsvAfJdccom+B9uB9XvyyScTr5uGVzDTSueEE04whg0bpq9ffPHFKS7XcIfG8mDq9eMf/1hdpk3uuOMONfZyu93GAQccYFxzzTW9DK+mTp2q+5sQQvKB4znHc0JIeanDf6UU24SQoQOGF5hoISp9xBFHSDXy8ccfy5577imfffaZpocTQshQhOM5IaQWYM0vIaRooL739ttv1/rhagUu2EgZp/AlhAxlOJ4TQmoBRn4JIYQQQgghhNQ8jPwSQgghhBBCCKl5KH4JIYQQQgghhNQ8FL+EEEIIIYQQQmoeil9CCCGEEEIIITUPxS8hhBBCCCGEkJqH4pcQQgghhBBCSM1D8UsIIYQQQgghpOah+CWEEEIIIYQQUvNQ/BJCCCGEEEIIkVrn/wP1EPZu1Zv7gAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA78AAAI2CAYAAACYKD8+AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnQeYJFXVhr/qPDlszhnYJWeQICCI/IIkEVABBUEQJCqICIoJDCCiBAUBMaEomAURECTnsCybc5ycOlf4n+/09GzPbPdM90zPTHfPefepnQ7VVbfuvXXrnnuS4TiOA0VRFEVRFEVRFEUpYVyjXQBFURRFURRFURRFGW5U+FUURVEURVEURVFKHhV+FUVRFEVRFEVRlJJHhV9FURRFURRFURSl5FHhV1EURVEURVEURSl5VPhVFEVRFEVRFEVRSh4VfhVFURRFURRFUZSSR4VfRVEURVEURVEUpeRR4VdRFEVRFEVRFEUpeXISfhsbG3HUUUehuroap512Gn7zm9/gAx/4wLAU7PLLL8dnPvOZQf/+xhtvxIQJE1BZWYnm5masWLEC+++/P6qqqnDVVVfltaylxn//+18cccQReT3md7/7XZx55pkoVi688EJcc801KDTWrl0LwzDQ1taGYuR///sfpk+f3u8+s2fPxp///Oesj8n6eOuttwZVnm984xs46aSTMn5/22239XtvDOXc2cB25jnY7oV8vw8nA9UBnxt8fhQDubTnPffcgylTpsgz7c033xz2sq1fv17O1d7eXlTHJscddxzuvPNOjCYvvfQSDjrooCEdY9ddd8Xf//53FAKWZWH33XfH+++/3+9+vP8eeOCBvI5PtbW1BdvXCoFin1+N1Dwtm/nGSMOxn3McpYCF35/97Gdwu93ywH744YfxqU99Ci+88AIKjQ0bNuBb3/oWXnvtNXR1dWHcuHH43ve+hz322AOdnZ245ZZbRm0gzicDTdQLia9+9av43e9+h0KF/eILX/gCpk2bJg/KGTNm4Iwzzuj5/u6775Y+lC+Bc/HixTj22GMxfvz4ohZeh8phhx2GjRs39rynEEYBUylNCmn8LCbi8TguvfRS/OEPf5Bn2t577533c/RduJk5c6acq6amJu/nGuqxt2zZgk9+8pOYPHmyLGjPnTsXV1xxRc/3//rXv2Q8z1efe/rpp3HkkUdKebM9Fifh11133ZDO+9577+H4449HIcC535e+9CV5lhcyI9mPC2VcK/T51Ugx0Dyt73xDyR4K6GVlZTI/5ta3vzuOg5tuukn2q6iowE477YSXX36532NSgXriiSem/Y5zQb/f33M+bps3b+61z7333oudd95Zzsfz/uUvfxke4XfNmjWyEulyFba1NDs9K2rWrFm9ys5Vy0KCnYWrqaV+fk7cRpLBnI8TJ/abN954Qx6UL7744rBqw7xeLz7xiU/kdYVcKS3yfd+M9ngz0uNAKbF161ZEIpGMz7DRbtuR5qyzzkIgEMDSpUtFo/fEE09gr732GrbzcXJ17rnn4tZbb816cXPZsmX4v//7v0GdzzRNadNCuz8//vGP48knnxRtaj4Ya/12rM6vlNKo/9/97ncyP+bWV2HDhb5//OMf+M9//iPfc0zmwlN//O1vf8PHPvaxjN9zISN5Pm5Tp07t+e7nP/+5KDIfeugh+Y6Cdi4yXtZSLM2cH3zwQTElomD5i1/8QibuyQcOta3UYvGCSSwWwz777CPmx4SFu+SSS6QyJk6ciLPPPruXGcqzzz4rBeexTznlFNHE9Qe1uocccoisPixatKhn1YvmkR/+8Ifl2DwWzbQPOOAAWZnjSiw/Y+NQyKFJEk24We4TTjih59gNDQ2i1aaJGSubJnTRaFTMp2lOlTw2N5pRULA++uijZWWxvr5eyhUKhdKWm6sTXB3hucvLy7FkyRL8+te/xm677SYr2Kyf66+/vufBx78sN1e4WVauptAMitdJUxe+TpYluf/tt9+OXXbZReqGAlyqmVK68w9G48zV6PPOO0/KtGDBAjz66KO9zJ74HYU7fs/VuL5aaq7I/fSnP5W248SCk5nW1lacfvrp8htqNjixScJJB8/DOpo3b578Nklyhe/+++/H/PnzxayFwmxfs/mbb75Z2i+TiRrNhiZNmiTveQya0KQzpWR/Su7DeufqFWGfonaAfYDloJliJrhaxTpiu2cD7ytaLvD6WcaLLrpoh0GE52Sbs6ypA+S///1vqU/2T96T7P/JCbXP55N7k/zkJz+RekzWO4+ZHEyS9zotKnj/sgyZNLScuLGcqcfhcR977DF5/+6770o5OelJXTGnOwLvp+R9mtpWy5cvlz7L437wgx+U8aY/nnnmGaljHpt9KnWsyTR2ZNK8JM/Ltu278pjPcyfvK7Yt+9BXvvIVGXeS7+fMmYM//vGPvc41Evd7f2PlypUrxYKB5eN9mdonkn3m61//uoxfHJfTjZ+EfZL3Fa+BC6x//etfe44zUB2ko6OjQ8YbnoP3zXPPPSefc2WYWsJUwYL3Po9N4bIvyWu44YYb5Np5Hb///e/x/PPPy73Le4r3sW3bA95vg70WmjizfZNjDut5MM+S5D3/6U9/Wp5trOvDDz8c4XC4Z0yjGxPrjM+WvpoTjinXXnutHJcuRezbdIVKwn051vP87Cuc1GQyNe17bI5Z559/vljbsOy8fzg2ZIJt9tnPflaugYvxrJNzzjlnBwuSTM/sgfpcX7gfn1HJuh8IHot1S21pErbXd77zHekTrB/eN6njSfKZyPrjM5HjcqrLR659kb+nVoXjNb9jed5+++1+xxuO633rfeHChXIewnLRfYyT3MGSrt9mmnOlo78+PlA/5r3E36XOz2hFwOfgpk2bcn6Ok/76GMvK+mMfO/TQQ+XYY3F+1Rf+lotJ6cZowvn/BRdcIP2BG+diwWBQvmO/4G95D7Bfsy+8+uqrWc3T+mro+ztP8rp/9atfZZxbpYNtzHJxAYyw3tlPf/nLX2Iw9H2Ocoxkf6fgx7ZiP/3IRz6C1atXy/68V1PdLU499VS5tiScZ33xi1/Mal6ZLS0tLdKH7rvvPqkr1huVj6nn7Qvrkc/KwVi2cO7IcfDHP/6x9GWej+Xnsz1rnBw455xznMsuu6zn/f333+/sueeePe//+Mc/OpMnT3a2bdvmXH755c7hhx/umKYp35122mnOmWee6bS2tjpdXV3OGWec4Xz605+W71paWpyamhrn7rvvduLxuPPXv/7V8fl8cr508Bjjxo1zbr/9dicWizn//e9/nYqKCue5556T759++mk5Xiof/OAHnR/96Ec97w8++GDn29/+tmNZlhOJRJxnnnlGPrdt2znwwAOdK6+80gkGg05TU5NzxBFHOF/72tcyHpvX9fnPf17Kwu355593otFo2rLPmjXL2WmnnZylS5dK3XC/f/7zn86yZcvk3G+++aYzceJE59e//rXs//jjjzvTp093Nm3aJO/XrVsn+5Kvf/3rzoknntjr+HfccYezxx57OMuXL5e6/PGPf+zMmzevpzzpzt8XXiPrKxM8r9vt7tVefr/fWblypXzPdisrK3Mee+wxqV/WY9+ysusdffTRTnNzs1wbr3n33Xd3/ve//8kxP/OZzzgnnHBCr761fv16qaOnnnrKCQQCPe29Zs0aOd5JJ50kfYPne/fdd53Kykqns7Oz5xg777yz84c//CHtNV1wwQXO/PnznZ/97GfO22+/LefJ1PeT5+O5kmzZssWpr693fv/730u98vxTpkxx/vOf/2Ssx0zHSgeP9eCDD8pr3j/sY6m/Zx/s6OiQumR/4b1JVqxYIXX1pz/9Ser14YcflrZZvXq1fL9w4ULpf4T1x75y5513ynvew5deeqm85vE8Ho/zwx/+UPo4+wjfJ9u8L8cdd1yv4/C4V199tby/7bbbnI997GNp76e+92myz7JvsMzhcFiOnWlsIKyPfffdV+qC9XrMMcdIf8pm7Ejtp6yvuXPnOl/96lflPnnhhRecurq6fu+NoZ6b9xXrmudmP77++utljE0ej9fOc7DdR+p+zzRW8ny8p7785S9Lu/C+YT/9zW9+I9/zOng93/zmN+W8vJ504yd/V1tb6zz55JNyDo4B1dXVUmYyUB30hX2D4xHHJZbxrrvuknbjb/l+0qRJUo7Ue/8LX/hC2mMlr4H1yt/ee++9UjY+z/hsSI5dvL+yud9yvZb+xolcnyWs2/3220/qh89clo91zTYlPD5/k+mcN954o7PbbrvJM4jj6umnny79Own3PfLII+X5z9/svffe0qezuR6WqaqqStqF1/Ktb31Lri8TH/7wh5199tnH+eUvf9nzPEwldRwZTJ/LRLpjpYP9g22dCq9n9uzZzvvvvy/3wtlnny31lYT1wXuNfYNtwnLxN48++uig+mJ7e7vz0EMPyfOC9yfHcvaX5LMt3Xhz1VVX9Rpbk2Neso+QSy65RO6ZTPD3yedPOvr2Wx47lzlXf308m36866679tr/Bz/4gcxFhvIcT9cvOE5yDsK/HO/ZHydMmOC0tbX1e6xSnF/lMkaTz372s3JvsC80NjbK/Xz++efLd5yj8d7nviwv+wLLns08rW879XeegeZW/XHLLbdIPwuFQs7JJ5/sfPKTn8y4L8/T31iX7jnKcW/q1KnOO++8I/c2751FixZJXW7dulXmZiwz64dtz3FnyZIlcry99trLeeSRR/qdV6aDZeSxOIc56KCDnH/84x893/E1x6JvfOMbckzuy/leJjmI8J7icTLBtuC52C9YZl5zkvfee0/a5qabbpJzTZs2zfnc5z4nY1625FX4JRwUeZNxAEl2yIaGBsflcskDNwkna16vVwYYVj4n4al85CMfyTjB5cC1yy679PqMHTbZabMRfimYc/8NGzb02u+VV16RsnNQSfLvf/9bJsGZjs2HGCfzvKaBYEP1ndz3hXXMhiQciMaPHy9l4ACaSjrhlzfAn//8516f8SZ59tlnsz5/NsJvuvbihIWw3fqWK93gzME7CR/gXBBJvZnYoTPBY3FCnjpIpT7wyAEHHNAzUPEhznZNfYinws8p2HHywUGZDykOYEkGGlS///3vy8MhFQpN5557bsZryHSsdMycOdO54YYb5F5K93tOqJKw73CCQlhHbJtUOGH9zne+I6856afwwv7Oa77vvvukLQiFqmRfYj1yYSsVLhbwoZkO1kfqcXiP77///vKe9woF4FyEXz4cU+9/TsIzwfrg5CXJSy+9JItpvMaBxo7Ufsp7hgN66n134YUXDij8DuXcfcdTjjt9j5cqLI3E/Z5prOTkiPWT+oBjv0oKROwzfcfSdOMn+yAXSFLhZIEP+2zqoC+8VylUpsJ6/9WvfiWv+VBOPls4cWB5Xn311bTH6tvvOfFIN3Zdd911Wd1vuV7LQMJvLs8SnouLLZyQpWMgoYH3O4WpJJwM8vvkwixf/+tf/+r5nnVx/PHHZ3U9bA8K00k2btwo33NSmg5Ocni/UMDmRI/jY3LRJRvhd6A+N1Thl4IHhapU2F7f+973et5zksprTN5XfJ0UdFN/kyr85tIX+8K65v6s20zjDSfIqUIN53MXX3zxDs+15Ng+WOE3td8OZs6VqY9n04/ZBscee2zP98nn01Ce4+nKyDLxeZEKhf7UfjpW5le5jNHsB3xmcrxKQqGM8zJ+xznKggUL5JypfSZX4Xeg8ww0t+oPCp1sM/Yt9uP+hLJshN++9wfHl5tvvrnnPeudi4dJ4ZXzArbxG2+8IfMulpkL5VwM4XjZ0i2LZZpXpoNzCo45PBf7MBdIeO8Sthvr6lOf+pSMHVwgpRzY33jKdko+F9PB9uVCEedf7MucaySFdi7i8Hwf+tCHZNGCG18PdJ+mknfnXQaZoFkjg1EwaFDSfICmODTzoukAN5rO0FyJZlg0/Un1zyV936dCh/W+0dGo7s7FkZ3qeZq57bvvvmJSljTzYFlpHkNTgmRZ6eeybdu2jMf6wQ9+IIGSaPrMctEEJdUMri997eAff/xxMdNJmnHQjKWpqUm+o/kNTcdp2sPvacJAM+tMsPw0a0uWnRvNLlLrZiA7/GxI115Js6Fsz5E0MSY0f+r7PmmOS2iyQnOxZLv885//7KmjTOekaUzSp5Z/aVZFB/p08HOagzCAG82XaMJBMzCaZWQD651lSq13mqPSpCof0OyJZjQ0B6SZBwPfpEJzmCQ0c0q6DQx0r7B/MZgLzcF4f9JMji4INGekORpNjJOktk/f8/SFx6WJEY/DjeMB64h9kcenO0IuZLq+bPonX9MNg+XIZezguEQTPPpnpzvucJy7bx/uOzb2Pf9I3O+Zxkqeg/VDk8FM18NxcaAYEbwGjnmp10Dz5KRJ6EB1kOv4xHHhT3/6k4wvvK9YP/vtt1/GY/Udl9J9lhyrBmrjwVxLf+TyLFm3bp20B4OWDIa+18a257iZ2t653qep9P0tyfR7mm7yOUsTQ/Z3BgOjK9VAkYiz7XNDpa6uTkzv+5La3uxDrL9cnpu59EWas3M+xjZjfSXbLvW52fd8NNGlGSnN8XnP04SS90sqvC5e31BIPW+uc67++ng2cB7w1FNPybOZZuCrVq0SV7t8P8fTjQV8xmYzTy21+VUu18hnJZ+ZqXXHMZTmziwTTbhpfkwTZfYBvs6l/ZMMdJ6hjGk0w2X53nnnHbkHef8Nhb7P0b59i/XO8bjvvI79nK8/9KEPyXtuNHOu675/B5pXpsJgYew3PBfnc3R/4nOUJN0uKavwNfvKZZddJi5vg/X3Pfjgg+X+5vyLLiKf//zne9wvkuejGw77ADe+7u98fcmr8MuOxBuCvjf0D3799dflcwrBbDg+WDjIJTcOrmxUNhofzKn0F1CBNvx9U0PwfS4hzGkrzzJS+GbEMEYxZHlZVvrIpJaTwlByoEg3keP+9IXmNbDyORin+mj0JfUYrDMOvGxY3vg8F2+aVD8t3jz0cWKdsOPxQZ+pLCw/I3Gnlp/+Lalh8PMRsCxde7Et83mO1GOzT33/+98X3yBeEwOJ9A0I0vecvGb6WFKI401DH7FsYB1ToKC/Kxdy+pKp3k8++eRe9c5Bkg+RfMAHEwcaDspcCOHg09+CTLb3Cn3jGBmT/ZUCKR9+vB8p4Oy5556DjmDJgZR9m8ehAE3fN/o80Q+Pg1kmX+d89ZvU/sn+QwGNfoq5jB2sB45ZqT4+2QR6Gcq5+15/37Gx7/lH4n7PNFay3H3rZ6DryXTv8EGZeg0cb++6666s6iDX8YkPevZtTvA5act2XMiGgdp4MNfSH7k8S5KTy3S+zckJWy7Xxv7AieJopw7hRIgLl5wopfNpH0yfGyr00Uv1qUyS2vZ8lrH+huu5SZ9A3qf0paTAmmy71OdmuvPRn5T3BZ8J7DN89qTCOh5qcLHU8w4050olm/nSQP2Y9c1n0m9/+1vx5+Txkostg32Op6vHocxTS3l+NdA18lnJZ2Zq3fE152UUcjwej0S35sIFF7v4u2Rsof7K25eBzjMUuCBHv1r6E9PvPJ/jfLq+xfuCz+Jk30oVfjmv4zyPfugMVsfvhjqv7FsmPk9zgbFU2L+yjXnT93x8hjPg4VDIq/BLTRkfRNQUMLADbw4OYFw5oWM7A14lV1T44EwKiB/96EdlIGNgAQbLYTAFNlomeGPyJqXAyf3ZqFy54spvtnAyx0bmQJkMmsEJOjXSHAC/9rWvyaDHBuJNytQJhKtn/JznT8LVEnZu7stj8Ti8QbOBDz9ORpiOiTcdI5ZxUE5CR35qI9m5uWLPQTp5bJaFZWMdJLn44ovFEZyRJgkfelzRzmUFPhsYgKhvezGYwnDAPsS65QOS7cQHUTYaWa62UVPOG5orrv2lB+HgyXrmajmd6RmwhIM6V5/SDZosB1eMk3A1knXAgYTCADcKlclADH3h9bDdk0E9kv0gXYRPtj0f0hxQed6kQJpNH2ObUAPLPsC2euSRR0TzmkzjxEGeq/0MdpUcFDlYUkjNVTubCu8BBljhcfoelwNxpgkK+3RqvQ4WWmMkF9t4P/B6WXe5jB0MGsHFAAb5Yhvw3kyuPA73uZNwDGUgkeTxvvnNb/b6fiTu90xjJQOKsL14fvZfriCzH6UGHupLuvGTE1kGU+HDmvcej8Vo60kt3kB1kA7eixyXWM8cp6i54XMmdYJP4YD3Ahe68sVA99tgriVbBnqW8NnGSQMXU3lulo+CUXIMGujeYz1xIsdgcxyTr7zySrF2So3AOVJ8+ctflvGV9yU3LsowUA2tE/LR5/pCSy7WLc9F+DrTIgJhEBfe330jGTNdJO9VPmcY2I9j5HAtHnAs4ASRWh62V7YpitiHKTSzn/bV+nJhjc+0wUaxTsdAc65c+ni2zxCOuQxAxN+mjr+5Psf762O8Xzi+MyAZ7zWOjQyOlU3dFfv8imPgQIsQmcZoloHHZPRgBlJinbHvsm34HX/HNuHvOB9mH083F0o3T0tloPMMhc997nNyb/N+56IAteL5jGrOvkXFAueovCd473DhIBnki4s7XBzgmEalA5/bHGfYH5PzulgO80rKN3yO8Vy8Jyjz8BmXDLLG9uezgM8zjhF8vrG/Z0pjRCVhauDMvvD5xH7IY7HeKLRTqcj+RigLsQ4YDZrl5/58nel8wyr8MpIrBxNGt2NFUtDlpDoZVYwriUlzZ940VKEnNcOcYLIiGbmL+/BBxs6SCQ7mHBh5Lg6CXF3hii0bOVsY6ZGrFRTWWWGcsHI1k5M6RlCmMM7yczWZNySjmqZG6WUUPZaVkwdeRzK6IIUlft+fOj8VRlm744475BpYL1w0SB3k+ADjZIXXyUUEdirWUzICN3/DmzzZcVnvNAPhaia/4zX0fTjkA0aXS0ZJ5Qo624LRAocD1jUHKN60rAcKINnWL9uCg8BAq5K84Rnpjg8xnoMmdeyHbNe+8MZj5D1GNmS9s3458NAci4MdI9zxOBRM0pm+ET7ceZxkFFe2Ld/3XQ1NwnMwih77C+8pvmc5B4K/4QSc5WVbcXDiolNqVDwKp5xQJO8fmsiw3EMRfpPHTT1ONsdlpEbem6zXoeS35MDI81NzwTpL3jO5jB3UUHMRhO2ajIbadzI4XOdOwocaTXK5QsrxqW9e75G43zONlawfjpUc/9h/eU9SIOKEIhPpxk9Omhj1mtfKsYz3Elehk0LZQHWQDpaBE6qk2SKfL6mmmoyUynuN9zDPmS8Gut8Gcy3ZMtCzhM9lTjo4oWA7cOGL5Um66HCRh1ZFrCcKPn2hWRnNz/iMo8kdJ0Hsy6MB+wYXFJLPRQqybOO+ZqaD7XN94cSP4zOvn9pGvu7PfJymhXwe9hXgOH4kswpwjpHMFDAc8F7kfIbnYn9Lt5CbqR9xbkHNdd95GIXC5NiWLwaac+XSx7Ppx4TjJd3HeE+kPo9yfY7318cogFAA4Ofsp0zJwv6QjTVVsc+vKCylmztlO0bzmcl7mWVjJHaOq8k0Y1yI5T3E31HoYn/heJvNPK0v/Z1nsLDv0I2Mi9yEkc05v/r2t7+NfMEFG84DOUfi+Mc24NieFFw5tvOaktG+k/Mvjv0UynOdV3KBhPcUv+N4+cMf/lAE4NSo0hzLODbynqGcx7Hy6quvxmBMnvlsoUKK18Y+wejibBeOS0moSOHCK/sA7z+OSbm0nUHH36z3VsYEXLWj8Jcp1QS/48pbMgVDIcNBmA8NLhpkIywqylhjoPu9VKE5Nyc/Q1lkUZRMUOvCSRuFGMJJNids+VzwGC64aEN/xdRUXFwk4YINhThOqjPBxTha9/RNhaNkRynMrygMc3GKAlA62DcokGZKlziWoPky75e+JvKlSmNjoyh9aP2bGk9lpMnONldRihCaS9AUgloeFXwVRUnCCTzHh2zzUipKrlDTmhR8i21ySo1cMphREmpJKRArykDzK1piKEo6aKacjP0ymqjwq+wAV6iLfdWWJk009aJJRL6CTilKKVIK93su0LSSPl5006HJZaFAs7t0bg80o6e/k6IMNzQjpl83/R5pJjkYqNlOZ35e7HChjH7cfaELXzr/5P5IRqvtS7Z+2aPNWJ9f8R7hlo50gdr6gxpwunuNFXbaaSfZRhs1e1YURVEURVEURVFKnrzn+VUURVEURVEURVGUQkOFX0VRFEVRFEVRFKXkUeFXURRFURRFURRFKXlU+FUURVEURVEURVFKHhV+FUVRFEVRFEVRlJJHhV9FURRFURRFURSl5FHhV1EURVEURVEURSl5VPhVFEVRFEVRFEVRSh4VfhVFURRFURRFUZSSR4VfRVEURVEURVEUpeRR4VdRFEVRFEVRFEUpeVT4VRRFURRFURRFUUoeFX4VRVEURVEURVGUkkeFX0VRFEVRFEVRFKXkUeFXURRFURRFURRFKXlU+FUURVEURVEURVFKHhV+FUVRFEVRFEVRlJJHhV9FURRFURRFURSl5FHhV1EURVEURVEURSl5VPhVFEVRFEVRFEVRSh4VfhVFURRFURRFUZSSR4VfRVEURVEURVEUpeRR4VdRFEVRFEVRFEUpeVT4VRRFURRFURRFUUoeFX4VRVEURVEURVGUkkeFX0VRFEVRFEVRFKXkUeFXURRFURRFURRFKXlU+FUURVEURVEURVFKHhV+FUVRFEVRFEVRlJJHhV9FURRFURRFURSl5FHhV1EURVEURVEURSl5VPhVFEVRFEVRFEVRSh4VfhVFURRFURRFUZSSR4VfRVEURVEURVEUpeRR4VdRFEVRFEVRFEUpeVT4VRRFURRFURRFUUoeFX4VRVEURVEURVGUkkeFX0VRFEVRFEVRFKXkUeFXURRFURRFURRFKXlU+FUURVEURVEURVFKHhV+FUVRFEVRFEVRlJJHhV9FURRFURRFURSl5FHhV1EURVEURVEURSl5VPhVFEVRFEVRFEVRSh4VfhVFURRFURRFUZSSR4VfRSkRZs+ejbVr1w7qt+vXr0dlZSXa29sxmlx44YW48847B/37//3vf5g+fToKheeffx6HHnroaBdDUQoKHat0rFIURRktVPhVlBHk3HPPhWEYeP/997MSnPbcc0+Ul5djr732wosvvjikc//rX//CAQccgJqaGtTV1WH//ffHP//5T/lu5syZ6Orqku/IZz7zGVx++eVDOt/111+P3XffHR6PJ6tjrVy5Ev/4xz/wuc99btDnPOyww7Bx40YUCocccgi8Xi/+8pe/jHZRFAXPPfccjjvuOLn/a2trZXz5/ve/j1gs1lM7DzzwAA488EBZDBs3bhz23ntv3HTTTQgGgxlrUMeq3NGxSlEUZXRQ4VdRRojOzk784Q9/QH19PX7xi1/0u29LSwuOP/54XHLJJWhtbcXFF18s79va2gZ17lWrVuG0007DV7/6VTn2li1b8MMf/hBVVVUYLubPny8T64997GNZ7X/33Xfj9NNPh8/nG9T54vE4RhLHcWBZ1oD7nXPOOfjpT386ImVSlEz8/e9/F8H32GOPxYoVK2Qs+f3vf48lS5bIeECuueYa3HDDDbjuuuvks+bmZvzmN7/B1q1bZXEqHTpW5Y6OVYqiKKOIoyjKiHDPPfc4EydO7Pkbi8Uy7nvvvfc6u+66a6/PFi1a5Nx3330ZfzNr1ixnzZo1ab97+OGHnblz52b8LX/H4aC1tdX58Y9/7Hg8Hsfr9ToVFRVyXsLyXn/99XKc+vp654QTTnA2bdo04HWfc845zmWXXTbgfgsWLHD+8Y9/9Lx/+umnnZqaGuf22293Jk+e7EyaNMm54YYbHNu25fv777/f2XPPPeUzfnfKKaf0/CbJBz/4QefLX/6yc9RRRznl5eXOgQce6GzcuNH5+te/7owfP96ZNm2a88gjj/Ts//jjjzv77ruvU11dLee86KKLnFAo1KuOv/vd78pxAoGAc9tttzlz5szpKRN58cUXnbq6OiccDsv79evXS312dHQMWAeKMhywf7Kffutb38q4z8qVKx232+08//zzOR1bxyodqxRFUYoJ1fwqyghBbe+nPvUpnHHGGWJC+Le//S3jvu+8846YOqfC9/x8MOy7777YvHkzLrroIjz22GOircnEpZdeKuX8whe+IKbQ7733nnxObRDNG2k6Sa3QTjvtJNeSD0KhkGijdtlllx205W+88YZorv/73//ivvvuw4MPPtjz/eLFi8Wsmj7Lv/rVr9Ie+3e/+x1uv/12uWZquj/4wQ+K9p3XcOONN+L888/v0cSUlZXhnnvukX15rU8//TRuvfXWXsejWegvf/lLqRvWJ8v+zDPP9Hx///3348wzz0QgEJD3M2bMkNcsq6KMBry31qxZI/0yE//5z38wdepUfOADH8jp2DpWJdCxSlEUpThQ4VdRRgCaFr700ktiAktfupNPPrlf02cKVvTJS4XvOcEaDHPmzBFhjselT+2ECRNwzDHHYPXq1Vmb+DIQFQXBKVOmiGnyt7/9bTnmhg0bMFRo2k2qq6t7fW7bNr73ve+J3zMFY5qBpwq59FGmUM7ycJ90fPrTn8auu+4Kv98v9c6FBwr4FJopDNC0c926dT1+ePRxdLvdmDt3Lj7/+c+L0J0KBd6dd95Z9uF52aYUiEkkEhFT0s9+9rO9fsPrSl6joow0jY2N8nfatGkZ92lqahLhNxWOERx3eG9lMt3XsSqBjlWKoijFgQq/ijICUNBlcBluhALT448/jk2bNqXdP13kZb4fio/uPvvsI4IjA0ItX75cBFoKhtnAiTGFxsMPP1wmw9wmT54swl8+hF8G4CEdHR29PqfGdOLEiT3vZ82a1avOOJl3ufofxiZNmtTzmpP4vu+TE3jy6quv4uijj5Z9KLDSR5rXngqDg/UNYvanP/1JjvHoo4/K9/vtt1+vfXhdyWtUlJFm/Pjx8jfTeJPch9YhqTzxxBPiG8xAeaZppv2djlUJdKxSFEUpDlT4VZRhhia1FDopcFJg5EazYgZLSmoM+7LHHnvgrbfe6vUZ3zN6cj6YN28eLrvsMrz77rtpv+8rUDLqKwXFl19+WSbDyS0cDudsJpkOHnvBggVYunRpr8+pSW1oaOh5T/PmVO3VQIJvrlATfOSRR4pGnALrd7/7XVkkSKXvOakF5qLGH//4R2nPvlpfLg7wOnbbbbe8llVRsoUuCkwv9NBDD2Xc50Mf+pAIx7RQyQUdqxLoWKUoilIcqPCrKMPMX//6VxGk6LtKAZbb22+/LamA6MOaFK6YAilpYkvzXGpoqTFmGhL+pY8qPx9s/luaLSc1O4zeSt/WTIIrNZ8UAJNlo8DHHLxXXXVVj6aX5sI08e1P6OeEkEI+N77uL8rpCSecID62qfC81157rQjZy5Ytwx133CELB8MF24la7YqKCklHddddd2X1u/POOw+33HILnn322R206U899ZRozIczsrai9AfHlp/85Ce4+eab5S/vXcIFOfZdmv0zOvsVV1whfvyMR0BLBt7/3IfjRd/j6VilY5WiKEoxosKvogwzFFypUaTPalLzy41+pxRGKfBRo0nhKKnZZUAmTkB//OMfi18rAzbx/WBNZ/k7mlkz8BUFO5pA8zMGbkoH/YKpBWI5qNkhzPV58MEH46ijjpKy8lj//ve/M56TgaQYQOrXv/61+AvyNT/LBP1rqZlKFZB5Hgb6ov8tBcizzz5bTMaHi5/97GeSAoqmnBT2sw3o9YlPfEIECKaSoT91KgzQRV9lRRlNmCqNub6ZS5uWH1zk+fjHPy7jEv34Cfs+F+UYCI7uBuzLTD/GvN9JiwYdq3SsUhRFKWYMhnwe7UIoyliHQihNfilgDhaaNVIbw7/FCgVgCrsMKsVrOemkkwad23ikoUDBxQoKGUleeOEFXH311RIhW1FKAR2rEuhYpSiKUpx4RrsAiqIkAmApCc1rMUKNNU27qflNhWblKvgqpYSOVQl0rFIURSlOVPhVlBLh8ssv3yE9kjL8LFy4UPICUyPG9EeKovSPjlWjg45ViqIoavasKIqiKIqiKIqijAE04JWiKIqiKIqiKIpS8qjwqyiKoiiKoiiKopQ86vOrjArM1/qDH/xA8kfuueeeknvygAMOGPB3tm1L/skkPp9Pck4qipI/mASA+aWTMPUTcy4rOi4pymih45KiKPlAUx0pI87vf/97ydd6991348ADD8Rtt92Ghx9+GMuWLZPckv3R0dEheW8VRRk52tvbUV1drVWeAR2XFGXk0XFJUZTBoEv5yohz66234vzzz8dnP/tZLFq0SITg8vJy3HfffdoaiqIoiqIoiqIMCyr8KiMKTSlff/11HH300ds7ocsl71988cW0v4lGo6JZSW6KoiijjY5LiqIoilJ8qM+vMqI0NTXBsixMmjSp1+d8v3Tp0rS/uemmm3DjjTdmNHvy+/0IBMrTfl/mqsMnpx6HTjOOR7b+HRZMBFw12LlyV1yzYE+sCQbxvwYTcduFKYGJeLXzdawIvYhydz0unX08Tp8Xw2uNFp7Z6scJs/xoicXx5XcfQ0dsMyZW7Am/UQEbNpqjKxCOb4YLfnhd5YjZXXAQx+jhxaKKIzDROx4vdz6JsNXQ840BL7yuSphOGLYTycvZfEYFpvhr0W5G0WY20zur1/dz587BD3/4A3zpS1/G6tVr+jmSBz5XFUwnAtsJ56VsLqMMHiOAmN0JwES5qx5lrgDazBbMKNsTOwV2wqtdz6M1vhpT/Htgz4q9sTnehm32VjSG3s5LHXncNZhe+QG0xzegNfRed/0YUpaAyy91ZiOK0SYSCfUIdqnuBfStV/I3LimFQfbjkjKa6LikKEo+Uc2vUvBce+21Mpnk1tCwXYgjnGByoyCRbos7YTzX+ipe63gDNkx4PZWYXLYbdq/cCY2RcjzVvA6Lgy9iZfRtxGwbdd6JqPBPxrjAAlS4yxCKleHxhm14P7gcE8ZZmFoPuCW+loWO6Fq0RJejNboCcatdzuf31qGubGf4PFUZyzQym40tsfexLPQq4nZXr+9YB/VlOyHgrc/b+ep9U/CRiYdgQeVuMGRY6f29Ybjg9Xrlb3/H8brLpWxl3vF5KxuPxWN63RUy5E0J7ILdKg9BmacOzbG1WBJ6GSGrSfZtNzfhveCL2BR5R9rXccy8lIFB2QzD3ef6XZgWWITdKj+AgKdmlPtLYkveT4l7ajsaVC6/45JuhVEH2Y5Luum4pChK6aCaX2VEGT9+PNxuN7Zt29brc76fPHly2t+kTsapkcoF04lieXBVtx7Sgcvwodo7DVP9k9AWc/BeZwO2RFci4K5HzLFQ5qqA112NCu8keAwPgjELb7W3oTPegvLynQA3p0EOHFgIm407nM/jLpfftsfXY3Sx0Rrf3P26txaWdcAyhixqaPNDubsGu1ctwKaId0jHcbl8KPdORMTOn3k7hV4eszW2CrAM1HgmY5p/Ht4JvY7W2Bp0Wo09dRSyWhCyWtPWW77hdLLWOwXT/HPxdvDVYT2Xkn+GMi4piqIoijI6qOZXGVFoPrnvvvviySef7JW+iO8PPvjgYTknRdW+ggy1t+UeBx4XhTUHbsONOh9Q7XWJts+0w/B7bbg9id/F7ShWtbdgTWcLLJvmzC4xHfa7a+BzV4tAmbiWmJjXJjSGo82O1028hh/lrjK4jfytfcWdGJrjEYTs9CbCGzduxOWXXyl/sxMLE3/LXDWo8UyAyxi8UG05McSsLjiORV0PLNgIOiFYThx+VyVqPePFLHqgehsSjo241QmrT/2ErQ60m01SFkVRRpbcxiUlX7gMT6/npqIoykiiml9lxLnyyitxzjnnYL/99pPcvkx1FAwGJfrzSOF32ZheHke1p1Lel7mrsEetg40RD55s7UAXNmNcxUyUVSSEki6zBfe//xZMmwJLuzy8awJz4DXK4cBGW2wtIvFGRM02tIaXI25tz0VcaFS46jDDPQFNyJ8/YqfVhpe7GrAxtq17saE30WgMq1atzumYFFRpFjzBOx5vdb2A8CA11ZF4MxwrBtMKi+lxyIhiXXw9olYHxvvmYq5/Dt4JvSH+uMMFhd62yErEbPrUJu0QHGyJvo9OcwOiedR0K4qSHYMZl5Sh43FXoc4/F+2x9fLcVBRFGUlU+FVGnNNPPx2NjY244YYbsHXrVuy111547LHHdgiCNZx4XA7G+SzRgFLM8rkCmF1BMdYNyw4jYjWjqsyEP2DJ/mG7C//ZEJTXluPA7Qqg3DsJAaMGNiwErUZ5iJt2CF0pAk4hEnBVYDw11kZvv86hELKDWB5uR4tJ3+cdmTBhPE499RT86U+PoLGR/rXZYKDeOwMz/TPxXuhNhBNNkTPUuHITDb8rgChMdJmdiDlhVHsmY1ZgIZZFV2I445PZThxd8S19PnXQEt+AlnjCkF5RlJFlcOOSMlQ4Dld6p4rrjQq/iqKMNGr2rIwKl1xyCdatWye+ci+//DIOPPDAETkvfXUZibk13oat0XZE7UREYZqdNsTa0RLvEJNlvt8W7sDGYCdiYqrqwHJs2ZJmsaYdEWGXJtJ2j5nzMJjM5hn6QXdZbTDt/Pkp0qQ4agVhOYljMuhVuasGZa5qeVddXY2PfvT/5G//x7GlTikskqgdQtDuhO3kLvmyDBVShqqU4zuI22FpM/pFm05MPq/3VGKCrxY+WQxJjwtujPfVYJK/Bu5Bmevt2Ddchh9ulx8ew4fx3lpUuLeXNWM5DC8q3Sxrbpp7t+FFnacWVZ4aGHReV0YZAx66H7gC0ld9rkq5Z7abgnZ/Lyb5SVcAJZ9kOy4p+YXPi3iv56aiKMrIoZpfZUxhWiE0RJbg2Zb1WBqysDXSJgJJl9mEP21+HZ3xhHBEbe6f1r+JKr+DlljLDsfhQ5vRgEPwidYuJtGei4P2+CYs7noR7WYyINbQsewo2iOrus136UPtx/zyfWE6JpaGXsrhOGExD47Y7WJOTrPgLnMjopKmKDcoVC4o3w9RJ4ZloZdEoObCRmd0rbQfBeHW+CZEnTg+UDcfcbseT7esxcbw+2mP53VX4ugJ+6LGa+ChTUvQvoMmN1dcqPBPhcfww7BCOHrcIiwPtuLNjlfk2jNR5q7DruX7YFNsCzZG3sl6saXcPQ4H1+2NNjOCVzpehVnApvljAQq8Vb4ZMn50xjZhkn8X1LpqsSr2HkLxLWKiX+WbKWNRR2SNLNwpSinAsUfcQIrouakoSumgwq8ypqDvZUtsDdrjBlydgGknhIyQ2YqnGztEjDAdC6YVxdNbl0tk55i9o3BBISoY29yjkelPWCk0usxGrJK8svkrs+3E0Bnb2FMP1F7NCOyCmB3H8vCrOQnR24/joCm2Fs0SpCr3iT+1szMCCxG0Q1geYhniIkAEY0mh1ZZIz3Enjj2rZsDrmoa3O6PYGF6aIVBYGQ6sXYjJAQN/2bppyMIvfZrLvBMkR7LLaMeBtbsjam/Cmx3915ffVYW5Zbsj7Hi6hd/sYHC2Pap2x+ZYJ17vfIfG30MqvzI0mF6n3DdZrB2CZiPqfbMx1T0VG+2NCeGX1hO+ybJow4W2AjcoUZSsoeVNZ2xDUT03FUUpHVT4VUoUA/5uf16azm6fOSbSFJl9LFBFe9vLBMuA5bjlcwdxEeYo5iai8iYDFuXy4DbE7NTpFhRHE5abEY+HjiHmupUeL/xuC7ZjIGK6ELQikhc20B1J2wUP/EZ5lnWU0KTTLI7wvcdwI2KHc9Z8Scs50W7T5u2NndpuPE/YDiJoGfA5NG3PbIbH34WsqOzLGswHPL8tzsamRMqO2wP3DZaDpvi5RojmuSJ2BDExd1dJqhBIWCDYbByJSs7+muz7ye8HY/KvKIVN4jmsKIoyGqjwq5QkjMY8K7CnCE7LQq/BctKn4MmEYXhQ5Z8pE9Ku2CZU+qaJGSI1MINJY8TfVvpndpvdri+JFW9qpur9c/ChcZMwf0IQ0bAb77aU4bnWpTJpnxzwoN00Ueapw4TYHDzxt+fQ1pY5qrGYefpniADKOiJT/Duh3lOH98NvIWrRRB05aZFXht4QzW4mAYI+yqtCb8Jq9kn6q0bRCqcXDGN2EE81vYFKj4GguaMpfK6wD9B6gD68LjuKp5pewYZI54B9I2y14f3QK2iK5xb9Omy14OW2V9DJyNfdvu7K6EGhtytK7RcF3wgaossRMjYj0t23KAQnvqdwXPzjRSHS1taOP//5L/JXURRFGRuo8KuUrPBLk1ef4cPK8Ds5C78uCqu+6RIcKxjbKuaH1NwmBNfBCYqVvqkiTNOstxQmsxRW6/wzcdS4XfDBeU3oavchYNXi9Y4GRKwWTPS7ZZ+AqxYTw/Px1O+WoKUzs9DIIExcZIjbEZn0k4m+eZjln4lV0RW5C79ODGsj78prB+kXLKg9XRtZjI1RGiFTwM2sjWAgrudbFsNlUJDMR6AWB6HYNjF/5uvnW1tTAqplJmq1Y0XorW6BPvveSKH5jY43YYuWUfMKjzbUfHV1L7ZQwG2OrUKLWJyYKd9v7vleyT/Nzc34xS/u16pVFEUZQ2i0Z6VkSZgsDyxM9HeE1I0BkoZUHv6+pKxNWSf0HE5EwWYKKL6mybPP5YeXUmJ3O7j9LsxYMBGBACPXJhCNp9F7/S2R8ifVRHkoE39HTJ6TEZ23n9eTODfcKHf7Ue52wXFcsB0XPK4dy5RalogdQ8iKpS0TTbR9RpksdGRfQpo9m2JGHbGjoqVOR6KsZZKeiuemeSzrOmGOv/18fN33s+3nshGVc/Q2A1dGD94r3BLR5OPSV3uZ5cNU89BhhOPRzjvv3GtcUhRFUUob1fwqJQnNbtdHFsPNvL2D8LHl7xmQw7ITk1EGSaJmMtUfLxf4u2BsU8JXtAS0voSCb2tsPZ5ojGCVE0Qs4sbi1jKYThwLynaB3+VCW9xCxGpDW806XHzjp7H88v9h1apVUpcVvqmJSNvRRIAr0XRFN4owmMx7uy3GiKBNiOUtMjGDTE2SCMuWHcQhtXMxuczC2y0Jn26Xy8GKyIZewbuyPe5E31yM89RjZWSJXHM+qfKMx8F1C7Am1I5lwcVSbx53BSq9UxCMb+uJmup1V6HCO0lyCidyGyuFSrp7QBlZpk2bih/+8Hu4/PIrsWrVaq1+RVGUMYAKv0rpCr/hxT3mr4MRVjvF9DYRfCkZ2XmwE1T+TsydS8iEkdfREl2LfzduhLfFhuMYiFoGvO6JWFCeFH5tRKxWrI0u6flVMtItTZxl4k/TT4e/t3rqiJGYydbocjQa7u5cy0OHJsbl3knwu6sRizfig+P2xe61UZjRCvnW67axxbbRGd+c0yIFjzvBNw8LAvOwIbYp78JvtWc8jqjfDy5sxPLge9KLmHqpxj8XMSe0Xfj1VMtnEbtDhd8CJ3EPTN9u3lwii2KKoiiKUsio8KuUKIzePJSgPk6vqMxMNzLU8uQryjNNWr2GGx4XELVpbmz1Mo/1utySwsnK4OeaPxIRajvMGIMV91DtsuE23N2Bphz4XK60GnNqvnqb3+5YR6ZEax64PtyGR8zKaUJqiym22a/Qwd/wr8/lkUjVHoORqQ14XJb4KQ8GmkuzHAkz1v5LnLx2t0Fzay4j9N9WibJ65fipx6FvejLdVvLI/CzhR6wUOmxXtUBXlOTzIOEKoiiKMpyo8KsoRYWBgHcc9qqaivnVFp5tCGJdaG2PEFnvnYw9qqdiebAZGyOjY8bHIGHLgu8g7Pjhc83EUeMXwVM1odc+kttU8uQ6Q9Z4UYs72z8fYctGmduNJrNJ8gOnkyqoMQ3HGxE3OmHZXXix9V1siMaxLORPCI4uJ1GuHMvE4zbH1or5d3QAc2OPq1xMkxlpeqpnMjqsTmyNrujXD7fLbMELrW9hXbi1xyScAbg6outgppiEx62u7s+Y3kspZHgPhGJbEu05xHgCilL8LgBTul2MtvZY/iiKogwHKvwqSpFBs91D6/fB/82IYkNXQ7fwm2C8fzqOGX8AQtYybIysGZXARjGrA+90vYKVkXIcWTcLH560D7ZU1qCzIwTLSqzqc5KTjOg8VDPwMnc9FlYciLaYiXqfF0vCy7qF30wRlil0MyCUhaeaOuB1AcG4IQpU6kvDdnQQZXKwLboczbE13XmlM0Nf3drAPMSsTuwS2FvMpLdFV/Wr8Wg3G/BkcyciEmU60aZxsxNt9irxS0+t+zY73OszpTARV4g83QPK4OB41N7e3jMuKaNnAcHUgnRXCsUbErmvFUVRhgkVfhVlmKBpbTJ6cTL6bj4mucxBXOEpxzi/C36Xr9d3NN+t9lRItOX+SsZ/fSMr51gKxhVOG42ZJrxBqwMxhybEQK2nHNsabFx3/h1YG1qXYjIdRT6gmW+ZqwIhVxwBlw8eo3ed9CXpA84r6DRDonRjzOWh1gkjMGcTSZkTPbfhh8uIIOAqh9dIRJrtr48wEnBrvLfpPYXl3vl6DdEgOoh1i/aJKMJKoZKPeyB5L6uwMBjWrl2HT3/6nCG2gTJ06K6RcD1RFEUZblT4VZRhgOlmyr0TEbdDiJltKPNOlM/DXNUe4kQ1arbinfZl8G2OY3Okd2CltngDXmlfjIbopoyCj9sVQLlnvARlGmxEYJ+rEtP9s9FudqA5vj7tuVwuH5qtEF5oa8SGYBhdZiOGA5oZrwm/hy7TQqflQVuc1z5w+4zzTcfCiko0xUysDG+E310rwbkGUycUXKmRrzRqEUZQoi1n8hO37DC6Ypukb6wz3keT2UyJGGWe8XKkwfYRBsCq80zGrEA5gpaJZcFVOee3VooJAwHPOHhcfon47XTnB1aUYoPjXUj6sK0uAIqiDDua51dRhuPGcvlQ65+HMu8ECaBU5Z8hZl1ggJshQbPdrXi2+XX8fNVbWB1c1UvwbIxtwGMNL2J9mD6k6fG4y1EXWAC/p27QpQi4a7FrxcGY5N8p42q9y+XHFrMTf2t8H8sr1uE7P70SM2fOQL4JWy14L/giVkdexuLgC2iIrRxQ48kFgKlli3D8pIOwf+1+Uhd1gfnwe2oHVQa2cbV/Fmb490Wdf760fybiVhCtkRWSSmtJ8CVsjiQiYVf6Z8gxBhtwi9cwvWxPHDXuA9i3Zn94XGWDOo5SLBjiJ1ntn9OtNVNyhePRz35217CMS0qu2RXWoyu2Qa0YFEUZdlTzqygpJKPkJoMKDSkis7sCbtvfHUW4LMVEcWhHpqlkU4zbjt9G7TC2xcIDRiX2uiqGNGF2G15UuevgdzVmNK5lHUTsOLZFOzEJbkyZMhleb/4n6RJx2mrJ6TcUMP3uSkzy1WGTJyx1MbQ6YRuXo8yogRftPSbM0uIJa+SePkUNXdxJBKnqRMI/2JDzl3dHPDV69cbuX/VzZqPHbDDgqsJ4Xz1CdnDQQrRSHBjdizjsN2ouOjg4Hk2dOiVv49L258f2/5VscCSAn6Ioykigwq+idOM2fJjsmy0pehpj64a0As3AHcH4VsTMdjlOON40ZKHaZfhR5hmHmN05aHNlmvsG3PUSkTluBwddlpgdxIbIMrTFu6PVpsGyIwjGN3dPaqpQSDClUmtsI15rj2B1KCZRkmmqTK3sYEi0cQOaDD+60Cjtz6nwlLIp2G+qD8sbHCzrzKzV4KS52lUNN9zYQqHVAao8E1HnrkNDfAsidiKPb1+87ipM8U6G3+VBs22jObYO73Z2oDEWhZ0nn2qlMOF9RxcIx46lTSWmjCyMxTDJNx3jvGVYH2lGp9mgTaAoilKAqPCrKN24jQB2Lt8fXXYITfGNQ4o4adtRtEVWit9n0qSLDGWS6nGXiWlue2zdoIVfmjxXeqcgbDUjFm8ddFnoL7w4+CIiNgWs9MIvy0jz3rgEZSos4deyItgUeg9/NX0IWTaiZof44pr24Hxk2a5MMRQxtiGOGGw7JgLtTpULcMV+1XjwFRvLuzZl7FMM2jXRNQEew41lcEuwqnG+uVhUthAvd/4PkVh64TfgqceuVQeg1h3AU22vYGP4bXTFvYg5NkxrKHmulcLHQTC2GWHJcT3UPORKPhZP55XviT2rJuAfTe+gU2IcqPZXURSl0FDhV1FSzIFrPBMBq6PHbHWwMBIv084kidvbc7EOpXw+dw1crkR04MFAU1ia93bGNw8p0ix/22pu63cfCv5RK73QNtqwfUJWK9alyIeWNRRNqSMa+Ri2L0rQhLnGW4PdJ9RhUsAeIJKpgTIjAI/h6el7NGGu90yCp5/2ptlrnXcCxnsq4IIbQasZQVUCjhnUVLSAMFyo9o7DFP9kBFxMM6coiqIUIir8KkqKsLYtthYhO5zXoBsUZhiMiNrBhDCYmzaAaXEmeKfCcvkRNptg2kExd/UYATneJN9E1Hh8WBNuQHQAIdu24wibDUMyec4eFwKeWjHP3LK5Abd96w5MCvrQ7puKNqsDPqMcUZsa11KNSOygIdqAp9aFsSZow6Hjb6Y9HQtN8Q1wwyWpoggF2c2x1WKiTug37nNVIWK195g000x7S3QdgvEAonb/1gD0SWaUbmrtafbNhZCAu058plMXahRlrLB58xbccMM35G+pkXBxqZXxg4skQ30OKYqilAoq/CpKN8yZuiz0CizHyqsPHX3BavxzEHfCiIU6cvb79bmrsLDiIDSZ7VgZfk20yOW+yajwTEJz5H0sqtwTCyvr8dDW5xCN9i/80rSXpsi988MODzTlrfbPFv/X1shyGKv9OHniXng42ool4WWo8UxHc3RpyQq/bOflnctx62tebGrnO6vfhZdVodfFVNrqNiVviq1G1GxAV3cwLx8jUvvmoinyPqJmQviNms1Y3PkKvIYLXWb/Qb/83nrUemehMbIEMTMmk+OawDxJ7xQLU3DWybAytgiHw3jzzbdQinjcFRLVvy22FmYsNOTnkKIoSqmgqY6UvPLss8/ihBNOwNSpU2EYBv785z/3+p7arxtuuAFTpkxBWVkZjj76aKxYkTktz0hCjVtzfBPazK351fwaXHGvhc9dPaiorPQlG++djkp3HaJWqwiunNjQ35MRfif4J2Fu+Qz4JOpr/1DIipgtIyL88lpppu3zVKO+bhyOOvVwLJoyB3XeiaLFTJSf0bBLFQfNsRa8tGUbNoQY/KYfzS9stJpb0GJu7hGSw1YrGuIberT0NHGWOnNtj0zLdmyMbcLm6AYJQtYfid+P64loLZpfT73kB9ZowcpYpK6uDmeeeYb8LUnNr2cc3N0pz4b6HFIURSkVVPhV8kowGMSee+6JO+64I+333//+93H77bfj7rvvxssvv4yKigoce+yxiERKU/tHGOQoarYhJkGqnKy1vdP80zHBN01W6JvimyR6aNJ01rSCEumVwmxjrAFrwhsRG5FUES743QNPoOjvWu+dgnJXmURSrq2rwu7H74TVRjva4g2i7bVhdfuz5joRMzDOOwG7VU1FvW9Cj1DIv6TMVYuJ3unwuSpQSrDOIlaLmK4P9vcMjDTeMwl+V1XC/NFslfZRra8yFqmvr8MnP3mG/M3HON8a34b1kQ0IW20YbejawPGC1j6DfQ4piqKUImr2rOSV4447TrZ0UHC77bbb8LWvfQ0nnniifPbggw9i0qRJoiE+44wzSrI1mNe1PbpW/mZnamagyjsVB9XshdZ4HC91PIf3uyMrJ7SCDsLxRsTNLphWCEs638KmiB+dcUYXHRlTZvqJtoaXZTTldbv8mF+2N9yuKmyMvYFqh9pF4PHGd7EquBpROyhBswKeOnQabqmbXNi5Yld8bGodHtnaine7lqHWN0vM+8KxrRjnm42dy3bGm8FX0RJbjVIhZrah1V4OU4TV3InGW+F4w1hYtjditonG2Aq0RVZ1Bz7TybCiDFXYXBV8G83RAJpjraN+T3GBtDW8vCdmQO7PIUVRlNJEhV9lxFizZg22bt0qps5JampqcOCBB+LFF1/MKPxGo1HZkq+LDZq0RsxEnt/sMMQ8bU75XJRFKPD+D1tja3ZIIxTvjiy8JboRW0asWlwIeMclzKYNI+P8zgUPJnhnIibBlBhcJWGS/X7XJjTFNolJnuWY8LrKxc81l6kY95/on4oDaifiudYAPOH1KPdMRJfk1TRQ4RmPaf55WBJeilKCQWuGEt2Xv3XsOCZ7p2OZe4VMgsOSjkUZDMU+Lin5hfdTY2wjGmOFI4yHUnIN5/4cUhRFKU1U+M2BN998E6+++ipaWlrER2ifffbBfvvtJ76tysBQ8CXU9KbC98nv0nHTTTfhxhtvzLGKDXhd1DbaiIvAUKgr3enK6cjrplgzbKcSFa4ahNGUt2tg4BOmO6IZbO7pjhwRvE0nxtleP3tZ6LAaEbfNtLltORFLmtsmtRCMRlrlrpd3DPLUn991u9mKVUEXOuJtsOyYRC/lZI/HY9Rjmh8ysMtIQ39aCvQ0R58WKJcq2hBuk2v0uitgOfEeM8RsoX80j8vIzv0FzcouHVNQUlSNTLTv0mZw45KiKIqiKKOJCr8DEIvF8NOf/lS2devW7fD9tGnT8MUvfhGXXnop/P5SDt4zelx77bW48sorezQsEydOzErAqw7MEiGkLbyiYM28MpWzK7YZr3W8gV3KDsN0/05oia+TffIBBV9GAe2IbUQ4nnnRIZN2oyPKslj9Cqc0i14Reg22Y0u0566uLjz99H/lb+I4Njpj60SeTwrHTOk0v3w/2LDxbtczGQVziaIcfA+/2RjAymBEzIHb7BU9OYWbY2vxrt3RrQkeWRg8qtY/D3G7HSdPnQvTBu5d9yLijola/3yErCZ0RTfksJBhoMw7CWXuOjRHGBl7aAJ9e3wj3g1G0R4f+bopNQYzLimFRd9xSVEURSl9VPgdgF122UWEXvqrzpkzB7vuuiuqq6vR0dGBJUuWYPXq1bjmmmskgNOqVatGptWKlMmTJ8vfbdu2SbTnJHy/1157ZfwdFxWSCwvZmhdSi0hTWNOJAMYqII32sRBIX04HYbMFq0IO5gcOxTjvVNkvX7hcflR4p0g04XCO8jQF3lCP4JRZgKOgviW6snsvB9u2NeDWW29L3QPheFOv47gMD6b450mqqcXGc/0c3sHmyAZsiST15A66LGoyEz9gYLAus3FUFjwYWbXSOwURy40D6ygEAw+sfwOmYaDCNxlmjBVO4Td7aAJf7p2M1ujKIel9k7mDQ6JVL8zFoGJiMOOSUljsOC4piqIopY4KvwMQCoXEtO3Tn/40Zs+evcP3FIx/9atfiWZY6R8uHlAAfvLJJ3uEXS4iMOrzRRddlOfqc8Qv1aT2sB/z3P6g32qFu0q0dhE74V87FCjA0oSVmtDtWs1EOW3DEY0sTYGTQa3oE9tltSFMX808Cis8vmkHs9Qks8wB0c5aFNC7j5Ddebbv5/X6MH3CLLS1tKEt0tJzjb33txGyOmDxuwHabLuxNDGkXvn7RM7g3t9mi8cIoNxVjrAdHrTJNCMou2DIcZpiYURtS6JQuxy2c6jbNDs32FcY2Cw/6bcGVzfKyMOI6bz3aCo/mH6jDIzX68X48ePQ1NSMeDw/ljWKoihKYaOpjgZg7dq1Ep04neBLZs2aJd9zPyVhRvbWW2/Jlgxyxdfr168X3+jLL78c3/72t/HXv/4V7777Ls4++2zJCXzSSSfltfoohNA8NxjbNGihgemGFlYchGmBRXnRvLpcPtQE5qLMR59no1c5JR2Qf67k701CYXNF6FWsDb8t++ULClKtkZWS9mLAMhte1PjnotxHrf3gfdtnzZyF23/2A3xgwTHd6Y3Sm0qvDL+O1aE3czLxpsaYEagrfFOHNKRVe6dgt8qDUeudPuhjsB29DjDTOwuPN6zFP7e9hzLvFPjd1RJZOSxa89yEz3CsAW3RVbBtFYDGEh53GWoD8xHw1o92UUqWmTNn4Oc/v1v+KoqiKGMD1fwOQCCwfaL+m9/8BqeccgrKysr63a/YoMketa/UYlPTPWHCBOy9996iqc2V1157DUceeWTP+6RP3DnnnIMHHngAV199teQCvuCCC9DW1oZDDz0Ujz32WN7rj5rFUI8/6+A0XV5XGWZT8I1uxGq8lhf/3krvVMBwd/t9bi+n21OOWv9sdMQ3IW519AiDGyKJiMVDC3TUG2pHu2Ibs9IAMrVRpW+KmMp2RtcPui6pxSKTfLPFtzeOHQMuUeDdFFmWeJ3D9RqGGxW+KZK/shPrBq3XLHePx9zAbtgSb5M0QIOBPrkex8Fk9yQ82foSOqxtmFRxEKJOGK2RJYNYiHEQMVu6I2IXpum+MjzQPaHKNx0xsULIzTdfURRFUZT0qPCbA2eddRYuvvhiEYD5OlXIK0aef/55/PjHP8bf/vY3Mfli2iEK9oxmTYF47ty5IqReeOGFqKqqyuqYRxxxhPhHZ4La329+85uyDTdDFRZo6huxQ4j3mPvuiM9VhnKXF0EriviAkZNpyhwTs+e+5bTteLcpdO8y5yIEDofpK/dKmF2aQz4jiTlM3dSPr/Agr5da0UQZ+x7bgNvwySvW/UB+ymxvXu9g4bXF7QjCdhBROybRrhmNOpFbM+HPnYsZts/wIuJEpGxcPHHDK9fBxRCfUQ6/y4OoY8o5k/3dZwRQ4fEiaMYR66fvKgWO43T3neEYAxRFURRlbKJmzzlAbSh9VKnBZK5amkLT5Hnp0uLLJ/qxj30Mp59+ulzDv//9b3R2dqK5uRkbN24U7e+KFSvk2uifu9NOO+GJJ57AWCNmd2FZ6FVsiS7NIEgbmB6Yh6PHH4iJ/pkDHo8Cbnt0Tbfpa59zMWJxZJWYJBcSjhNHB8sco+Zp8L6iSc31xsj7Q45Y3BcKvQkT9y07CNY0V6/0T0OVf8aApuud5la8F3wZ7fFNQypPY2wVlgRfloBiFF7ao6sRjjOfbm7ZjMf5ZmFRxYGocI+XT3zuGtQG5olpPK9lamAX7F9zEKYGFsHtTuRRJpP9s3DM+IMwJZDeVUMpDhgLgObuUbNltIuiKIqiKCWDan5z4PXXXxff1UceeQSPPvqoaE6Z65Hb/vvvj8suuwxnnnkmioGPfvSj+NOf/iQBP9JBrS83miszqvWWLVsw1mAu1FXhd7pT8aQXXCb5Z+KQ+l2xLBTDpkj/prLU3nVGN3Zrd3sfT3LnWsFh0vQOTbBMlJkMJVBSYvFgc3RlIghZHqFWtSu2KX0ZDUMiJdMvuCO6vl8tGiNEr7BaJI3TEEqDlth6tMU3SsAyCuMdYuKeuxVCjXc65pfthfXxLeg0t8DnqUKVfyaCVqP0lYm+edijai66sB5b4mtgSt5kYLxvGg6t3wtrQg7WhYtvYU7Z7p5AVwM1d1cURVGU/KHCb47MnDlTgjbttttu+MEPftCjEX3llVckIjQF5B/+8IcodD7/+c9nve+iRYtkG2tQcDEHiLLKnLQ0bc00QaWvK/1mKURyHwfpzYcT36U/htvwwgMv4jSZzvD74a2FXIVBFzyGF17DDcuxxdR51arVOOGEk3oF0qJJr8cAIrY5YD0nMMSkl2XiMVMF3f7KKHWbhUArUbDzYGLKNrJTZPBMbT5geSTCttnjRsC//Cz5ngslpsNzMTp26vks6ZNDE+KVkcBj+OU+MR1b7gGatsuoknQzMOjt7e7Jha3kl77j0liEY7GLfYwjpRPv8xwyZOGQvZAwb3vqeNb3+aYoilIMqNlzDiRNgRnh+dhjjxXBlzl/6QfM1wcccADuvffe4WstpeDYElmD/za/hsYe7WhvfJ5qiZbsSTFLzQ1DNIA7l++HCu9EFAOM5jwusAC7V+2PmWW7y+QpFZrsMpDPHpX74vD6/THBNyerSNL03Z1TtidmBnY8ZkYcG8HYZtEMF5vvZGt8PZaGXkPISuRDjlkdYjbPlEkUcbdGl+ONjlexNbK0lyn5tugGPN38GrbGGKBMKVQoOEz0z8fe1ftLRHmPm5r9GWKpwG/FZN83XaKY5zPPt6Js74MuCRY4yb8b6vzze7lPyPeGG1W+Gaj2z5HnWN/I4z5PLWr8cyS/uaIoSrGgmt8c2HnnnSVgEzUv++67rwSCoplzeXnigfH222/jS1/6EoqBuro6uZZsYAAsJR0ONkfWoDm2XrSX6fC6qyUND/0/mQZnMNR4p2Gn8n3RaHegM7654JuCE6Fx/vnYo2IeVoebsTa8GFOmTcDll1+K2267HZs3N6DKOw27Ve6LBVVubIouxpbo8iyF3z0Qc2JYG1lMnfuAv6E2gr7AidfFJPw6IvyyvanxJ3GzA+1WsCd41tboCjTHXIiL1nB7XTTEKPxuQTRDn1QKA8NwYaJvAfaqmg8YW9Bgb0OVbyaidrss2FDwqPRNE0sCMetX7W/emTZtas+4tGlT4Y+tecdwocI7FZNcO6PLaEOXuFR09VqgqfRPh9coF0GZ2dZS4xf4PTUiGAetZskdryiKUgyo8JsDFRUVIuzSZJjCb1/OP/98nHrqqSgGbrvttp7XDHTF3LvUZh988MHy2YsvvojHH38c119/PYoBr+EXE+ShROrNDB/77oQJYrd5KU1OKYDQVNHsT6bibwzXUFLkduuAqPkZwkFyxNMdITkhVOXo62skaox1lkxxxFRWu+yyS09KK9aJ3xWA37V9n+wO7cppf5JLzuD052QZDWnzpAZuJARp9mUrRcDnOVO11/SdNtM0DdvMtIbjPlDyjRg1G66efsX3qVpeGTuckbvvxxp9x6UxSfIZleH5ImOumN8n/vb9NvFPURSleFDhN0uYCui0004ToTed4EuYDijblECjDQNZJaHAztRDl1xySc9nl156KX7605/iP//5D6644goUOosq9kaLGcRGyaWaX8GEJrbUwPhQBjfo/wS0WpuyisJKbR0jEQ8linNHfAtWht9CyOSK+/DjggfT/QvhNtxYG3mvOwVT9lhWBC3R1XgPHWiKMYhXHw2kYyMUb0SnFceGsImWLLXZFAbXRRbDcqwR9H02EPCOh9dVjq74ZpR7xssksCu2ucg0yUqhQT/exthqvNPZhS3RdglixvzbTK2WiFpuJfqZ+HSrP6UyHJ3QRii2FY2Gg4gR3iESP8c4Wh3Q6oYibtRs7bUYGrPa0RFbD9PWlGqKohQPKvxmCaMiP/zwwyW5QkwN7/e+970dPv/IRz6Cr3zlKygG9qg8CKsj27Apuizvvp2G4RXT5UqjHn7HL8JvOB7JSviNme1os0JD0D46aItvQNDc2h3kafhh0J1ZZXvAb/iwIboid+HXDqMpsgwdsVUSyCddXuNgfBs6zBiCloNG8U0dWLtsO1GsDr8pew4173AulHsnitAbNltQ4ZsuAV6C8a1F50OsFBa8D7ZFl6Mtvhoxh0HKouiwwt2CbyK4WZdECpdQRKNdXKUESbiFbEIY2xKhDfuM9RzjGHE8oeFNBPNLJWq2IW52wc4qYKGiKEphoFE0cuDkk0/GCy+8AMsqrUnvuHHj8Je//GWHz/kZvysGyt1V8LnKh8UAy+iOuOw2/LIxQmu2AWi4cm6JJmfwfYbmrWG7a5hMujObkftcXOgxBjWhijthBK1ORMUPbEfB1nJiiNlxhKx4VqmPEvVtIGIHMx5zaEbtnoxtSt9LRkSlyR+tAPi6/3ox4DE8YtLNvyNprq4UE47cJ11WJ2J2qHusiKYIEglhhPfK6JE0w9Y+XKqwf8WdEEwnnGaRJdkHIzCdyI4LmY7Z/XzTxRlFUYoH1fzmQCQSweLFi7FgwQLxjfX7/fI5J8W/+MUvUKzceOON+NznPof//ve/OPDAA+Wzl19+GY899hjuuecejHX4wKf5oWl0wON4ZRrIyLulCifhm6PL4TW8Q/aXTbJtWwNuueVH8jdZp6Kll/Qa/WtxKWxWeCcjbocRMZvzKviSas9kTPROxKbYxp7IyqlEzBY4tgnLjiEcbxD/uP4me35XOfauno/ZNRGs7wjgjfZViNiDC3amKKOJx1UuVg8hBjQaZMC+QqbvuKQoiqKUPoaTTBqpDIjLlUkzZBS9NpjC7u233473339f3i9cuFD8fpPCcKEQjUZ7mZ5zQYKLEBdOvxHLw5vxbOuvczbTHRiXpO9JBHBKaEDol1e6pl4Gyl1V0q9DVuewrOpTm1TmSvjHh+3+z8EJ+OSK/UUwbQktybvwO7v8YOxdsRde6HgW26Lv7fA9tf3U/lp2BC5Xwvct4eOWvhzV3im4eNYJ+PDsVjy9rg4/WfsPtMY3oRigJqe/+0xJT6b6SuTtLV7KfJMxIbAbmiLvIdQdNV1RRhodlxRFySfF/WQeYb7+9a+jVKGQ+5vf/AbFCv3juA2NRNzKpM/dduzu3KoD/767NH1eDyfDcR4HITu/mm3mwz700EPw3HPPo6OjQ4RdniOxmNC77NsjKne3p8HFhzK4bEag3nH/oeI1Aqh018DtSkS47osspnSfkgLwQHCRpM5bjWnlcdT7qrPPSTwi9DVf1bVPJTPsu15XWdEL8dmOS4qiKErpU5pPtGGilIXfVatW4f7778fq1aslDdLEiRPxr3/9CzNnzsSuu+6KQmdVeAkaY61D8K01UOYZh1rXeASdLnSaDGhk5qbJ9E4QgS0Sb4bfUyd+woxqPFxRgTkhLfdMEOEs0icKZ6ExYcJ4XHTR57Fs2bKeSabfVYkpvhloY1AwM6EZZVTRyb45MB0TDbG13el9TATjWxAVU/P8X2On2YC14aUISx0OHZpnv92xDL5NnXizvQmxAsl/mewv9LFmIBufq1IiiJeuBYMyVBilnlHOhxKtvpBJNy4piqIopY0Kvzny7rvv4qmnnkJDQwOSFuM0D/3Od76DYuWZZ57Bcccdh0MOOQTPPvus5Pyl8Pv222+LL/Mf//hHFDpvdj6PmGPCHmQEXmogmc5ojmdPbLY3IGg1w8pF+DXcqPLPFKEiarajgqmRXBUIW61wbGvYtDI1gTmIWO2ImG0FLfymo8xdj90qDsbyyEq0mUx35MDtKsOC8v0RZsTo+MZEWiM7hvbI6u5Io/m/xtb4Oiy2GiXwUD6I2B14quklvNbJSKluhArEP9xl+FATmCt90rKjqPZORyzchZipwq+SnrjVidbICph9UuAoiqIoSrGiwm8O/Pa3v8VnPvOZtP69xSz8Mp0RBd4rr7yyV57io446SnL9FgMt5lADlhjwuCtQ5R4Pv9GeJsLpQEKXAa+rArZoeXmsMnhdlVlHhR4cLnhdVTCd+DAYAw8/bpcftd6JCMS3t50BN6rd4+AygmLuzIuiNj1m50cwTUfU7pItXzAq95ZoI7aMTGaqrGGgLp+7SvzVCV+zvhUlEwx4F7NGLsq8oiiKogw3KvzmwPe//33Yti1aUWp+d9llF6xcuVI0psWuzaZg3xdeZ1PTjtFvSxFqbt3wwnJs+FGGWs90wI7Da7gQhymm0NwnarWn9fsUc2ezGbb4qdqS/9B2xYY1FyyPHTYbEZWgVMUm+tI8OIitsQ2yXMCgVqYdFBPcbbE1iHSb5uYbLkYEPPWySME2crsC8LurM7ZrKUFBl9dPgSZudSEUb8hbNG9FURRFUZRiQIXfHKBfEHP9Tp8+XSIjv/feezjyyCNx2GGHoZipra3Fli1bMGfOnF6fv/nmm5g2bRrGAkZ3YBcTNipQjWn+PeBz3Khw+9BpB7HRWQ+PEUBzZGl64dex0BFdJ69peh2MbZLAR7n4DeeK48QT5sByjsIWfkOhMN5440352/OZ2YKlkXdQ7psNn6cGZiwodbs89KoIp8NRd2znaj99iiOImR3weapR598JzZFlJS/8ulxM08Wo1VFE4k0wzU41Z1XGNOnGJUVRFKW0GU6bzJKDvr2TJ09GRUWFvKdWlMLhnXfeiWLmjDPOwDXXXIOtW7fKNVK7/fzzz+NLX/oSzj77bIwFaBJKza/tOPDCjyrXRNR4pmCcZxpqPJNFOAt46sRvMj2O5P5N5P+1RbNGbeJwpAnafkYbUasN8SLIIcvFla9//Ub5m8R0wmg2G0XzSxNoQr/eVnML2s2G4UmxRNNfTw287qqeNEYBd13GSM+lp/k1ZHGG0csj9EcfxsUZRSnGcUlRFEUpbVTzmwM0A25ubsbuu+8uwa72228/eWhWVlaimPnud7+Liy++GDNmzBB/5kWLFsnfT37yk/ja176GsYCYEFvNaHe8iURHho0wvIjZPnQ5IUSdVvATO+85hBOmuD53jUQ2jknQpcLW4g4Gr6cMVWX16Ag1w7QiIojVeSfD764XAX4oWlcuSPjdVRJVeaDjMB1W1GwVzS/rmZGyK1wezC2bhBXxdkT6+BbncuzM5QrBKoCAQTQpD5tNBVEWRSkEXC6X5GNmnmYu+iqj1hLifkL3ncQCcuk9AxVFKRxU85sDNHGmsEvT55qaGqxfvx7xeFyExGLG5/PhnnvukTRHf//73/HrX/8aS5cuxa9+9Su43WMjIA4jCrdEVmB99FVs4BZ5HWsir2Bp+AWsDb+GjsgatESWI27mP/CSYXglCi8jRO+Yh7U0WDBvV/zyt3dgwfxF8p6a1gVl+2GWf2e0R9eI/+1g8borURfYSdJLDQQ1nR3RNeiKbpKJFttzvNuHI+v2QJ1vypCO3RcGUEv8thaFgGkFJXIvhX9FUYA5c2bjD3/4nfxVRg9a5FT7Z6PKP2OYg0QqiqKo8JsTzIP79NNPY8KECXj99dclH+5DDz0k/r/FzDe/+U2EQiHR/P7f//0fPvGJT2DBggUIh8PyXS7cdNNN2H///SVqNDXlJ510kvhKpxKJRETTPG7cONGan3rqqdi2bRtGE2pdGTyq1dwgW5u5AS3mOjTE16DF3ICI2SLfU1M4HA9+BmGi/ynNUkuRpLl48i81v+N9M1Dnnig5ioeijXS5fCjzTJA0SQORCEzWgpiVSA1F/9cKlxsLyqeizF09pGP3xd39WwbzKgQSmt9GMXlWFEUpFCjwcoFRFhmN0nwGKopSOOgS2yCZO3cuLr30UhEU6SdbzNx4443o6trRb5QCMb/LNWcwBduXXnoJTzzxhGjGP/zhDyMYDPbsc8UVV+Bvf/sbHn74Ydl/8+bNOOWUUzBWoSkuTb2omStVcy9/t0+vz/D35CiOODF02e3wuMrgE8FzcMMRIxbTdNq2o90m5NXwuBJ++dnQFm/B+vAWRO3tfTSBgQpXDcpdAWAQkadt20yYdA/Dgkk+ze0LRThXFGWsQnPnTskrXarPQEVRCgf1+c0BRne+5JJLROubKsxR+DXN4g0cQ//ldAL822+/jfr6+pyO9dhjj/V6/8ADD4gGmHV2+OGHo729Hb/4xS8ktRLzCCc16gsXLhSB+aCDDsJYI2mKazlmUaYsGhgDdZ5EP6rtNgF2u8vQ7HSgNbYaZb6JEhm7Jfw+bMcevDmv1SERjWv88xCzOyQS9kATKWqCVwYXoyO+Gm2x3rmiqZ2e4p2DSa56rLZzTwnE1E3JchUi1GrXBuYiYrWLWb9OOhVFGa2YG53RdTIeczFYURRlOFHhNwcY+Zjpf0qFuro6EXq57bTTTr0EYAa8ojb4wgsvHNI5KOySpBBNIZja4KOPPrpnH+ZLnjlzJl588cW0wi+DkXBLvi41aHIdijf2vCtFKrs1seWiZTTE/LlLoj1vRaVvGryuchiR5cAg8s4yEFXQ3ip1R41vuXciDIu+6kYW9elga3Qjtkb5qve+vB9qPRNR66oYVFTk1HIVIkz7VO6dNIzxyEubUh+XFGWkoNDLYHzJd4qiKMOJCr85wCBQu+66q/i10qe12KHPMrW+5557rpg3M4hXahCs2bNn4+CDDx708Rk98/LLL8chhxyC3XbbTT5jOiUem7mFU5k0aZJ8lw7Wd3bm1wY8roCYc8bFr3HwD1GX4UWlqxoxJ4aIpBJKHstAhbsGbrgRdeKwEB9yrlSvUYZydwVCVhBxZ6iRePNXB/lk/boNeOv6J7Fu3YbEVMeJi5mb5STqz3ASFgiDx+mZRMXtIMwcIjNn1rY7CFsd6DTckh5oKOUqSBym5AoWrFl2oZP9uKQUKmvXrsOnPnV2L0suZbgwUO6uRrnLi5BtwXIsed7xWVDwY6WiKCWFCr85QG0lNaLHH388SoFzzjlHzLWp4aIJMgNe5RP6/i5evBjPPffckI5z7bXX4sorr+zRsNCMOj2GREz2GH60RpanPFRzJ+CuxaLyA7A13oB14dd7BCT6qs4K7IlqdxU2mE3oshvRLiajg9efVXunYmHZrlgWfh+NsRUYCkYe6yB/ONgQXIG/r23C5mBidT9mtYtJcNzsQNAx4YInLzlneb00d04IdEObTNH8bkPkXTS6/GLCXGpYTgzt0dWIa+qjYR6XlEKFz/OOjsJ0Syg16EYyK7AHdqmsx7KuINqcTrTEVyPSY/WkKIoyMqjwmwN33XWXaDD32GMP8VFNpgGi8Pib3/wGxYjH48FFF12E999/P6/HpW800yY9++yzmD59es/nkydPRiwWQ1tbWy/tL6M987t0MA8jt4HMCyn4Bbzj4XNVoC26alAmtEm8rgrMDCxCDH6sC7+Rcg4XJvnnYYJ7PDpca2GaNjqwZkhiVoV7POaULcLGeNOQhV+WsMw7Xso/1DrIJ556Cx/+3Mew/t77gK300w2hywrLogKjDycMlIe+8k8BOhTf2n2sIQq/sNEQWyP9yi5B42DWVTC2JS91NRbJdlxSChc+cz73uXNx7733ZbQ8UvID50mT/HOwW+V0tMZa4ThN6LC3ASr8Kooywmi05xz46le/KkIbtZmMVMw0R8mtmDnggAPy5stM01UKvo8++iieeuopzJkzp9f3++67L7xeL5588smez5gKiTmTh2JiTYGXAXyYr5fpawZrQktzZ0YfpjBAc+dUM2Sv4cfkskrUeN1iDm06YdGeDRUeI2R15eVYcrwh1sFwUF5RhgMO3F/+JqDIRYEyIXjlM9DX9uPm51g2aPJcOHWZT/JZV0rxQ1/8xPjn6tHWMRo4/cNHGy48smwco/NFRUU5DjzwAPlbSCSfQ7nmvB2OOsonUTuETqsLMSeEuB2RQFeKoigjzeg/0YoICrzl5eU45phjdvBZLWa+8IUv4KqrrsLGjRtFOK2o6J0mhpruXEydGcn5L3/5i/hFJ1fT6U9cVlYmf8877zwxF2QQrOrqanzxi18UwXcokZ4n+HdFu71VNFk0TXYwOBNa5hms8ExC2GzG+8GX0Wa1dwsIQL1vKj41ZyYq4cZfN65GU2QpInbHkAW3DnMz3gu+hPb4JgwVljUY2yyTn8HWgaIoYxED5b5J8Luq0BpZBduJwuupQrV3BjrjGxE1W0e1dG5XALX+uQhZTQjFSltLG/BOQJm7Xqx3csmBznzkrKOg2YBwfBsKCQq668OLYTnrsDkSQRARxMxEQExFUZSRRIXfHHP7UkvKVD2lxBlnnCF/mbc41UQpmQKJflG5mIaTI444otfnTGf0mc98Rl7/6Ec/gsvlwqmnnirmgsceeyzuvPPOIV1DnW8+ImYULcH3ujWJg1tR9nqqUeWfgS5zG1ZH3u1Ou5AQbmt8E3D8tAVoCLrw87Ub0RZdkxfNWZfZiJVWiwQAGToOwnGm7DEGXQeKooxNAp5xqPBMRHtsPWwrKtHTq/0zEbZbR134dbn8MjabURMhbCthiwVDFmGrvNPREd+Qo/CbqKM4ooUn/MLG1uhKNMQM2E5iyThhVaMoijKyqPCbA2eddRZuvvlm7L333li0aJH4yyZhDttiZc0aBmzKD9mY2gYCAdxxxx2y5QsbcRFUEwLfECZFPIbk22U0yt7+soz4G7Ji6DIZpTIKO0+aVU4KzDzmNkxqqkcbmkxSC9876JYbbsMP2zFR5vYh4DLQaZqIp5h8U2tNP9tE/ZfqBFdRCg9q53hvbr/vGJndLBAXiu6yjAVTfYfuFiYbJLefddfRUAIwDicWTFgl3nSKohQ+KvzmwFe+8hXRhF522WW9PudnjJpcrMyaNQvFTsIEmRrPoRE129CGNbCsHVfbW2Nb8dBaG10xF4LxZE5CJRM+T42YkNNksrm5WYLKdLVbqPHPRWd8M/aono+9an3425YGbIqs6v6VCxW+KSIAd0TX5SUCtKIo2RGON0owOttOLFjFrc5ERHCrc9SrkHmz26NrZIzOF8lxiX8LBwcRswmOHRfT88HV0ehq6RVFUQoZwymMJd2igKa6/eW0LWZWrVoleX+TUZ+p2aaQP2/ePBQSNJOm5jhJJBKRiKsMykLN7FBNfRkwxJC8rqnajwRMx1Phccuaetg01WRrACoDszDBvwhbQ2/0mOBVB+aj3j8fDeF3cObko/CJGWW4ZvEyvNX+bHf9uzGxcl8J2rKl86WcJ3/K0EguNmS6z5T0ZKqvQggUlQu8/wzD1TP+JcdDaoRH36LESMRz6LHwKV36tsNYryMdlxRFySca7TkHKOBm2oqZxx9/XITdV155RYJbcXv55Zex66674oknnkAxwEjJ+XjYS3TfDOa2NEPrNKMImjR5Lp2JRX4wus2V3b0+S8Igaocc8gGUVyajPSf32L5P78+LD7fhlYjguUZoHR4MeA2alQfg6tUmipIZjqGp419yPBx9wbe7NFKW/I29yXGpb5DHQmuH0awjRVGUUqMQZmlKAZhzX3HFFSLw3nrrrbLx9eWXX45rrrlmtIunFAHUNlT5ZqDMO6FHdI2bHeiIrZU8vpMnT8JXvnI16sYlzJlNK4j3Opfi0c2L0Rjd1MtnLRTfhq7YpiKbwBmY4JuLBeV7w++uLoj2mF+xCAfV7o1yz7jRLo6iFCTJcYl/FUVRlLGBCr8D4Ha7JQ1Q8nW6LTXwVTFCU2emH+rLueeeiyVLloxKmZTiyw9a7Z+NMt/Ens+YxqItsloE3e2fJX0Iu/BOx3v4/ca30BDdkHKkRKqmzuj6osoBSQ32RN987FK+P/yumtEujpjb7ly5Ow6tPwCVnvGjXRxFURRFUZSCQIXfAaBLdNItOvm671bsZs8TJkzAW2+9tcPn/GzixO3CjDKaopW7sG9XI6FtFF+17vJKwikn2sdk0ur+zELYiqI1HukV6ZnQbI9m7Nmb/PFsHtmSdTUapsduwyOmxgyAVwh4DS/8LvqdqtmzoiiKoigKKW6V5QilAaqurs57SqBC4vzzz8cFF1yA1atX4wMf+IB89vzzz+N73/serrzyytEu3piHuRsrvJMRsdoQy2Ok03xi2ya6YhsRtbskH2eFd1K/5aVwWu6bLK9Dsa1D8imkr/E0/04iKm+JrUaZZzxMJyKRa0cqJQoF/ebYOqx2wohZXSNyzn7L41hYE1qOuL0ZIbNltIujKIqiKIpSEKjwm2UaoHg8jlNOOQVHHHEEbrnlFpQS119/PaqqquS6rr32Wvls6tSp+MY3voFLL710tIs35nG7ylDrn4e22JrCFX6dmKTYoNDlcVfuUN5oNCYRxflXMNyo8s3s9vFtkLyWQ1kcmFO2t1hhNFoNqPHPQdhqRVjSUY1UMHsHW6PL0Rxfg0iKmfdoQe35+11vY1XIhS5zx7RdiqKkGZcURVGUkkeF3yzxer3YvHkzOjtHP99hvqGZJgNecUteH4VhpTBgygum/instCkOTDshZHnSlHfjxo244vIv97ynYbDb5RPhl4bKQxFR+fuAqwL0TmBdURh2OV6MNDEnhFiPm3LC+JvXlx8BPHm8bBcJHAStTgSLx21aybmNlaHCcenyyxMxPfKBpIbqtgRJ3PuKoihKoVHAToSFB7Wgf//737FixQqUEuFwGKFQqEfobWlpkZy///73v0e7aIqYFMck+nHcKo6FF9uO7lBeTgon+eZiun9nuA2/TPBD8UYxTWZOyiGdz4ljS3QltsRWwrIjCMa3IiqmvqM3+aTwX+mbKvmn84HPU4MK3xQJLKaUJj53FSp8U+EyNJ9yMUL3iymBOdi9aldUeTR6tKIoSqGiwm8O/PznP0dDQwMWLlyIadOmYe7cubLNmzcPxcyJJ56IBx98UF63tbXhgAMOEBNofn7XXXeNdvHGPKYVQltkJSLx5qIt79x583HXQ9/DoQuPEcGQAm9ndF0iqvMQNV2WHcXq8BtYE35doki3R1YjGNs6usIvTb8DC0RoHTqGpJCq9c8XrbZSmvi941AXmA+Pu3cubGX4mDt3Dh555GH5mw/hd175nvhg/SGo980u0mzliqIopY8Kvzmwbt06iezMbcuWLVi7dm3PVsy88cYbOOyww+T1H//4R0yePFmulQLx7bffPtrFG/MwMnLM7hS/2mItrwsueLxuVLiruyMx00w6JNtQhVQKz11WC7qsVvE5jttdsLpNsHOjv8lqbhNZRr72uapkQpyPY1Jb7nVXauTmEkba2FUIbTx2hDa6/NClKS8R2g0XKjw1GO8bB3+eLD4URVGU/FPIToQFx9NPP41ShCbPSR9fmjozsJfL5cJBBx0kQrCiDJWkdrcpvknMlIcD+hiXecbBtCOIWe25/RZuBDzjYMNE1GztJZD73LXwuPwIm81wHDOr4yXNrxPCfbrzueR8rJdIFibaMasTofg22Pbw1J0y+sR72nj0FrkG09eVBFx4a4iux9KuLnSZDLanKIqiFCIq/ObABz/4Qfm7bds2bNiwATNmzMCkScXv2zN//nz8+c9/xsknn4zHH39cAl8Rmngn0zwpylC1wWRt5N2ewFj5xm34JMp0yGpCLNyRk0aZgnNNYDbidlgiVG8PVmNISqYydz2iViesLAUCml+3RphqKJzxfNWB2bDsGKJyvv4iUzmIxBsQN9tFqFZKk4jZDNMKDtv9MTDs65NQ7h6XU19XEnDRYlXwbWyL+NAcZ7ozDXg1FolEIojFsl/A8vl8CAQCw1omRVF6o2bPOcBAUCeccIKkATrwwAPlL983NxeHL2YmbrjhBnzpS1/C7Nmz5boOPvjgHi3w3nvvPdrFU0qCxESw02oR7epwQHNRv6dmUEGmaPboc1eLaXFfs0+vu1yOy0jS2UKT76jVBtuJZjqjBDiS82VhckmBiMfrX0hWihkubEgbj6LQ6XVXiJ96Ln1dScB7s91swqboZkRsLr4pY1HwnTNnGmpqarLe5syZI7/LhTvuuEPmaxSaOWd75ZVXsvrdQw89JM+6k046qdfnTBPIeeCUKVNQVlaGo48+uuQCuypKKobDXq9kxSc+8Qnxie1VgYaBU089FX/4wx+Kuha3bt0qfsx77rmnmDwTDqjU/O6yyy4oFKLRaK9VUj40/H5/1mmAaG7q99TCAx9s2IjaHQWjTWOUV7+7GrFB+6wWLlzdnjx5ErZu3ZbTqnguuF3lmFC+G8JmC9ojq3LSvDCK8vjy3cRkujW8NCUIl4GawHyUuevQGH43b+1CX+Bx5bvBcmJoDS0tGKE2KXhlus+U4RmXCoPh6ev5znle5qqRcTziMJq8Abfh7WM9wTG+hp0ZUXF/cHp+63NVImp19CxKJcalyWhtDCMWp8sD9y/NVFOMQVDvGYcOqwMhi64WueF3VaPOU492q13yqGfC46qA11UmdZ8vF5diGZc6OjpEoF275iFUVw+8CNvREcLsOWegvb09ayu73//+9zj77LNx9913i+DLzBwPP/wwli1bhokTJ2b8HWPTHHrooRKktb6+Xqz9knzve9/DTTfdhF/+8pcijF9//fV49913sWTJEtVKKyWJCr85UFtbK76xHGgWLVqE9957D6eddhq6urokSrJS+JPMpJBTYdQjjhiaou8jEm9EIeD3jMO4wM5oi61BKLZltItTdNBvlwsblhMVs+PcfusSjRf99uhfmyo4e91V3RPs9jwKqS743TUiZMes3Ey0h5NimWQWGqUh/Cb7ui8LU/zRodw3FZN9i+CBF9usVWKjEXDXojG0XVhnnXOMZ0T55tB7PddR7puCWt8cNEeWdqdC2z5u1JcvkiB1zaHFwxaTYLQZ51+AvcsPwLLIUmwIv5HzmDPBvwv2LN8XS8LvYXPk7Yy/r/LPQqV3KprCi/OWnq/YhN+Wxj+juroii/2DqJ9wUk7CLwXe/fffHz/96U/lPQOw0gXvi1/8Ir7yla+k/Y1lWTj88MNx7rnn4n//+5/MV5PCL/VftGK86qqrxAKQsDx06XvggQdwxhln5FADilIcqG1TDtTV1eG4446TQFAcqGgezPfjxo1DsXHhhRdi48aNWa80/uY3v0FJYFArUIcKzySUeycUVOoYl8uHMs94uF2l5/8zYcIEfPGLF8vf4YKTXPpN5ir4Jn5rS6CrdIIoJ3AMSpVfYcBG1OL5tmumFGW0SfT15oIUfInHFUClZzKqPFPgc1eKsM7AcakRssW6x12bSDOWYr7NcZXjqytlzOd4dMkXL8KUyTPkuVDKUyJqvSf5ZopmfzD43VWY5JuFMndtP3sZ8LgrJPCg0W+k+xLHtrPfuoXm1I1CfjpoNfX666+LWXISWurx/YsvvpixON/85jdFK3zeeeft8N2aNWvE8i/1mBTgKWT3d0xFKWZKd6QfBr71rW+JH+w777yDeDyOt956C//5z3/EZKTY4EN/1113xf/93/9JLt9XX30VmzZtEv/llStX4q9//SuuvvpqzJw5Ez/60Y+w++67o1hJTIZqEHDXi5aAeWEZFIkCzWhGVu0LNQ4Rq1XKV2pUV1fhwx8+Rv4qiqIMBmp1LZhithu3grKJltrZbqrMYHVcxEpoHbcvLHFc5fjqOHERmunjX11dgw9/+GjUVFag2qiEx+XbbgnirkG5e7w8N2gxVOzE7CCa4psRyTESfs/vrSCa45sG8Gd2tscnGMsB0+hNmO0GiOY21Q+YJsjpaGpqEi1u30CrfE8BNh3PPfccfvGLX+Cee+5J+33yd7kcU1GKneKyyRpl6GdBH9++QaBOP/30ntf83jTNohDkL7nkEtx777248847xbcjFZp3cyXw5z//OT7ykY+gmJHIuv658BnlEsuI/kqd8bWwmY82T2ZZ+cC0utAWWdGtfVQURVG2Q/9eP6IIoyW2FCGTE3MDMaOlV05xui50RNcnxGBnuwabUdzbnBUwrRCq/bPE795AIt5DAAHM8IzDBleNPBMk+rt/DsqNWsQRRXN0WS9T6WKkK74Ni4MvoM1sG5S1SYe5ufv3/admi8QpoIUK0md8xLCdHq3ugPsBkj0k1ew5X6bcnZ2dOOuss0TwHT9+fF6OqSilgAq/OTJQfLBiih/Glb3rrrtOttbWVqxfvx7hcFgGyXnz5okgnyvUInNjcAVC7TKjCNI8POmjQ98SRh2kac+xxx4rwvdwpoyiSVyZd7wESqHA2xHejM74poIzN5XcsLautCqKoqSDfrkmYug0N/f4k+5ou8Pc2Tvm2WXObW4UbP2eeslnDGyW77zwot5dC293pPjEM2MCqoyJiCKEtnjx57uP2u3YFB18fIGI1YaNWbhpsF3y5etbtKSYNA+4n1hGVWfl88u5mdvtlnSbqfA9A7f1ZdWqVTIXY1aS7adMnNPj8UiQrOTveAxGe0495l577TXwNShKEaLCbw7QN6KU/Zm5DZXp06fj5ptvxoIFC2QhgNEDTzzxRLz55psiCDOH8D/+8Q8JGkbzHmqfTznlFDz//PMYPmwxj3M5hviyMbdq/gVfQyZOPConWIUmWA8G+si54UXcDhWsD6CiKGMHLhBSsErV6OaMQ9PckJjl+rvH6RitbkxDguXJLvLM6ELU8CNmhEsoCNZQn0vF/1wrROE3WxidfN9998WTTz7Zk66Iwizfcy7VF2bqYNTmVL72ta+JRvjHP/6xmFt7vV4RgHmMpLBLv+OXX34ZF110UU7lU5RiQYXfHJg1a9bwtUSJkLrCSL7zne+IJvill14SwZi+J7/97W9x1FFHyff3338/Fi5cKN8zkNhwYNsmOqKrJWKv+IOJ2Vd+oaaAUS45aWoLrygBYdFAmXciyt3j0BxZBksE+sHT2tqGhx/+o/xVFEUZDKH4VsTMdkkRNlg4RndFN0ou43irhYcf/hPWNC2GHYSkSZN95JmxBiFsgW2Ygwqip4xhhkn4JVdeeSXOOecc7LfffjjggAMk1VEwGMRnP/vZHve8adOmid8wI2PvtttuO2QtIamfX3755fj2t78tSotkqiNGgO6bD1hRSgUVfgeAg8KXv/xlGUwywajJP/zhD2UQUrbDwAzU8HJgZmRsRilkoLDUqIJcmWRQLUYVzCT80jw6Gf0wUxTE/qAgGoxvk7QYw7VuzQApFBYdmGiLrCyJBXJGS63wTkZrdPWQRfmWlhY8+OCv81QyRRl9hjouKbniSHCrKAbns5p6nHC3WXSoxcGDD/4KhjwdaBnk9HlmJD8rgQFdGTkYgC0bwTYlUFu2MMZMY2OjuJMxIBW1tY899liP6xjd1xgBOhcY3JTztAsuuEDSIDEfMI+ZmlZKUUoJzfM7ABxE6GPBHGnUVjK/L811aRbCIFE0FXn22WfF9ITCngIxs6GwS//eyspK0fQyqjT/cnWy70SRq5dHHnlkxqjZ3/jGN3DjjTem/a5Q8mkyeMrEin1l0tQQfDPnSJf8PTXTph0tEK2xgdqynVHlnYKtwdeG7MNVVhbAvHnzsWrVSoTDiSAzSuFRLPk0C4FiGJeU/tFxqTgotjy/rasfRHVV+cD7d4ZQN/fsnPL8KooydPTJPAD0T2Xi76effhr//e9/d/iefq3UXt5yyy15aI7SYOedd5Y0UBzQ//jHP4qJzjPPPDPo41177bVi6pN8+DFfXaFBH7TO2HrpD6lpN7LF56lFlXcq2qJrEC+IaM8OwvGG7qidQ9dq0YTqppu+jcsvvxKrVq3OSwkVZTQphnFJ6R8dl5RiM3tWFGXoqPA7AIxSzO2JJ57Ao48+ijfeeEMiI9NvYp999hGfCEYsLmYY4ZlCW3l5YqVy3bp1cq3Ucn/4wx/O+XgMyjB//nx5zeAMzCHM4Ao012GSdprVJP1O+otUmIQru8nV3UI1L6QfWSiWjNSc+wONeSerfNPRGd9SIMJvIjVIDO1ybYqiFN+4pCjKaKU6ysJUPpt9Spg//elPMr9Ozj0VZaRQ4TdLjjnmGNlKEUZjZsTlCy+8UATTAw88UCIAMqH6rbfeOuSIfzQJ5+SQgjCPS1PxU089Vb5jqH36qNBMutgZmpBod0cUdYr2elyGTzznrAK7DkVRFEUZMVTzmxWnnXYaqqqqRDFy3nnnydxTUUaC3LzilZKE2uzDDjtMXtNMmYETqP198MEHcfvtt+dsCkgfaOaWo+8v39Nc/FOf+pT4wnCAo6kgzcgZAIs+wBR8hyvSc7HAQC5tkdUwrSCKEQNuVPqmo9I/Q4J/KYqiKMqYFn6z2cY4dCt87bXXZB7ICNQMHNvc3DzaxVJKHJ2lKgiFQrL6Rv7973+LFpiBviiQUgjOhYaGBgm1T7/fD33oQ2Ly/Pjjj/dozX/0ox/h+OOPF80vg4jR3PmRRx4Z860QNzvQEVsL0w4XZ10YBip8k1HhnSIpRPpimpZYEvCvoihKIaDjkjIsOE4ikvOAm1pIff7znxcFDOeKnBMyiCCzq3ziE58Qd0NFGQ402rOCPfbYA5/73Odw8skny8obQ9wnUxN99KMflXD6hUKmaI8aVXV0Yf1PrtxftL5bu17pNuFWChm2FduNkVSTJu7FElW10MjHuETrCS4c2dIGOilWlCRFF+353btRXVU28P6dYdTtfuGYjfZMJQvnl6nBAtmmTJF53333iRUhU2GuWbNmVMuplB6q+VUkXxxNT2bPni0+F0n/W2qB9957b60hZWAcG12xLeiKbR5UtGtl5GGQtRr/bHjclVr9BYDfUyvt4XZpbk1FKYmAV9lsYxjDYJSQ3nBx46yzzhLXOMaE+eQnPzkqZVNKGxV+FXz84x+XoFP0u6DWNwnNlmmmrCgDQc1hMLYJXbGNaQNlzZo1C/fff6/8VQoDr6cSNf658KrwWxD4vfWo9s+B2zWwxkjJDzouKcOC+vxmBbOM9AezhnznO9/JT5soSgoa7XmIfO973xOf1qeeegrFDH1v+6YbOuCAA1Ds0JSQOFBf0+GmP1Nnj8eN8ePHy1+lUHDBZbA9dlx9V0YDQ9pDW2PgekoE1UtOnLfXGBfe+C37dmLM739yXajjUuL6jO6FxOHWDrLG3FnV18iVfXsbF2WqPSfLYFZj3EqK5swTJkwY7WIoYxAVfofI0qVL8cwzz6DYYFCrbCnWgFT0t6v0ToXlxBCKb1M/OkVJwbS60BFdD9MuzgjjpUbMbEcHNsCyNWdwf7gMLyp9UxG3Q/Le6yqH6UTgNcrQFd8Mj6scAXctuuJbYNkRFCN+Tx187mpxI7Gd4e0PtPwo90xA0GyQMWGo+Dy1CLhrhlT/yTaOWV2ImE0oNgzbli2b/cYyagmmjBYq/GbBueeem/G75557DsUIgzKkmp48+uij8tl+++0nnzHYFXP+5iIkFxouw4Nq/yzE7C6E4w2yhqwoSoKY2YE2eyUsO6ZVUgBEzRbErQ4VfgfA5fKixj8HQbOJQeZR7h6PsN2Gclc9QlYL/J561PrmIGy1FanwayDgnYBq7zSEzWbY1vAKvz5PNWoD8xALh/Ii/Aa841DjnYWw1Tro+ne7/OKS0WVuQcRsLtJoz1nMNzTacw/PP/+8zD8ZwCz1taIMByr8ZsEDDzyQ1jE/KThm+q6Quf/++3teX3PNNRJW/u6774bbnTD/siwLX/jCF4o8AqEhD1GXrJyPfhvRjCshgKsQrow+NHMs2tRaJeo2oFHSszGH9STGdcMj0bEZIMzl+OFxlckYS60htcH8vlhh2d0un6SQG25o8uw2/HnKz07TfS88ErTNNeRnN9u6KMk2h+8Y1/ymctxxx+Gtt97C3Llze71WlOGgSEeWkYUCIc0z9t9//x2+e/nll3POhVtoMKQ8NdhJwZfw9ZVXXokPfOAD+MEPfoBixHEsBGNbEHcio671dRk+VHgnImYHETVbMdbYvHkzrr32a/JXURRlsCa6Fd5JiJitMo5Sy9gXF1yod9Wg3T0ekTi1hnbRjUtRsw1dcMO2hz9lXNwKoiu2qaAWwrgIxACKMasNRYkKv0MKfjVQICxFGSoq/GbBrrvuirq6Ovzud7/b4bvPfOYz+NWvfoVixjRN8V3eeeede33Oz+wiXpnkA7Q9uqY7YMboXoeYcQXmoSu+VSY2Y037Gw5HsHjx4tEuhqIoRQz9Sat9M9ESXSGuLAFvfVrhd7JrPNq809AUXdFv6rXCHJccROKNiJmtsEfAbJum9q2RFeI3XSjYdhRtkVXFm/M62zRGYzzVkaKMFir8ZunzyzRA6TjssMOK0uw5lc9+9rM477zzsGrVqp4Iz9Ro33zzzfJd8eLA7A6KMvq44HVVwEVTtjFIfX09jj/+//D3v/8TLS0tKFyS97JOSpRCx8iin6buU2h9O5vy98Zt+MSk2XKi4k+6XUO03Z2EhtE+mt4aNL01Mpwz8Xl9fV33uPQvtLa09LEQSvdcH5m6k+sb5kBXw2dun2yLwdcVF6zjxRyITzW/ilLQqPCbBZdeemnG7yg0citmfvjDH0qao1tuuQVbtmyRz6ZMmYIvf/nLuOqqq0a7eCWBQzOu+FaJ6DoWqaurxWmnfRzPP/9CAQu/BvyeWvF/C5uNYjavKIUI/VzL3PWIWO0Zo3XT2iTgHoeY3QHTCiHgHS+pVRhEabQF4GzKn96f1COCUUIjCDGLDTlA1OmCYZsixEWtdmyKLkdnfEuvVDLMn1zmrkPE7hDBmP7CdXXVMi69/dIKVAbbsTW+CTG7U/xgy7zj4YWfZ5PfW4gjHG/StHn94iBmdiJobB3b/uui+c3G57dQFqIUZWyhwq8Cl8uFq6++WraOjg6pkeIOdFV4MH1JW2QlLJkQ6AOvMDFQ4ZsCv6sGUUaKVeFXKVA87krUBhagNboSZiy98OhxVaAusABtsdUy/jDyPYXGiNU66gs73u7yt/RT/kwCvQi/4gvrIBTbhojRDNuxEDYaYFlhhOxGvG+/iAg1wynuLslztsZWocwzXrTIBhKLkfXemZhaUYn2zicTwq/hkYjSFUY9TCSioUedoAjWlq2LYv3B1ERxqxNWAfkQjzga7VlRChoVfpVeqNA7fJF1Y1ZiYUEpTGjkyByhzK+Zn8inijI8UAPqd9fA5cqcCsTotY8Br6sSNqgxHX03HcPwStkkonEuv4NbTJOTQm06txbTCaPNDKcNOpg4p19cUDxSL4kx2e+uQp13omikE+VzweuuQsCoQ9xImB87tqsg6q7QoTm6hcLxHx4V1OxZUQoaFX4V4Y9//CP+8Ic/YP369YjFeuf9fOONN7SWlJKHU2pZoHD4aujaHWrefK4KRKw22I7m0lXyGxAoZDbA6iemAfsc96HJM3t0xKRPq1UQuUWT5c8lwjCF0UpXLbzwwmVsz0zQF5+rCvWe8eiyg+gyG2UhK+CpQ7m7XvyBXfCizl2HuGP1+PgyP7BjuWTxyzDcohlnflmXYcFEwnw35gT7DZ6lKD3wHsvGpLkA7kVFGYuoekPB7bffLoGtJk2ahDfffFOCXo0bNw6rV6+WfGuKMlQ6Ojrx738/IX8LGabGao+szkuKEb+3XswsPe7yvJRNUVLT0zBCbyJyfHrMnn0SZs6d0bXojK7vZQo8WsStLilbrJ/y98ZAmXcCxrmmw48yySWbiXLvBCws/wAm+OeLBtdweVATmIta76xElmBXADM801BlVKCzs0PGpXXNS7A6thp+b51oiB3HRHtkDRoii9EceV82vmfsBkXJWvObzaYIP/vZz2QO2ve1ogwHKvwquPPOO/Hzn/8cP/nJT+Dz+cT394knnpBAX+3tYzNAk5JfGhsb8ZOf3CF/CxdHfNWi9InMg4DASXaZp77fibqiDAZqdamZpIlp5n3i3ftQu2qLv2rC9cIpivL3xesuR7lRBQ+8op3NuJ+rAuO901HuZg5gl5hK+921CLhqE0Gz4EGNqxo++NDQ2CTj0rqtK9Bmt4nZs2h+pb5aRTsdNptky9e4oIwBhln4veOOOzB79mwEAgEceOCBeOWVVzLu+8gjj2C//fZDbW0tKioqsNdee+2QnpMpO5m1JHX7yEc+gpHkk5/8pJSv72tFGQ5U+M2Rf/3rX/joRz8qOXE3bdqEb37zm/0OPMUATZ0/8IEPyOuysjJ0dia0c2eddVba3MaKkitcVJk5c4b8HSuYdkRMTcd01FOlqKBfsN9dJwJjoRG3QugwtyBoNsC2M7sRxO0QmuKbxJQ54cxgSQA7EWStZsTtLvm+02yEz+vpGZeojU5qyRUlL3l+s9ly5Pe//z2uvPJKfP3rXxeXtD333BPHHnssGhoaMqYZvO666/Diiy/inXfeESs/bo8//niv/SjsMttHchvJud9RRx2FJ598Ul7bqg1XRgAVfnPg0UcfxfHHHy8C8MqVK8UsgytwTBVUzDDNUTL9zMyZM/HSSy/J6zVraOY1+loCpfiZMWM67rjjJ/J3rBCNt4hpJ30uFaXwMVDum4TawLwCzEfuIBxvwPrI69gWWSxm35kIxRvwfugFNEZXiCDr2DRhXo3GyGK0hJeJa8OS4EvYFHkP02dM7R6XpiEU24q2yCpdrFLy0F3t7LccufXWW3H++eeLALto0SLcfffdKC8vx3333Zd2/yOOOAInn3wyFi5ciHnz5uGyyy7DHnvsgeeee67Xfn6/X+aCya2urg4jxX//+1/ce++92HvvvUUBw41C/W9/+9sRK4MytlDhNwe++93vyoBwyCGHyHuPxyOvX375ZRQzXHX761//Kq85oF5xxRU45phjcPrpp8ugqShK7jB/KbVNGuxKKRa87mrxre3PrHi0oEtCq7keHeZm2E4iAnM6mKpoa2w1usyGHs0vzZY7zc0ImdsQs9rl+zZzcy8TcJqEJ/J7J3IIK8pIaX6ZYjJ1i0bT928GI3399ddx9NFH90pVyffU7A4ElRnUsC5btgyHH374DgLoxIkTxarxoosuQnMz84GPHP/4xz9Eg83gq9Q6cw56wQUX4Mc//vGIlkMZG2i05xxYunSp+CJwle2FF16Qz6j9LWw/xoGhv2/S1OTiiy+WYFe8vo997GP4/Oc/P9rFUxRFUUYA+gdTCNSoxqWVFouR5yUFUT+LBsropTqaMWNGr49p0vyNb3xjh92bmppgWdYOwaD4nvPTTDB2y7Rp00SodrvdEueFwmWqyfMpp5yCOXPmYNWqVfjqV78qwU4pUHP/kYBBrs4888ye9yeddBJ23313qQdqqxUln6jwmwMUCrliRtMMEo/HRUikiUgxw5VDbknOOOMM2RRFUZSxQyi+DTGzXa0VSgiPuwJ1/gXoiG1AOL5ttIszNsjWn7d7nw0bNqC6urqXCXI+qaqqwltvvYWuri7R/NJneO7cuWISTVLnexQ4aRZNE2lqgz/0oQ9huKGAzSwjfWEsGvofK0q+UbPnHM2D//e//+GXv/ylvF+wYAEWL16MI488EsUOr+vTn/40Dj74YAnkRRgRsK9fSC7cfPPNEjXw8ssv7/ksEon0aJcrKytx6qmnYts2fSCXOjS34mKR+pArSqHiSOqkYHzrmDH9HQvjktsIoNI7BV535WgXZYwJv9lEe070Owq+qVsm4Xf8+PEiKPadM/F9f0oYKjfmz58vkZ6vuuoqfPzjH8dNN92UcX8KxjwXY9uMBIw1w6jU6YJ77bTTTiNSBmVsocJvjj6/NE9pa2uThyWjJE+dOlUiPhczf/rTn8TXgkEGmOc36W9CUxle82B49dVXxYyFK4ip0J/4b3/7Gx5++GE888wz2Lx5s5jbKKXN6tVrcMopp8lfRVF2hPllmaYnGWmZf/men/eFCXv4ndvIr4Yo6QMbcFWh0l2XpzRdLnhc5SKElfK4xDYpc1Wjwl0Lwxh9o7pk/4HhkgjYGnW++KM9Myr5vvvu2xMZWU5l2/Keiousi2fbGf2KycaNG8Xnd8qUKRgJLrnkElxzzTWSSeU73/mOzKk5J+VfvleUfKPCbw5wZW3JkiV48MEH8e1vfxsPPPAA3n//ffGlKGZ4LYwYeM8998Dr3T7ZYTAvhtLPFZrWfOpTn5LjpUYMpDD9i1/8QqIVUovOQfz+++8X0/FkhGlFUZSxSMA7DrWB+XC7y+S9x10m7wPe+h32ZTTmmsBclPno+2fk3Ud0SmARFpQdAL+7Jg/H86LGPxflvsl5L2shwTaZHtgD88r2k5RRhWDuXBtYALfhk6jz1OorI0W2kZ5zj/ZMk2XOrWiByPkng1MFg0EJVkrOPvtsXHvttT37U8P7xBNPYPXq1bL/LbfcIlZ9tPRLzte+/OUvyxxs7dq1IkifeOKJoimmADoSUCnC1EtUwFAxQgUJBW8G9zrhhBNGpAzK2GL0lyeLCApsp512mgw2Sf785z/j2WefFYGuWEkX+Y/U1NSIljtXaNbMFTxGIKRgnYQDGU3MUiMV7rLLLmLywsAKBx10UNrjcYUyuUrZ32qlUrhMnz4dX/rSFfjhD38kq8qKUuzke1zyuatR6ZuOjthGmOiCy+VHlW86Yk4YwNZe+xqGF5XeqaLV64puGPK5ex/bg3G+2ZjqmYJV8WUSKXkouAw3Kn1TJO9uZ3R9rwjLpTQusd4m+uei2lWNlbH3ELNGV9h0uwLSf6KRTnRG10jca6UwfX5zgVk4GGT1hhtuwNatW8WU+bHHHusJgkWLxNQYLhSMv/CFL0j/pnDJOdevf/1rOQ6hGTXz/1KY5nyP1owf/vCH8a1vfSvvvsf9wQBcqUG4FGU4UeE3B+j8z4Emlaeffho//elPi1r4pUabvh2zZ8/u9Tn9fen7kQsPPfSQaItp9twXDtQ026mtre31OQdtfpcJrlzeeOONOZVDKSz8fp8E0OBfRSkF8j0u2Y4F247CSWqDHAeWHZNctWn2huXEYA/ZN9cQ02mec3tKLgemHUXUTuSn9hhlci6mDBoMnN5bTjwPZS3wcYn+w3YEMXiHJVo2Nbg01ktEbB5YaGKbcl8KvWxjC/RtLrw2KEmGUfhNmglzyzRPTYUKiFQlRF8oEFPrqihjCRV+syDVp5emIcn39JtgftxAoPB8mXKBCdMZSp5J0hmgin641MR+6UtfwvXXX5/1cRixkMehiU0+64QmPDT1SWpYmItOURRlNMn3uBQ1W9Dm2JJuiJh2GG3RVYharTvsa9txtEfXIG4Hh2ySXO2fjbgT6tYgOyKkbo2+j874eorEqA3MQ0d8E2LmjuXIBseJoyO6BqYdKSitb77h4sGmyHvwGT7Era48H92Fct9UeAyftHs2/ruWFUJbZBVsxKQNu8ytiMSHpsVXhifVkaIoI4sKv1nAPGMUCrm9/PLLsiVh4Kt0JsPFxFe+8hUR5BnSPhQKyfXQ3IXC7xe/+MWsj0Oz5oaGBuyzzz49nzEnHc3CqR3n6iKTtNO0JlX7O1CkQpYlaX6jZs+KohQC+R6XYmYb4mYH9XXynnlZaSbcowlOgcJPZ5RmuvaQBEqa6lb5pyFstaJLjueIdrAhugouwwW/byKq/bMQttsGLfxSmE6UFSUu/HLRYDkMAzDzrGE1YKDcO1ECWHXE1gNZCL9cPGH/8fvGo8o/E1EnpMJviWh+FUUZGir8ZgEDCFDwpU/EwoULe/KR0VeC/qoXXHABihle23XXXSdBD2j+zAAIixYtklRE4XBYzGKygcLzu+++2+szBmGgjwkj+TFSNgNqMaACUxwl/Y3po5JLpEJFUZRihQGlqMlLaO+cbtNjr2hZe5sGU+zNJETRmDUfAhaFXXuHVD88L+flPodlsoacCsgwGMGa50EJI8bjcMEtwmq+fWwTiyC5aAoT/Ydm8wkzbNUyjhgq/CpKQaPCbxYwqjNhJDwGvGLwgFKE/rgUepOaDPoxf//73+/XH7dvIvXddtut12cVFRWS0zf5+XnnnSemgvX19ZLPjpplCr6Zgl0ppcHWrdtw883sS5rTWRnLGCjzToLXVYaO6DoRgJnKqJqaOasToTjvj5GTEKnl7YiuhylBtXY8L813aWZrDsGMl8I+ry9uhxGMbS4o7W++xyWfpxoVnknojG/Mq+kzxdhQbCuihi9n32nTDqE9uhoxsz1v5VEGQM2eFaWgUeF3AKiVpJBGM11qfpOf9YUa4GKDAi5NuumjS8H36quvxkknnSTph6gJpmabIejzyY9+9COJREjNL8/PUPp33nlnXs+hFB6MOPn88y+MdjEUZVRJmK9OQsBdg87YJjFfdbt8qPLPghHf2i38jhxikkwzWhFIdxRKKfR22PQxHbyWmZGpq3wzEbZaEIxtKSjhN9/jktddLT7UjGydX79fB6E4F6GNDAHQMmNZYbTba3P+nTIEaOKQjZlDaZtC5IVzzz0XRx55JM4666zRLopSQqjwOwBz5szB5ZdfLrnR+DqT2bBpFl8URYbK/9nPfiaph5hrl1ptmikzqBe1vnxPAXgo9I08yEBYd9xxh2zK2KG2tgYf/OAH8cwzz6CtTTUQyhiGTqFw9f5I3o9GDlwGuIoPEDE4GQV68AI/A2slzL2HhtFtUpzwi3ZGaVxiayWei26D5uuMg806NOBx+eB1BWAYvds3Hwy8AJEoF9ss1U9c3g+xDdOfje1J424VqtM0lvr85gnmJ37qqadkDv7WW2/l67DKGEeF3wGgr1PS3ymT39NQ/aFGCyYTf/DBB/Gxj30Mixcvxh577CFC/Ntvvy0CvaLkC5q+f+5z50o/U+FXGatQVAjHG2AaXT1pZyR4VWwDopYuCvWPgYB3HHyuCtGab0/NNLLjEoV45mN2wYXJ7nHosDqxJboMLpcPde4pqEZlj3A8ktCCoNI7DVGrA5Eh5mbOJlBapW+a9N1C0+YXBCr85o2kAmXJkiX5O6gy5lHhdwAYBTnd61KASc/33XdfeU2fXEYupZmzCr6KoijDgYNQbFtCe9kt/Fp2FO2RNd0aNBUi+qPMOxEVnokImg2wrfxrM7OBWuwa/2x44MXOvgXYFNuMLdEVcLsCGOeZjmqjujvA10iXK4Aa/1x0xDcgYjYPa1+iBr7aN1NSZLE/q/a3Dwwwls18cRjyQZcqyXg0ipIP8m+boxQNTENEX98kHo9HIjwriqIowwM1lpYTTRFOHFhOJKvcrcWr76Yp7tCFMWpdKXwmzJ9dO2zZmo5v33/HbwY8jpE04/bB5wrA4/J3785SueEaJUswLlq7Xd5BCN5ZXHOac0k7GN6cy5ltidzwZGijItL8ZrMpGWltbRXrREXJN6r5zdLsgiYXn/70pyX41W9/+1uJWMxgGccccwzuvfdeiV5cbNBc+zOf+UxPrspIJIILL7xQIjSn8sgjj4xSCRVFUZRihhpuRnmO2cEhayMjZosIRhXeSYgYiYVbRs42nSg8RgDB+DbYsrCQGYpV5b5J3el/egtXHlcZyj0TJDhX3OpMfz22ia7YZhF019omWqxW0eDZdgyNsVXoNAKIW7zWkcWyY2IOznzRudSz112BMvc4hMwmmNJGA8PgWcH4FvEHz3dKJ+J312KGbxaazGa0xjeg6JDMUtnk+R2JwhQvDC7LODRMN6oo+USF3yy48cYbsXz5cklx1NzcLHl9Q6GQfPeXv/wFU6ZMKcoATuecc06v9xTuFWU4CAZDePnlV+SvoihjS/hluqREtOGhCEoOwrFtMO0wJpTtJj62FGXLPeMQtttR7qpDxGqHbQ0g/BpuiT7NHLih0JZe45LHXYHawHxY0eWZhV8nLqmDKISHYutgMo8utfd2GJsj74ovcNwZeeHXtiNoj6yElWNkbq+7BrWBBYhHIjBjwazbtCPKCNLOsEhwFe5x2LXiYLwXXoLW+EYUHerzmxUdHR39ft/Zmf4eVJShosJvFixduhSHHXaYvH7sscdE8J09e7ak7WGe2n/84x9FKfwypZGijATMFf3tb39XK1tRxhy25JrNBzQXd9kR8a9109wYBryuCsQRhc9V2R1lmWIpTXgpkqYXtqnhpdaS+X1TxyUKxl5X+QCRqbdfT6qhOs8VsUdvsk7T8vgg6pnXSu05NeK5nI2LEMMFTaor3TXwucpQjDi2I1s2+41lmEK0vxgzXFzRGDTKcKDCb5Z+BxMnTpTXzz//vNyMzD124oknijD8wAMPDEvjKEqpwJRZNKenqwB9zRVFUfIB/UJrjXpUuiqxES743TWY5JmKdrsd7fEtO2gmKSSG4g0U3+B2uVBeUd0zLll2BF3xzTBHwWx5tKCpM6+Z114oRO0OrI28j3bJbVyEaJ7frKiqqsJ1112HAw88MO33K1aswOc///n8to2iqPCbHfTnff3119He3o7//Oc/8tmhhx4qf9va2jRIlKIMwOzZs3Dbbbfi8suvxKpVq7W+FEXJC0wrNNE9CVWuMiyBGwFvPXYpOwirYqvQYW7r9u3dDs2vO6PrRFM7a84M3HbbLT3jkml1oTWyAmYBCYLDTczsQKu9Ylg1ubkSMpuxOPgiIlKmItSOqtlzVuyzzz7yl7m2M2mGizWVqFLYqOY3C2jyzJy4yaBWdXV1PcLvm2++KSbQiqIoiqIMJ2lMJA0DAaMcAYkCbUjgqxrPBASsxgwRjB3E7a6e1319WWNW/36IpRh9PDZKaaMyYToRtJlFvAChwm9WfPKTn0Q4nHnRZfLkyfj617+ev3ZRlG5U+M2CH/zgB2J+8dZbb4mW95577pG0QP/73/8kENZVV12VzWEURVEUpQgxxJyYvpiMuDwaeV297mpUGfVw3B5ErQ5EzfZE0Cn40O4pg2NUi0kzo0q32m2I9/LIpZ9vOXzuKkTNtu5UU9vxuasR8IxHVK7NHrT5dcBTDxuWnCNbjSX9l1m3Maszb77RA0H/XpbVcuKIWe0pZe3dzgwq5ndXS327xB+6AhGzVQTmTHjdld37tUhwsOT18RgMCjYmUOE3K84///x+v580aZIKv8qwoMJvFsycORNvvPGG+P7SR4GCLzn44IMlGl0gEBie1lEURVGUUcdAhX+qBJWKhjrg2CMt/Boo807ADM9eCBthbI0vQyi2VfS6FFjdASBsTBfBM2w2Y5O1GRH0EXA9tajzz0NTZAmseO/vyr2TUe13o8lq38FMOusSGh5UB+aI+XDMbM86BRCFxbrATmiNroQZGxnh1+XyoiYwVwTSWLijl/Bb7psKv7sKsVCnLBawbC2R5SLE1vhmoCH0Lux+NMUB7wRUeaeiUfaLy6KFHCO6HOGYCr+9GOMBrxRltCjSDOKjA82dk4Iv4WsG8WEwH0VRFEUpRShkel2Vkn+VGs7RwOsuR7VnEsrd9YjbQQnUxL/URHbZHQg6YRE4KXx22p0w0TvlD6NDB9x1cHXnB07F4y6Hz1MzpCkRI01Tw+l1V2Uwt04Py8NyJaJXjwxsQ7YlBW+jbzu72c6JunAl68zllwjZfnndn87EkP34Gy4GELer+/qMkbu+0YZ+qsmIz/1u6s8qZs/PPfcclixZskM9RiIRPPjgg6PShkppo8KvoijDzpo1a/GJT5wpfxVFKS6on6JZLgXNwWpGRwpqKCl4JUyI+y8rx6PTP/FprFq1ElGzdUg5a1kvrJ+Ez3D2Gj2aYFNbPZJBtmjaTbNk5jJOLSlfx60OuQ6A0a8TZWMOYdZnRF7H+z0yI2VzP8dJ7Jc8Bv+OGZJmz9lsYxi6DS5cuBCHH344dt99dwl8tWULI7QnYJDZz372s6NaRqU0UeFXUZRhx7ZtWeHlX0VRig0HwdhmtEdXw3F6a1QLDb+nDh7Dh1C8aUBBneNRKBxEZ2S9RIC2ncGbc7NeOqJr0BXdmLXJMzFNRpheLqbSIwUFWLYl27S3oL69nW3bFEFYykaBON4skbAH8tsNx5vQGlnZkzqJAnbiGCN3faOOCr9Zcc0112C33XZDQ0MDli1bJm6FhxxyCNavXz/cLaSMcVT4VRRl2JkyZQpuvPHr8ldRlGLDEeFFNHqjEOwqF2h2y4BOiYjOThbj0g2on1gmmtAhaX67tam9A0hlq/ltHNFgUGxDtuWOWmq2c0dPO1OATZSNmt+gvGYQq/7ou1/qMcYMKvxmxQsvvICbbroJ48ePx/z58/G3v/0Nxx57rGRYWb1aUyIqw4cKv4qiDDvl5WXYZ5+95a+iKP3DaLv0yRyKn2Q+jlFImFYYXVYjQtaOprcUHKMSrCohsEWtth4NNU2gk77K/DxVeEuOSxXl1bIP6yxbDMMtvrFu1/Yxjedg5GiPqyLj7yRwlDvhu5x//2lDglTRPzsXv2Mlz9CXN9ttENxxxx2SYpPBVg888EC88sorGfd95JFHsN9++0nOXMao2WuvvfCrX/2qT3Ed3HDDDbIYVFZWhqOPPloynAw3tAZLjaNjGAbuuusunHDCCWICTbNoRRkOVPhVFEVRlALC465AXWABfN66IRyjXI7h9yby0xc3DsLxRqyPvI5tkcWiXez9XRPaxCQ3IlrLtshKEZaTmuDawHwJSEWzXdNK5vjdjtfDiMsLuoNVZQeF3trAApR5x/d8Zhhe1PjnosI3JaPwWeYZj/llB2BSYKGkEsonFMir/LNQ5Z85aoHJFIDW9tluufL73/8eV155paQAYhaSPffcU7SlNB1OR319Pa677jq8+OKLeOedd8SHltvjjz/es8/3v/993H777bj77rvx8ssvi5DMYzLg1HCyyy674LXXXtvh85/+9Kc48cQT8bGPfWxYz6+MXXR0VBRFUZQCgtraCu9kyZc6WFzdx6AgXQrQnLg5vgbt8Y07mNDSNzUY3yb5Z+ljGoxv7clFSwGT9UBhkJ+nM791GwFUeKf00uIOBLXEFd5JvaI7U/gs906E30NNc3rhl9GUJ/vmo8Y7rScicr7gNVK4DnjHAYZO70rR7PnWW2+V/LgUYBctWiQCa3l5Oe677760+x9xxBE4+eSTJbDUvHnzcNlll2GPPfaQCMtJre9tt92Gr33tayJw8jtGWN68eTP+/Oc/YzhhuX73u9+l/Y4C8JlnnqkRsZVhQUdHRVEURSkgbJiI2V0Z/SspNHldVZImh5rNhJCcTthKfmZIjt4q9zjZvz+hmyaz9JkdTlh+n5Q/ezPjBBQWMgkMfeMWd79yLEmJZIkw7HSn4ynvNk1O1I/X8KHM8MPVLTBSGK501aPCVQ+/qwYBVw2qWXdGWa/jxuwgbDshZLPO/K5KeOEf0OQ4hjhidigPkbONPu3PVE8REYI9I5g6SRma8NvR0dFri0bTR8aOxWJ4/fXXxSw5icvlkvfU7A4EBd0nn3xSgksxwjJZs2YNtm7d2uuYNTU1Yk6dzTGHwrXXXot//vOfGb+/8847NUimMiyo8KsoyrDT2NiEu+76mfxVFKV/mC6GpruJ9Ds74hWz6Pnwe+tQ4ZuGav9suPrRIlIjWe+fhwVlB6HSm9kkN+AdLybC9EsdTqj9pMmwz1OL4caywmISHY03y3tqZCt901Htn4WmplbcddfPYbZ6MdU9Hm5QGDdQ7puIuWUHYE7gQEwK7IbJgT2wsPwDqPZN7ak7apDZRjS5ptDJOqv3z0et0b+ZOcWdJqcFTfG1PdrpwcJrSW1/CuTB+Ba4DS/KvBPV77dIzJ5nzJghAmdyYxCodDQ1NcGyLEyaNKnX53xPATYTTBlUWVkJn8+Hj370o/jJT36CY445Rr5L/i7XYypKMaPCr5JXvvGNb0jQgtSNfh1J6ENy8cUXY9y4cTIYn3rqqdi2bZu2QonD1ex//vNf8ldRlP5hTtSu2CbE0/inEprnVvqmicktfU7LfZNFwM0ENYHV3qmY7tsFAc+4jFpin6calb6pefdF3bH8ge7yD79JNqMpd8U2S55iwXAl6sw7CZ0dQfzrn4/BCAUwzl0Nd7fGm+mSpvh2xmTvLqj1zUa9bw5mBhaKSXESCq5MC5RM4cM6q/ZMRQWqB9D7OuhwutBhbstD2iiju/0nSfsnI05TEKbptQa9GiWcLLW+3QGvNmzYIAJqcqNGNJ8whdBbb72FV199Fd/5znfEZ/i///0vCombb74ZbW1tO7xWlOFAhV8l7+y6666SqDy5JX1LyBVXXCHh7B9++GE888wz4ldyyimnaCuUOFzoOOKID8pfRVEGgpli7RTzXZqxlomZs3wrUY2jIjxZdqz7dW9zYP7eFNPahIBlOhFEHJpSJ81/05zVsUVQ7k+LnA94HmpOh5JXN6fzpdal44gJNIXiyspyHHHE4fBXeBFz4lKvhNGko3YQcdA/2KDqHHHH2sEMvXcb2TCdMKJ2pxw/k3E2jxG3Q7DzlC850f6xnvZnnZo2c6qzrIOLJqwMETuHDUB1dXWvze9Pb7LOlEBut3sHhQHfT548OWNxaBrNVEKM9HzVVVfh4x//eI92Ofm7XI+Zb7773e+ipaVlh9eKMhyo8KvkHYau56CZ3DhgE65o/uIXv5CADUcddRT23Xdf3H///ZLr7aWXXtKWKGEmTZqIq666Qv4qipIb9I2t8c8RDR+FMTGLjtIsuk20jx3RdfT27PWbxD6rEI23ilDXEl2NFeFX0RXPZMrIHK9dPWbJw6k1ZMRllj9mJrSmI72sEIxuQmd0PSZMHIcrr7ocgfEubDIbEO3ODRyKN2BN5DVsNpfTO1f+bba3Img2ZhQoKcw3RZZhQ+QNBGNbMu4XNVvk2vOR15fCd0/7dwvTjhNHe2QNQhnbWRluHNvJessFmi1z3kS/3SS2bcv7gw8+OOvj8DdJv+I5c+bIPC31mLTQYtTnXI45VFIX7/ou5ClKvhne5V1lTML8cFOnTpUcdBw8ucI4c+ZMCdQQj8d7BVagSTS/Y2CFgw46KO3xOEgnB+pMgSAURVFGkpEcl1wur/ipGmYDuqIbRbPXEV0vglwcdCUw4MDaQRhL7JPQTrbHNqITm2HLfuknl6IphjOkKNPZsL38Qw34NBhshOLbuoX7WfJJxImg2e6SAFZJAXWj2Y5KYyYm+mqkThqtToTMzNooal/bYmvQJm2RqhHuTczsQMzszNO1M81TQ6/2p2aZJvP9BwdThpUUre6A++UITZbPOeccyd17wAEHSKTmYDAo0Z/J2WefjWnTpvVodvmX+zLSM8cpBphinl/m0yV0Tbv88svx7W9/GwsWLBBh+Prrr5c53EknnZR7ARWlCFDhV8krjBD4wAMPYOeddxaT5xtvvBGHHXYYFi9eLMETuHLJZOu5BFbg4M3jKIqiFAojOy45YiabNMtN6C/52oC3OwJxzAnvEPE4VSDma/5L1SbTxHl7FORuc2onuoMgPRzXkyx/0pQ7m+BPjKhMk2wKeBQe010DI0m74O7ZJ9NvU2Hdxuxwz+f8a4kZM02KIwxnhbjDvzbchi9hlL5DJO7Erwa+8oElHpaT5U29rlyOl779jO6y2xmjiPdHpnrNtJ8l5xiNxY0CINt1h0GsTZx++ulobGzEDTfcIPMmmjI/9thjPQGr1q9fL2bOSSgYf+ELX8DGjRtRVlYmCodf//rXcpwkV199tex3wQUXiK/toYceKsekAkNRShEVfpW8ctxxx/W8Zr44CsOzZs3CH/7wBxl4BwODP3C1k3DlcuJENZ1VFGV0Gclxif6bNG1lyp6+qYlml+0hwtjq8FsiuGYfKGkivK5yOW5S8LSdKDoiaxG120dEa0jBtco3XYQpmiEPJBj6PDWo8ExCZ3wD4lYQZd4JksKpPbo9cjKDVZW7x6Ejtl402Tv+dqPkAk4lYjajM9q0QwCquNkhpuOMqmx2122Vf5akE+qKbRy2OvJ7xqHMXYv22Lq0eYkHAxcKGOGaKbT6M8vOXKb09dqXgKceZe767rIP3bS7GMnWpDlXs+ckl1xyiWzp6BvIihpdbv1B7e83v/lN2RRlLKA+v8qwQi3vTjvthJUrV4pfCfPU9Y3iN1BgBQZ/SA0GoRQfjPK9dOlS+asopcBIjkvUtnXG1iMc7+1zygjDMwO7YUZgtxwjNCeE3yrfTNHUpZrudsTWIRIfmWAzhuFChW8qKnxTJArzQDAadY1/Ntyucnkf8E5AlX+mmIUnYZRjpv7pm66JkbH5W4878dvUcam9aws6Yxt2CEJFIbktuhqt0dXoFGEXIqyX+SaKQDw8GAh4x4mQ7TLyl6uXdcS6Yp0Nhkz1uuN+dSJkD3e6rFIKeKUoysiiml9lWOnq6sKqVatw1llnSaAGr9crgRWY4ogw2TrNdEYysIIy8mzatBlf/vJXtOoVZVCkM7PdjjFC58k/RtZXkDBb9krk5UzfUXjla58rkNg3h3Fpu1m0ud0MHLZEpk7iloWC4QsE1pt8n4cm5l64DLcI7pIayeltGp+Pc/C4iboffPnZFl7DC4tm+BjJ/pgfUnP4DrRfKdDa2irBTN9//315v3DhQpx77rmor+8/53V/mmhFGU5U86vklS996UuSwmjt2rUSxfnkk0+W0PxnnnmmJG8/77zzxFTw6aeflgBYDNJAwTdTsCtFURQlPbYdw/rIe9gQeU9eZ4+DSLwRXbENecg1O3joY8xoxWKGO4AkEPDWI+CulZy9SXNaXkNndINodat91Db6magJ44061HgmiVl1qha3I7YWphXKmC+3yjejX6GZdUUNcTjWIKbmw4ODaLwFnWKOPjyB1Kjt57VSg52tkMrI4jSRz5cZdn+UueuwU/neqPPNLM5cxWNI8/vss89KkKzbb79dhGBuP/nJT+QzfjcYNNqzMtyo8KvkFQZVoKDLgFef+MQnMG7cOEljNGFCwtTqRz/6EY4//njR/B5++OFi7vzII49oK5Q48+bNxd/+9mf5qyhKfqCP79rw21gbfqc7OFK2MJ3Ptm5/39HTrImvb2yjRCfu39+XpsDjxZe0M75J0jglIh03oiO6Fj6aNAfmiKmtG25MctWj1jO1lyk10yq1RVZ3/3bHcYlm4DTrTTWh7gu1whRKE2mEhs8nOmI2oT26RvI3DwcU8GlWXe5NpM7KhqjZivbo6hERfss99dilfH+M881GMZLU/GazFTsXX3yxzPXWrFkjczluq1evxhlnnCHfZcuSJUswe/bsnteMFaMow4WaPSt55aGHHur3e0YPvOOOO2RTFEVRBmcWmoiYbCPmZAo+RONWV6+0O6m/Syf0cn+J6AtrmNMQbS9bNlGek2Xj/xQIk2VLXANNbWnSmzBJ5tUZ3f7EqUi0655o2WmOL+a6282aWVcsZaqGN3GMWPd3ybrNPwnTa3NQdZps30yadjHjNrojSosJ9P+zdx7wbZXX3//dqy1vO3tvQhIIYa8CLZTZUkZpGS2UUgqUPcpoyyxlr1JoKZRR3j+Fsgpt2WXPAAkhCZC9nHhvW/Ou93OOLEWyZVuyJVuyzzefG0tXV1fPvbp67nOec87vpChMbOkwUmgTh4r3Uk4rFeg4HKoLtnwdotKhp3JpDINKVKTn8uyzz3KEXxR6TBF+jz/+eMr7mTx5ctLHgpAN8rRnEQRBEISRBxleBc5xMK1ovdrko2zyghY4xiKoNyNstLK3r8AxHroVQEBrSDryLrCPxgTHJFRrVWjXSRE4O9hVD7yOMQjoTdAMqlOcmuex3VKSlhiikNx2qxKmGUJQb8Km4Aq0UPut1K0LUny2TI33T2HBhY7xXEYqWr6IjN4OrZpNzL7O41BAJaMKHeMQMtu5TnEyxXDysIfMNpimzo+1zrrOmcNiD3u7tZW/i/4SNFqxPrAczVpEZCzfoMsulUsvjcszZ9l1110515ei/eKhdQsXLkx5P6ZpsiFdV1fHj+OhKEFByCRi/AqCIAhCnkDeOgpZJWMsoFHuaXJI1bjUNQtNWBczfik02K83dhpt3faMYscEzCvYGwHfp2jXsxfaa7cVosw1CwZWp2j8UohzAxvAyYyqoNbARheF5AasBqw2FiPcRz3arvv3h2sRVBrYSLSpTg6BLlQqoClhPg0hqx1+vZ69kr2fx6GB8p1L3DO5lBOdp67fHRn1FLZsWgY/pnDxiJhXZr/j+O+iv/iNBnzt+wQhzmPPPwtxJAleXXDBBbjwwgvZcI1qt1CqG0X33XLLLVi+fHlC+ctk0PYnn3wyNm/e3C3fl6I6DCPbdceFkYYYv4IgCIKQNyhc3zfyUOnRNiAPsZ1VjzvDERUVNiWqgpw82NWmOOFVi9iQyiYUkmxTPdvblgIUHt1TWVQy5qIeYZoU8KWV/5xs/wqfA7vihUWh4gqgd4ZY0zmn82jr5TwOBXRO6ftWehTsMqHH1d2Nf5xJ4r+L/mJYGjoMqjWdp6QqZjUMjF/SeCEuv/zypK+R8UoGbW9G7Nlnn43dd98dL730EsaPHy9qz0LWEeNXEISss2VLJX75y7PR0NAoZ1sQBgDlU1K4M+eF9uI6Is8bKSNrRkfkfZYOn1aNEHtakxlsFnszNwe/hk+nesLZI9K2bdDixKdyqV9iFWqtBib8MBQ6z4CGAK8ni6X38zg0kNo3fd/kdc2ldo1ERpLnl4SuBsratWs5b3jWrFkZaZMg9IUYv4IgZB1N01BdTWGUgiAMBDLASHGYcjV7y9fUTT9agmuhd5bLIeOoNbiB/H89Gkdt2jasNFrgN/1ZNaDIII+0LfvKwf3plzgsOLgRHbDHznFEnEuHYqHP8zgUkBBYS3DdkKp3CyMv5zcTqsx77bUXh02L8SsMFmL8CoKQdcaOHYNTTjkZTzzxD9TW1skZF4R+Y0Ez+/aYkqc3bLXHvcuEZka8wD2hWQG0GtkJh+2tbbnXL/V8jsle6es8DlVEQC62a0RiKpElle2GCVSeaMuWLQiHE1MOjj766D7fe/755+PSSy9FTU0NdtppJzgciaH7PeUKC0J/EeNXEISsU1hYiG9/+yC8+OK/xfgVBCEnkH5JyAYjKeyZavoee+yxWLFiRSy/l6DHRCpiVccffzz//fnPfx5bl0qusCD0FzF+BUEQBEEQBCEDWBYZbn17dVPZJtchpefp06fjzTff5L+ffvopGhsb2ZN7xx13DFresCCkgxi/Ql7RVQY/FIqWvRgGyTPDGCqpQfl12SitIWSO6O9p++8q+e9OSET6pfxE+qX8IN/6pZHk+f3444/x1ltvYdSoUVBVlZf9998fN998M5dB+uKLLwYlb1gQ0kGMXyGv6JpPUlJSMmRtEVJnw4b1OO644+SU5Thut7vH311PrwnSL+Ur0i/lB/nWL7HgVSrGb27a7mlBIclFRUX8mAzgqqoq7LDDDmzQrl69usf3/fvf/8YRRxzB+b30uDdSyRsWhHQQ41cQBEEQBEEQMsBICntesGABvvzySw55JtXm2267DU6nEw8++CBmzJjR4/uOOeYYFrgaM2YMP+4JyfkVsoEYv4IgCIIgCIKQCUwF1ghRe/7d734Hny+ijH7DDTfge9/7Hr71rW+hoqIC//znP3t8n2maSR8LwmAgxq+Qd+qcra2tsec0w9je3s6zh3V1dSguLka+09bWNqyOZzge03A7nq7HRGFs8SkG9LsTRna/NByvezme3Ccf+6WRVOf3sMMOiz2mOr2rVq1CU1MTysrKYorPfREMBnMyfF0YvojxK+QVJKbQddAVvRm6XC5e8p3oMQyX4xmOxzTcjqfrMdFARAYjqTMS+qXheN3L8eQ++dgvjaSw52SUl5entX1paSn23HNPHHjggTjooIOw7777wuPxZK19giDGryAIgiAIgiBkACvFsOeUQqNzHPLa/ulPf8Lbb7/N3vmuIcxLly7tcx//+9//8N577+Gdd97B3XffDV3Xsfvuu8eM4e9+97tZPAJhJKIOdQMEYaDQjPC11147LDwRw/F4huMxDbfjGa7HNJQMx/M53I5Jjif3ycfvKBr2nMrSH+6//35MmzaNveAkMkW1dXvioYce4hxcCkOm5ZBDDum2/c9+9jMOUY5fDj/88JTacsYZZ7DIFak7U77vD37wg4QlFag00m9+8xu8/vrraGlpYUOaQqhpv6m2QxDSQbFytVCaIAiCIAiCIORJfjKVX1zz/Z+iyOHsc/t2LYw5//l/rBeQag49iUideuqpeOCBB9jwveeee/DMM89wWSHKje7KKaecgv32249DiclYvvXWW/Gvf/0LX331FSZOnBgzfmtra/Hoo4/G3keTDWQs9wUd78svv8yfMRDWrFnDnt/oQjWdDzjgAPb8XnjhhQPatyB0RcKeBUEQBEEQBCEDmKbCSyrbpctdd92FM888E6effjo/JyP4pZdewiOPPIIrr7yy2/ZPPPFEwvO//e1veO655/Dmm2+yER1v7I4bNy7t9pABHa3z219oH4FAgA1dWq644grsvPPOKQtmCUK6SNizIAiCIAiCIAxB2DN5jOMX8nomg0T0lixZwqHLsUG8qvLzjz/+OKW2+f1+aJrWTZSKvK3kOd5hhx1wzjnnoLGxMaX93XnnnWysbt68Gf1l9OjR3C6q+0sLeaHJGBaEbCHGryAIgiAIgiBkUO05lYWYPHkyhw9Hl5tvvjnpfhsaGmAYBsaOHZuwnp6T0ZgKZKhOmDAhwYCmvNrHH3+cvcEUFv3uu+/iiCOO4M/qCxKmItGrGTNmsAeYjOr4JRWWLVvG7SfPNRn+lP87atQoDtX+7W9/m9I+BCEdJOxZEARBEARBEIag1FFlZWVCzm+2xL1uueUWPPXUU+zljS8ZdeKJJ8Ye77TTThxyPHPmTN7u4IMP7nWfJ510ErZt24abbrqJjfD+hipTuaOjjz46lp/84osv4sknn8TixYvxhz/8oV/7FISeEM+vkLNQh0edoNfr5Y4xGVu2bMFRRx3F21DIzq9//WuWyY+HOvBdd92VbyikIPjYY48NSD0xk9BndlVZpBtUPMuXL2e1RmobzRCTAmJXSPBi7ty5vA3dvEiAIlcYqnObLtddd12374LOaRSa3T733HNRUVGBwsJCHH/88Ryele71mE2oXMT3v/99ntmn9r/wwgsJr5O+4TXXXIPx48dzHUWa/V+7dm3CNk1NTSySQoMx+t2RmmdHR0fa1+RwRfqlCNIvDQ7SL+Vfv2RaSsoLQccUv/Rk/JI31Gazdbvv0PO+8nXvuOMOHluQojIZt71BXlz6rHXr1vV5rB999BGPP8ijTMJZp512WsKSCs8//zwuuOACbhcZ0BR2Td8thVSnUipJENKG1J4FIRe55pprrLvuusu65JJLrJKSkm6v67puLViwwDrkkEOsL774wnr55ZetUaNGWVdddVVsmw0bNlher5f38fXXX1t/+tOfLJvNZr366quxbZ566inL6XRajzzyiPXVV19ZZ555plVaWmrV1tZm/RinTp1q3XDDDVZ1dXVs6ejoiL3e2tpqjR071jrllFOslStXWk8++aTl8Xisv/71r7FtPvzwQz6m2267jY/xd7/7neVwOKwVK1ZYQ81Qntt0ufbaa6358+cnfBf19fWx188++2xr8uTJ1ptvvml9/vnn1t57723tu+++aV2P2YY+87e//a31/PPPUzaZ9a9//Svh9VtuuYV/Sy+88IL15ZdfWkcffbQ1ffp0KxAIxLY5/PDDrYULF1qffPKJ9f7771uzZs2yTjrppLSuyeGM9EvSLw0m0i/lT79EbaB+d/l3f25tPPLsPhfajran96XKnnvuaZ133nmx54ZhWBMnTrRuvvnmHt9z6623WsXFxdbHH3+c0mdUVlZaiqJYL774Yp/bLlq0KOX99sTo0aOt448/nsdny5cvH9C+BCEVxPgVcp5HH300qfFLA31VVa2amprYur/85S/cyYdCIX5++eWXs0ETz49//GPrsMMOS7iZnHvuuQk3kwkTJvR6M8mk8Xv33Xf3+Pqf//xnq6ysLHY8xBVXXGHtsMMOsec/+tGPrKOOOirhfXvttZd11llnWUPNUJ7b/gwyaXCVjJaWFp5QeOaZZ2LrvvnmGx64RG/8qVyPg0lX49c0TWvcuHHW7bffnnBcLpeLB4oETZ7Q+z777LPYNq+88goPhLZt25byNTkSkH5J+qXBQPql/OmXosbvl4f83NpwxNl9LrRdusYvTShTn/3YY4/x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xowZWLVqFRYsWIBNmzbBsizsuuuuA9r32LFjsXr16oy1VRCiiPErCIIgCIIgCBlgJNX5veqqq3DZZZfh+uuvR1FREZcqGjNmDE455RQcfvjhKe1j+fLlCc/JcK6ursYtt9yCXXbZJUstF0YyYvwKgiAIgiAIQgawrMiSynb5zjfffIMnn3ySH9vtdgQCARQWFuKGG27AD37wA5xzzjl97oMMXBK4IqM3nr333huPPPJI1toujFxE8EoQhAEzbdo0DnXqD5QnRDdLqnUoCIKQbaS/ErKJBQVmCgttl+8UFBTE8nzHjx+P9evXx15raGhIWT16w4YN/JeWzZs3w+/346OPPsLcuXOz1nZh5CLGryCMEH7+85/z7CrN1PbFhx9+iIULF8Lr9fKs7Mcffzygz37llVew5557srBFWVkZqzi+/PLL/NqUKVPQ0dERE7342c9+hosuuqjfn0VKkRRyNWnSJBQXF2PRokUsxiEIwtDywQcf4IgjjuA+oLS0lPuY2267LUEk57HHHsNee+3FE2IVFRX8+7355pvh8/l63K/0V0Iuen5TWfId8s7S75o48sgjcemll+IPf/gDjzfotVSYOnVqwjJ58mS43e4st1wYyYjxKwgjgPb2djz99NMoLy/Hww8/3Ou2TU1N+N73vofzzjuPyw6ce+65/LylpaVfn00zwSeccAJ+85vf8L4pl+eOO+7g/KBsQIY0DZg/+eQTbjOFX5100kn4+uuvs/J5giD0zX//+182fA877DCsXbuWf5v//Oc/+XdJfQJxxRVX4JprrsFvf/tbXtfY2IgnnngCNTU1WLduXdL9Sn8l5GrObypLvkNqzjRZRVDe78EHH8y/a4qu6GusEQ9NbtGE+AMPPIB77703YRGEjGMJgjDseeihh6wxY8bE/obD4R63/dvf/mbNnz8/Yd28efOsRx55pMf3TJ061dq4cWPS15555hlrxowZPb6X3kddUXNzs/XHP/7RstvtlsPhsAoKCvhzCWrv1VdfzfspLy+3vv/971vbtm2zUmXRokXWww8/nPL2giBkDtM0renTp1u///3ve9xm3bp1ls1msz788MO09i39lZArtLa28r3sxjlXWXfseH2fC21H29P7RjJLly61xo0bZxUXF3MfMHr0aEtRFB4DUL8hCJlGPL+CMAKgGVgKBT7xxBN5hvU///lPr8qL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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "raster_render_mode = \"scatter\" # \"scatter\" | \"heatmap\"\n", + "raster_render_values = \"raw\" # \"auto\" | \"raw\" | \"smoothed\"\n", + "raster_axis_orientation = \"position_x\" # \"position_x\" | \"position_y\"\n", + "raster_coordinate_mode = \"relative_to_primary\" # \"relative_to_primary\" | \"local_window\"\n", + "raster_max_rows = 500 # cap displayed reads; set None to show all\n", + "raster_downsample_method = \"auto\" # \"auto\" | \"uniform\" | \"random\" | \"bin_mean\"\n", + "raster_downsample_seed = 42 # used when downsample_method=\"random\"\n", + "raster_plot_all_motifs = True\n", + "raster_second_site_offset_bp = 2000\n", + "raster_window_width_bp = 2000\n", + "\n", + "# Optional motif-specific ML thresholds for thresholded motif-color rendering.\n", + "# Set to None to use continuous ML-score coloring (default behavior).\n", + "raster_ml_thresholds = None # example: [0.70, 0.80]\n", + "raster_motif_colors = [\"#1f77b4\", \"#ff7f0e\"]\n", + "\n", + "# Sorting controls. Site 1 is the primary site for the two-site wrapper.\n", + "raster_sort_by = \"mod_fraction\" # \"mod_fraction\" | \"cluster\" | \"window_center\" | \"read_name\" | \"region_start\" | \"read_length\" | \"none\"\n", + "raster_sort_window_index = 0\n", + "raster_sort_descending = True\n", + "\n", + "show_vertical_axis_variant = True\n", + "show_heatmap_variant = True\n", + "\n", + "\n", + "fig, stats = cluster.plot_two_site_read_raster(\n", + " read_windows=rw_multi,\n", + " second_site_offset_bp=raster_second_site_offset_bp,\n", + " window_width_bp=raster_window_width_bp,\n", + " motif_index=0,\n", + " motif_count=2,\n", + " plot_all_motifs=raster_plot_all_motifs,\n", + " motif_labels=[\"A,0\", \"CG,0\"],\n", + " motif_colors=raster_motif_colors,\n", + " ml_score_thresholds=raster_ml_thresholds,\n", + " smoothing=None,\n", + " render_mode=raster_render_mode,\n", + " render_values=raster_render_values,\n", + " scatter_color_values=\"ml_score\",\n", + " axis_orientation=raster_axis_orientation,\n", + " coordinate_mode=raster_coordinate_mode,\n", + " sort_by=raster_sort_by,\n", + " sort_window_index=raster_sort_window_index,\n", + " sort_descending=raster_sort_descending,\n", + " scatter_size=1,\n", + " scatter_alpha=0.5,\n", + " max_rows=raster_max_rows,\n", + " downsample_method=raster_downsample_method,\n", + " downsample_seed=raster_downsample_seed,\n", + ")\n", + "print(\"Two-site fixed-offset raster stats:\", cluster.raster_stats_brief(stats))\n", + "\n", + "if show_vertical_axis_variant:\n", + " fig_vert, stats_vert = cluster.plot_two_site_read_raster(\n", + " read_windows=rw_multi,\n", + " second_site_offset_bp=raster_second_site_offset_bp,\n", + " window_width_bp=raster_window_width_bp,\n", + " motif_index=0,\n", + " motif_count=2,\n", + " plot_all_motifs=raster_plot_all_motifs,\n", + " motif_labels=[\"A,0\", \"CG,0\"],\n", + " motif_colors=raster_motif_colors,\n", + " ml_score_thresholds=raster_ml_thresholds,\n", + " smoothing=None,\n", + " render_mode=\"scatter\",\n", + " render_values=\"raw\",\n", + " scatter_color_values=\"ml_score\",\n", + " axis_orientation=\"position_y\",\n", + " coordinate_mode=raster_coordinate_mode,\n", + " sort_by=raster_sort_by,\n", + " sort_window_index=raster_sort_window_index,\n", + " sort_descending=raster_sort_descending,\n", + " scatter_size=4,\n", + " scatter_alpha=0.05,\n", + " max_rows=raster_max_rows,\n", + " downsample_method=raster_downsample_method,\n", + " downsample_seed=raster_downsample_seed,\n", + " )\n", + " print(\"Vertical-axis scatter stats:\", cluster.raster_stats_brief(stats_vert))\n", + "\n", + "if show_heatmap_variant:\n", + " fig_hm, stats_hm = cluster.plot_two_site_read_raster(\n", + " read_windows=rw_multi,\n", + " second_site_offset_bp=raster_second_site_offset_bp,\n", + " window_width_bp=raster_window_width_bp,\n", + " motif_index=0,\n", + " motif_count=2,\n", + " plot_all_motifs=raster_plot_all_motifs,\n", + " motif_labels=[\"A,0\", \"CG,0\"],\n", + " smoothing=\"gaussian\",\n", + " render_mode=\"heatmap\",\n", + " render_values=\"smoothed\",\n", + " smooth_sigma_bp=7,\n", + " smooth_win=21,\n", + " axis_orientation=\"position_x\",\n", + " coordinate_mode=raster_coordinate_mode,\n", + " sort_by=raster_sort_by,\n", + " sort_window_index=raster_sort_window_index,\n", + " sort_descending=raster_sort_descending,\n", + " max_rows=raster_max_rows,\n", + " downsample_method=\"bin_mean\",\n", + " downsample_seed=raster_downsample_seed,\n", + " )\n", + " print(\"Heatmap stats:\", cluster.raster_stats_brief(stats_hm))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Co-occurring-region raster demo (two sites)\n", + "\n", + "This uses the full `plot_multisite_read_raster` selector. It finds reads with two regions separated by at least the requested distance and plots each selected site in its own local coordinate system. The same selector supports arbitrary numbers of sites by increasing `n_windows`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Co-occurring-region raster stats: {'pairs': 500, 'rows_are': 'reads', 'unique_reads': 664, 'site_sets': 664, 'rows_before_downsample': 664, 'rows_after_downsample': 500, 'downsampled': True, 'downsample_method': 'random', 'coordinate_mode': 'local_window', 'selection_mode': 'cooccurring', 'window_offsets_bp': None, 'window_widths_bp': [2000, 2000], 'ml_score_thresholds': {'A,0': 0.65, 'CG,0': 0.5}, 'observed_offsets_bp': [{'n': 664, 'min': 0.0, 'median': 0.0, 'max': 0.0, 'unique': 1}, {'n': 664, 'min': 2000.0, 'median': 3868.0, 'max': 3898.0, 'unique': 24}], 'site_selection': {'mode': 'cooccurring', 'n_windows': 2, 'min_distance_bp': 2000, 'max_distance_bp': None, 'selection_multiplicity': 'one_per_read', 'choose': 'first', 'selection_seed': 42, 'anchor': {'mode': 'first'}, 'strand_relation': 'any', 'orientation': 'genomic', 'excluded_sites': 0}}\n" + ] + }, + { + "data": { + "image/png": "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WlrkpptukomJCfna174mm2yyibS1tckOO+wgv/3tb7PfW716tbz73e+WRYsWmeXY7kUXXZTTrscff9wcI/aL79966605y3/3u9/Jtttua/YJt10cZxyPPvqovOIVrzB9uc0228hf//rXnOUjIyPykY98RNZZZx2zzkYbbWSOgRBCCJkOKEwJIYRMK8ccc0yOCLvwwgvl6KOPnrLeySefbMQXXHHvuusu2XzzzeWggw6SVatWGXGLZeCRRx4xLrrf+ta3pmwDovfYY4+Vvfbay6xjC2LliCOOkE9+8pNG5GEd/OEzBaL17W9/u9x3333y+te/3ghKtMHm05/+tJx55pny0EMPyctf/nIj6H72s5/JBRdcIA888ICccMIJ8p73vEf+8Y9/mPU///nPy4MPPmgELb7z/e9/XxYuXJizzc9+9rPyqU99ysSavuxlL5N3vvOdWWvsnXfeadr0jne8Q/7zn/8YV2Rs0xbUNhDKb37zm6W5uVluv/12065TTjklZ51vf/vbRnT/5je/MX16ySWXGMFLCCGETAeN07IXQgghJAME2qmnnipPPfWUeX/zzTcb915YH5X+/n4j1iC0Dj74YPPZj370I2Pl+8lPfiInnXSScdkFsKjOnTvX279YBxZHCDK48fqAxbKzs1MaGxu968ASC1EIvvrVrxoBd8cdd8jrXve67DpnnHGGHHjggVlLL9a77rrrjCAGm266qbGk/uAHP5BXvvKV8vTTTxsL8a677mqW+wQgROkhhxySFccQzv/73/9kq622knPOOUde85rXGDEKIFwhdM8++2xvnC7a8vDDD8u1114r6667bvZYtG8B2gTLLSzTsALDYkoIIYRMF7SYEkIImVbgvgrBBdEJyyn+71oLERc6Ojoq++yzT/azpqYm2X333Y2FcTqBBVTp6Ogw2X1ffPHFnHVUYAKIR2QChlCF4NU/WFBxXOBDH/qQEeNIuATLMBIzxe0X7rVA94s+sPsG4D3cdcfHx6dsC+vDyqyiFKhoViBoYZ3dcsstjQv1X/7ylzx6iRBCCCkOWkwJIYRUxJ0X8Yzg/PPPr+ozAEFsA2siXGNtIFiVvr4+83rVVVfJeuutl7Me4kEBLJWwGF999dXGCgzrJzITf+Mb3/DuVxM2ufstdfzvE088YdyLYWGFq/ABBxyQExtLCCGElAtaTAkhhEw7cINF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F9cF7j+EEFJxpi3NEiGEzKCsvFdccUXQ3NxsMur6smjuuuuukd8988wzg7lz5wa///3vg/vuuy944xvfGGyyySbB4OBg5HewHrLP2plx3cybo6OjwUsvvWReAT73ZeW99NJLzTL7b2RkxLvfTTfdNNhmm22Cr3/968Hzzz8flJPHH388aG9vD0466aTgoYceCs4///ygoaEhuOaaa7LrfOc73wn233//7Pvx8fFg5513Dl7xilcEd911V/Dvf//bZBY98MADs+uce+65wZVXXhk8+uijwX/+85/g4x//eFBfXx9cd911Ofs/8sgjg2OOOSaoaiqUlfexxx4zWXMxFn77298G//3vf01G5G9961vBVlttlV3v8ssvD5qamoLXv/715rzhe/fee68ZPxh7f/jDHyL3weuidNfFxRdfHPzyl7806+MP2cIx5i+88MLsOjfffLO5p3zjG98w63zhC18w5w7XiM0rX/nK4Iwzzihw5BBCSOmgMCWEzAggPnsHR4IVfUNGkOIV78tVKubQQw81k28ft99+u5mEY0IONtpoIzPps0tjoJTLkiVLTJmY17zmNcEjjzySuM/dd989uOCCC1KXhIgSpr6/Z555xrsNTFinE5R22XHHHY3ohyi+6KKLcpajH9GfNs8991zw5je/2ZTtQZ8eddRRwcqVK7PLIYo222yzoLW1NZg/f37wqle9Kvjb3/6Wsw08FOju7g5uvfXWoOqB+BxcGwR9K4Kg96XwFe/LJEoVPJg4/vjjTf/j/KB8zGGHHWbOmc2//vWv4K1vfWuwePFiI3xQQgalTfBAxC4Xw+uifNcFhOnWW29tBG1XV5e5d6DklMtvfvMbU1oK20VJmquuuipn+bPPPmvEatT9gRBCppM6/FNpqy0hhKQF2XeR6AgxpaV23y0EJNJZsGCBKVBfbEIkZNc86aSTjGtdfT1TAJQSuD/D/RQlT2YMyL5r4krrS+6+W254XcwMTjnlFBPbbseSE0JIpWCMKSFkRgExWursu8Xw97//Xfbff/+SZOk95JBD5NFHH5XnnnvOlDwhpQOldb7zne/MrC41YnRmCVKF18XMAOWh7Oy+hBBSSWgxJYQQQgghhBBSUegrRgghhBBCCCGkolCYEkIIIYQQQgipKBSmhBBCCCGEEEIqCoUpIYQQQgghhJCKQmFKCCGEEEIIIaSiUJgS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NpCzzjprynYuu+wy2Wqrrcw622+/vVx99dUy05lp5zJfvvjFL0459ziHytDQkBx//PGyYMECUyvyLW95iyxfvjzv66ba+Oc//ylveMMbZN111zXHfOWVV+YsR/oDZPteZ511pK2tTQ444AB59NFHc9ZZtWqVvPvd7zZlSnBPef/73y99fX15XzekMHhP98P7ee3e02v1fg54TyclAcmPCJkOTjvttOCcc84JTjzxxKC7u3vK8rGxsWC77bYLDjjggODuu+8Orr766mDhwoXBqaeeml3n8ccfD9rb2802HnzwweA73/lO0NDQEFxzzTXZdS699NKgubk5uPDCC4MHHnggOPbYY4O5c+cGy5cvr9oTvdFGGwVnnHFG8MILL2T/+vr6sst7enqCJUuWBO9+97uD+++/P/jVr34VtLW1BT/4wQ+y69x8882mL8466yzTN5/73OeCpqam4D//+U8wU5mJ5zJfvvCFLwTbbrttzrl/6aWXssuPO+64YIMNNgiuv/764N///new5557BnvvvXde1001gnZ+9rOfDS6//PIAP0VXXHFFzvIzzzzT3CeuvPLK4N577w0OO+ywYJNNNgkGBwez67zuda8Ldthhh+C2224LbrzxxmDzzTcP3vnOd+Z13ZDC4T3dD+/ntXtPr9X7OeA9nZQCClMy7Vx00UVeYYqbWn19fbBs2bLsZ9///veDrq6uYHh42Lw/+eSTzU3f5ogj/r+994CTrCrTxt/K1dVppicyAzOABMEACIqguLsCouuucVddE7J+iooZUfmrCAZEUfTTNa9h3XVVMH5mXQwLElQEBASUIHnyTHdXd+W6/99zbr1Vp06fmyp0V3W/z+9XU11V9554753znOcNz3dOO+205ufHPe5xzllnndX8XKvVnE2bNjkf+MAHnEEFFjIf/ehHPX//1Kc+5axevbo5DsDb3vY25/DDD29+ft7znuc8/elPbzvv+OOPd84880xnWDGMc9nJQgbkyoZ9+/apzYXLLrus+d2tt96qiNzVV18d+r4ZdJjEtF6vOxs3bnQuvvjitrHIZDKKXALYfMF5v/vd75rH/PjHP3ZisZjzwAMPhL5vBN1DnuntkOf5yn2my/PchTzTBZ1CTHkFA4Orr75amZ9u2LCh+d1pp51GMzMzdMsttzSPgUmfDhyD74FyuUzXXXdd2zHxeFx95mMGFTDdhXnPMcccQxdffHGb6Q7a/qQnPYnS6XRbv2+//Xbau3dvqLEZNgzzXEYFTFRh0nrwwQcr01SYcgHof6VSaRsDmIVt2bKlOQZh7pthw913303btm1r6/fk5KQy+9P7DfPd4447rnkMjsc1cu2114a+bwT9w0p+psvzfCGGdS6jQp7nCyHPdEFYJEMfKRD0GViI6gsYgD/jN79jsNApFApqsVmr1azH3HbbbQM7h69//euV3+zU1BRdddVVdO6559JDDz1El1xySbPfBx10kOfYrF692nNseOyGDbt27RrKuYwKkC34SR9++OFqzi+44ALlE3nzzTeruQOpMn2y9XkNc98MG7jdftcz3uF/pSOZTKp7SD8m6L4R9HceV+IzXZ7nK/eZLs9zO+SZLggLIaaCrvD2t7+dPvjBD/oec+utt7Y5/68URBmbN7/5zc3vHv3oRysycuaZZ9IHPvABFeRJsHzxtKc9rW3usbDZunUrXXrppSroj0CwmJBnevfjIs/zlQt5ngsE3UGIqaArnH322fSyl73M9xiYJ4bBxo0bF0Tn42h1+I3fzQh2+IyonFjEJxIJ9bIdw2UMw9iAnMCU969//atS0rz6HWZsFrvfvcLatWsHZi4XE1BHDzvsMLrjjjvo1FNPVeZv+/bta1NN9TEIc98MG7jd6Aei8jLw+eijj24es2PHjrbzcM8gUm/QPaHXIWiHPNPtkOd591iJz3R5nruQZ7ogLMTHVNAV1q1bp3aI/V66f5cfTjjhBLrpppvaFps///nPFek88sgjm8dcfvnlbefhGHwPoK5jjz227Zh6va4+8zHDMDY33HCD8r1hU0W0HaHY4W+o9xuklc0Rg8Zm2DBIc7mYQLqTO++8UxEy9D+VSrWNAfwj4YPKYxDmvhk2wPwWCxm93zDthO+o3m8QdvisMX7xi1+oawQbO2HvG0E75JluhzzPu8dKfKbL89yFPNMFodFx2CSBICLuueceFf78ggsucMbGxtTfeM3OzraFSX/KU57i3HDDDSoFzLp166zpYs455xwVnfSTn/ykNV0Mond++ctfVpE7X/nKV6pw9HrU0kHCVVddpSLyos933nmn81//9V+q3y996UvbIpIi7cVLXvISlfYCfcQ4mOliksmk8+EPf1iNDaIDLod0McM0l53g7LPPdn71q185d999t5pDpAlAeoAd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MorFIp111lm0Zs0aGhsbo+c+97m0ffv2yPfNoOF///d/6R//8R9p06ZNqs/f/e53235HcnVE+95vv/1oZGSETjnlFPrLX/7SdsyePXvoRS96kcp7h2fKy1/+csrn85HvG0FnkGe6HfI8X7nP9JX6PAfkmS7oCRyBYJFw3nnnOZdcconz5je/2ZmcnFzwe7VadR75yEc6p5xyinP99dc7P/rRj5y1a9c65557bvOYu+66y8nlcqqMP/3pT84nPvEJJ5FIOD/5yU+ax3z961930um088UvftG55ZZbnFe84hXOqlWrnO3btw/sXG/dutV5z3ve4zz00EPNVz6fb/4+PT3tbNiwwXnRi17k3Hzzzc7XvvY1Z2RkxPnsZz/bPOY3v/mNGosPfehDamze+c53OqlUyrnpppucYcUwzmVUvPvd73Ye8YhHtM39zp07m7+/6lWvcg444ADn8ssvd37/+987j3/8450TTzwx0n0ziEA73/GOdzjf/va3HfxX9J3vfKft94suukg9J7773e86N954o/OMZzzDOeigg5xCodA85qlPfapz1FFHOddcc41zxRVXOIcccojzL//yL5HuG0HnkGe6HfI8X7nP9JX6PAfkmS7oBYSYChYdX/rSl6zEFA+1eDzubNu2rfndpz/9aWdiYsIplUrq81vf+lb10Nfx/Oc/3znttNOanx/3uMc5Z511VvNzrVZzNm3a5HzgAx9wBhVYyHz0ox/1/P1Tn/qUs3r16uY4AG9729ucww8/vPn5ec97nvP0pz+97bzjjz/eOfPMM51hxTDOZScLGZArG/bt26c2Fy677LLmd7feeqsicldffXXo+2bQYRLTer3ubNy40bn44ovbxiKTyShyCWDzBef97ne/ax7z4x//2InFYs4DDzwQ+r4RdA95prdDnucr95kuz3MX8kwXdAox5RUMDK6++mplfrphw4bmd6eddhrNzMzQLbfc0jwGJn06cAy+B8rlMl133XVtx8TjcfWZjxlUwHQX5j3HHHMMXXzxxW2mO2j7k570JEqn0239vv3222nv3r2hxmbYMMxzGRUwUYVJ68EHH6xMU2HKBaD/lUqlbQxgFrZly5bmGIS5b4YNd999N23btq2t35OTk8rsT+83zHePO+645jE4HtfItddeG/q+EfQPK/mZLs/zhRjWuYwKeZ4vhDzTBWGRDH2kQNBnYCGqL2AA/ozf/I7BQqdQKKjFZq1Wsx5z2223Dewcvv71r1d+s1NTU3TVVVfRueeeSw899BBdcsklzX4fdNBBnmOzevVqz7HhsRs27Nq1ayjnMipAtuAnffjhh6s5v+CCC5RP5M0336zmDqTK9MnW5zXMfTNs4Hb7Xc94h/+VjmQyqe4h/Zig+0bQ33lcic90eZ6v3Ge6PM/tkGe6ICyEmAq6wtvf/nb64Ac/6HvMrbfe2ub8v1IQZWze/OY3N7979KMfrcjImWeeSR/4wAdUkCfB8sXTnva0trnHwmbr1q106aWXqqA/AsFiQp7p3Y+LPM9XLuR5LhB0ByGmgq5w9tln08te9jLfY2CeGAYbN25cEJ2Po9XhN343I9jhM6JyYhGfSCTUy3YMlzEMYwNyAlPev/71r0pJ8+p3mLFZ7H73CmvXrh2YuVxMQB097LDD6I477qBTTz1Vmb/t27evTTXVxyDMfTNs4HajH4jKy8Dno48+unnMjh072s7DPYNIvUH3hF6HoB3yTLdDnufdYyU+0+V57kKe6YKwEB9TQVdYt26d2iH2e+n+XX444YQT6KabbmpbbP785z9XpPPII49sHnP55Ze3nYdj8D2Auo499ti2Y+r1uvrMxwzD2Nxwww3K94ZNFdF2hGKHv6Heb5BWNkcMGpthwyDN5WIC6U7uvPNORcjQ/1Qq1TYG8I+EDyqPQZj7ZtgA81ssZPR+w7QTvqN6v0HY4bPG+MUvfqGuEWzshL1vBO2QZ7od8jzvHivxmS7PcxfyTBeERsdhkwSCiLjnnntU+PMLLrjAGRsbU3/jNTs72xYm/SlPeYpzww03qBQw69ats6aLOeecc1R00k9+8pPWdDGI3vnlL39ZRe585StfqcLR61FLBwlXXXWVisiLPt95553Of/3Xf6l+v/SlL22LSIq0Fy95yUtU2gv0EeNgpotJJpPOhz/8YTU2iA64HNLFDNNcdoKzzz7b+dWvfuXcfffdag6RJgDpAXbs2NFML7BlyxbnF7/4hUovcMIJJ6gXI8x9M4jAfc/PAPxXhFRS+BvPCU4Xg7n+3ve+5/zxj390nvnMZ1rTxRxzzDHOtdde61x55ZXOoYce2pYuJsx9I+gc8kxfCHmer+xn+kp9ngPyTBf0AkJMBYuG008/XS1Azdcvf/nL5jF//etfnac97Wkq1yAe5njIVyqVtnJw/NFHH61yoR188MEqVYEJ5DfFwx/HIDw98hwOKq677jqV1gUpdLLZrHPEEUc4F154oVMsFtuOQy7HJz7xieo/9c2bN6uFu4lLL73UOeyww1S/kVbnhz/8oTPsGKa57ARId7Tffvup/mFe8fmOO+5o/g4i9prXvEalPQGpevazn61y4+kIc98MGnAf254HeE5wyph3vetdiljimj/55JOd22+/va2M3bt3KyKKjS6kxznjjDOaG11R7htBZ5Bn+kLI83xlP9NX6vMckGe6oBeI4Z/w+qpAIBAIBAKBQCAQCAS9hfiYCgQCgUAgEAgEAoFgSSHEVCAQCAQCgUAgEAgESwohpgKBQCAQCAQCgUAgWFIIMRUIBAKBQCAQCAQCwZJCiKlAIBAIBAKBQCAQCJYUQkwFAoFAIBAIBAKBQLCkEGIqEAgEAoFAIBAIBIIlhRBTgUAgEAgEAoFAIBAsKYSYClY0YrEYffe736XliPPPP582bNgwlH388pe/TKtWreq6nL/927+lN77xjT1p00pCuVymQw45hK666ir1+a9//au6jm644Yauyv3JT35CRx99NNXr9R61VCBoxzA+78JCnunyTO8U8kwXDAuEmAqWHV72spepxQleqVRKkbNTTz2VvvjFLy5YED/00EP0tKc9bdkteG699Va64IIL6LOf/WykPg4Knv/859Of//znpW7GQGIxyPZnPvMZOuigg+jEE0/sablPfepT1T351a9+taflCpY35Jkuz/TlDHmmCwQtCDEVLEtgAQxCBqXnxz/+Mf3d3/0dveENb6B/+Id/oGq12jxu48aNlMlkaLnhzjvvVO/PfOYzPfuIHdRBxcjICK1fv36pm7Gs4TX/juPQv/3bv9HLX/7yvpGMj3/8430pW7B8Ic90eaYL/CHPdMGygCMQLDOcfvrpzjOf+cwF319++eUOLvnPf/7zze/w+Tvf+Y76u1QqOWeddZazceNGJ5PJOFu2bHEuvPBC9dvWrVvVsfzCZ+COO+5wnvGMZzjr1693RkdHneOOO875+c9/3lYvjn3/+9/vnHHGGc7Y2JhzwAEHOJ/97GfbjrnvvvucF7zgBc7q1audXC7nHHvssc4111zT/P273/2uc8wxx6h2HXTQQc7555/vVCoVa//f/e53t7WVb3Mel/e9733Ofvvt5xx44IHq+z/+8Y/O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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "cooccurring_ml_thresholds = [0.65, 0.50]\n", + "cooccurring_motif_colors = [\"#1f77b4\", \"#ff7f0e\"]\n", + "\n", + "cooccurring_selection = {\n", + " \"mode\": \"cooccurring\",\n", + " \"n_windows\": 2,\n", + " \"min_distance_bp\": \"window_width\", # defaults to the displayed window width\n", + " \"max_distance_bp\": None,\n", + " \"selection_multiplicity\": \"one_per_read\",\n", + " \"choose\": \"first\", # \"first\" | \"random\" | \"longest_span\" | \"shortest_span\"\n", + " \"selection_seed\": raster_downsample_seed,\n", + " \"anchor\": {\"mode\": \"first\"}, # \"first\" | \"random\" | {\"mode\": \"index\", \"index\": 0}\n", + " \"strand_relation\": \"any\", # \"any\" | \"same\" | \"opposite\"\n", + " \"exclude\": None, # e.g. {\"regions\": \"chr1:1000-2000\", \"row_indices\": [0, 1]}\n", + " \"orientation\": \"genomic\", # \"genomic\" | \"anchor_strand\"\n", + "}\n", + "# Other useful choices:\n", + "# cooccurring_selection[\"choose\"] = \"longest_span\"\n", + "# cooccurring_selection.update({\"choose\": \"random\", \"selection_seed\": 42})\n", + "# cooccurring_selection[\"exclude\"] = {\"regions\": ctcf_off_target_regions}\n", + "\n", + "fig_co, stats_co = cluster.plot_multisite_read_raster(\n", + " read_windows=rw_multi,\n", + " site_selection=cooccurring_selection,\n", + " coordinate_mode=\"local_window\",\n", + " motif_index=0,\n", + " motif_count=2,\n", + " plot_all_motifs=True,\n", + " motif_labels=[\"A,0\", \"CG,0\"],\n", + " motif_colors=cooccurring_motif_colors,\n", + " ml_score_thresholds=cooccurring_ml_thresholds,\n", + " window_widths_bp=[raster_window_width_bp, raster_window_width_bp],\n", + " render_mode=\"scatter\",\n", + " render_values=\"raw\",\n", + " scatter_color_values=\"ml_score\",\n", + " axis_orientation=\"position_x\",\n", + " smoothing=None,\n", + " sort_by=\"mod_fraction\",\n", + " sort_window_index=0,\n", + " sort_descending=True,\n", + " scatter_size=4,\n", + " scatter_alpha=0.05,\n", + " max_rows=raster_max_rows,\n", + " downsample_method=\"random\",\n", + " downsample_seed=raster_downsample_seed,\n", + ")\n", + "print(\"Co-occurring-region raster stats:\", cluster.raster_stats_brief(stats_co))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "dimelo-toolkit", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/dmr_multi_sample.ipynb b/dmr_multi_sample.ipynb new file mode 100644 index 0000000..d1e71d6 --- /dev/null +++ b/dmr_multi_sample.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# modkit DMR multi-sample tutorial\n", + "\n", + "This notebook is for multi-sample DMR analyses using native `modkit dmr` wrappers in `dimelo.workflows`.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "execution_count": null, + "outputs": [], + "source": [ + "import importlib\n", + "from pathlib import Path\n", + "from dimelo import run_modkit, workflows\n", + "\n", + "importlib.reload(run_modkit)\n", + "importlib.reload(workflows)\n", + "run_modkit._get_modkit_capabilities_cached.cache_clear()\n", + "caps = run_modkit.get_modkit_capabilities()\n", + "print(caps.version_raw)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example inputs\n", + "\n", + "Set these to your sample pileup bedMethyl outputs (`*.bed.gz`) and reference FASTA.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "execution_count": null, + "outputs": [], + "source": [ + "ref_genome = Path(\"dimelo/test/output/chm13.draft_v1.0.fasta\")\n", + "regions_bed = Path(\"dimelo/test/data/ctcf_demo_peak.bed\")\n", + "sample_pileups = {\n", + " \"sample_a\": Path(\"path/to/sample_a.pileup.sorted.bed.gz\"),\n", + " \"sample_b\": Path(\"path/to/sample_b.pileup.sorted.bed.gz\"),\n", + " # \"sample_c\": Path(\"path/to/sample_c.pileup.sorted.bed.gz\"),\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pairwise DMR + HMM segmentation\n", + "\n", + "Use this when you want single-site beta-binomial statistics plus segmented DMR blocks.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "execution_count": null, + "outputs": [], + "source": [ + "pair_result = workflows.modkit_dmr_pair_workflow(\n", + " control_bed_methyl=sample_pileups[\"sample_a\"],\n", + " experiment_bed_methyl=sample_pileups[\"sample_b\"],\n", + " ref_genome=ref_genome,\n", + " out_path=\"artifacts/dmr/sample_a_vs_sample_b.sites.bed\",\n", + " segment_path=\"artifacts/dmr/sample_a_vs_sample_b.segments.bed\",\n", + " bases=[\"A\"],\n", + " threads=4,\n", + ")\n", + "print(pair_result.output_path)\n", + "print(pair_result.segment_path)\n", + "display(pair_result.high_confidence_sites.head())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Multi-sample regional DMR\n", + "\n", + "`modkit dmr multi` requires a region BED and emits all-vs-all pair BED outputs.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "execution_count": null, + "outputs": [], + "source": [ + "multi_result = workflows.modkit_dmr_multi_workflow(\n", + " samples=sample_pileups,\n", + " regions_bed=regions_bed,\n", + " ref_genome=ref_genome,\n", + " out_dir=\"artifacts/dmr/multi\",\n", + " bases=[\"A\"],\n", + " threads=4,\n", + ")\n", + "display(multi_result.pair_files)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/docs/benchmarks/hdf5_openzl_compression_benchmark.py b/docs/benchmarks/hdf5_openzl_compression_benchmark.py new file mode 100644 index 0000000..4008f95 --- /dev/null +++ b/docs/benchmarks/hdf5_openzl_compression_benchmark.py @@ -0,0 +1,334 @@ +#!/usr/bin/env python3 +"""Benchmark HDF5 vs OpenZL for dimelo-toolkit intermediate read data. + +This script is intended as a local/private benchmark harness. It compares the +existing HDF5 read/write path with an optional OpenZL-backed serialization path. +The OpenZL benchmark is only enabled if an importable `openzl` package is available. +""" + +from __future__ import annotations + +import argparse +import gzip +import json +import pickle +import time +from pathlib import Path +from typing import Any + +import h5py +import numpy as np + +from dimelo.load_processed import read_vectors_from_hdf5 + +try: + import openzl +except ImportError: # pragma: no cover + openzl = None + + +DEFAULT_MOTIFS = ["A,0", "CG,0"] + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Benchmark HDF5 and OpenZL compression performance for dimelo intermediate data." + ) + parser.add_argument( + "--output-dir", + type=Path, + default=Path("docs/benchmarks/tmp_bench"), + help="Directory to write benchmark outputs.", + ) + parser.add_argument( + "--benchmark-hdf5", + action="store_true", + help="Run the HDF5 benchmark.", + ) + parser.add_argument( + "--benchmark-openzl", + action="store_true", + help="Run the OpenZL benchmark if available.", + ) + parser.add_argument( + "--use-synthetic-data", + action="store_true", + help="Use synthetic sample data instead of trying to ingest a real sample file.", + ) + parser.add_argument( + "--num-reads", + type=int, + default=200, + help="Number of synthetic reads to generate for the benchmark.", + ) + parser.add_argument( + "--save-results", + action="store_true", + help="Save benchmark results to benchmark_results.json.", + ) + return parser.parse_args() + + +def ensure_directory(path: Path) -> Path: + path.mkdir(parents=True, exist_ok=True) + return path + + +def compress_uint8_vector(data: np.ndarray, compress_level: int = 1) -> bytes: + return gzip.compress(np.asarray(data, dtype=np.uint8).tobytes(), compresslevel=compress_level) + + +def generate_synthetic_data(num_reads: int) -> dict[str, list[Any]]: + data = { + "read_name": [], + "chromosome": [], + "read_start": [], + "read_end": [], + "strand": [], + "motif": [], + "mod_vector": [], + "val_vector": [], + } + start_position = 0 + for read_index in range(num_reads): + read_length = int(40 + 20 * (read_index % 4)) + read_name = f"read_{read_index:06d}" + motif = DEFAULT_MOTIFS[read_index % len(DEFAULT_MOTIFS)] + strand = "+" if (read_index % 2 == 0) else "-" + chromosome = "chr1" + read_start = start_position + read_end = read_start + read_length + start_position += read_length // 2 + + mod_vector = np.zeros(read_length, dtype=np.uint8) + val_vector = np.zeros(read_length, dtype=np.uint8) + positions = np.arange(0, read_length, 5) + mod_vector[positions] = np.random.randint(0, 255, size=len(positions), dtype=np.uint8) + val_vector[positions] = 1 + + data["read_name"].append(read_name) + data["chromosome"].append(chromosome) + data["read_start"].append(read_start) + data["read_end"].append(read_end) + data["strand"].append(strand) + data["motif"].append(motif) + data["mod_vector"].append(compress_uint8_vector(mod_vector, compress_level=1)) + data["val_vector"].append(compress_uint8_vector(val_vector, compress_level=1)) + + return data + + +def write_hdf5_direct( + data: dict[str, list[Any]], + output_h5: Path, + compress_level: int = 1, +) -> dict[str, Any]: + start = time.perf_counter() + with h5py.File(output_h5, "w") as h5: + dt_str = h5py.string_dtype(encoding="utf-8") + dt_vlen = h5py.vlen_dtype(np.dtype("uint8")) + + h5.create_dataset("threshold", data=np.array(np.nan), dtype="f8") + h5.create_dataset( + "read_name", + data=np.asarray(data["read_name"], dtype=dt_str), + dtype=dt_str, + compression="gzip", + compression_opts=9, + ) + h5.create_dataset( + "chromosome", + data=np.asarray(data["chromosome"], dtype=dt_str), + dtype=dt_str, + compression="gzip", + compression_opts=9, + ) + h5.create_dataset( + "read_start", + data=np.asarray(data["read_start"], dtype="i"), + dtype="i", + compression="gzip", + compression_opts=9, + ) + h5.create_dataset( + "read_end", + data=np.asarray(data["read_end"], dtype="i"), + dtype="i", + compression="gzip", + compression_opts=9, + ) + h5.create_dataset( + "strand", + data=np.asarray(data["strand"], dtype=dt_str), + dtype=dt_str, + compression="gzip", + compression_opts=9, + ) + h5.create_dataset( + "motif", + data=np.asarray(data["motif"], dtype=dt_str), + dtype=dt_str, + compression="gzip", + compression_opts=9, + ) + h5.create_dataset( + "mod_vector", + shape=(len(data["mod_vector"]),), + maxshape=(None,), + dtype=dt_vlen, + data=None, + ) + h5["mod_vector"][:] = [np.frombuffer(b, dtype=np.uint8) for b in data["mod_vector"]] + h5.create_dataset( + "val_vector", + shape=(len(data["val_vector"]),), + maxshape=(None,), + dtype=dt_vlen, + data=None, + ) + h5["val_vector"][:] = [np.frombuffer(b, dtype=np.uint8) for b in data["val_vector"]] + end = time.perf_counter() + return { + "write_time": end - start, + "file_size": output_h5.stat().st_size, + "path": str(output_h5), + } + + +def read_hdf5_via_dimelo(output_h5: Path, motifs: list[str]) -> dict[str, Any]: + start = time.perf_counter() + records, datasets, _ = read_vectors_from_hdf5( + file=output_h5, + motifs=motifs, + regions=None, + calculate_mod_fractions=False, + quiet=True, + ) + elapsed = time.perf_counter() - start + return { + "read_time": elapsed, + "records": len(records), + "datasets": datasets, + } + + +def openzl_available() -> bool: + return openzl is not None + + +def compress_with_openzl(payload: bytes) -> bytes: + if not openzl_available(): + raise RuntimeError("OpenZL is not installed") + if hasattr(openzl, "compress"): + return openzl.compress(payload) + if hasattr(openzl, "compress_bytes"): + return openzl.compress_bytes(payload) + raise RuntimeError("Unsupported OpenZL API: no compress() or compress_bytes()") + + +def decompress_with_openzl(payload: bytes) -> bytes: + if not openzl_available(): + raise RuntimeError("OpenZL is not installed") + if hasattr(openzl, "decompress"): + return openzl.decompress(payload) + if hasattr(openzl, "decompress_bytes"): + return openzl.decompress_bytes(payload) + raise RuntimeError("Unsupported OpenZL API: no decompress() or decompress_bytes()") + + +def write_openzl_standalone( + data: dict[str, list[Any]], output_file: Path +) -> dict[str, Any]: + if not openzl_available(): + return { + "status": "skipped", + "error": "OpenZL package not installed", + } + payload = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL) + start = time.perf_counter() + compressed = compress_with_openzl(payload) + output_file.write_bytes(compressed) + elapsed = time.perf_counter() - start + return { + "write_time": elapsed, + "file_size": output_file.stat().st_size, + "path": str(output_file), + } + + +def read_openzl_standalone(output_file: Path) -> dict[str, Any]: + if not openzl_available(): + return { + "status": "skipped", + "error": "OpenZL package not installed", + } + payload = output_file.read_bytes() + start = time.perf_counter() + decompressed = decompress_with_openzl(payload) + data = pickle.loads(decompressed) + elapsed = time.perf_counter() - start + return { + "read_time": elapsed, + "record_count": len(data.get("read_name", [])), + "dataset_keys": sorted(data.keys()), + } + + +def compare_results(results: list[dict[str, Any]], output_dir: Path) -> None: + print("\nBenchmark results") + print("-----------------") + for result in results: + print(json.dumps(result, indent=2, sort_keys=True)) + results_path = output_dir / "benchmark_results.json" + with results_path.open("w", encoding="utf-8") as handle: + json.dump(results, handle, indent=2) + print(f"Saved results to: {results_path}") + + +def main() -> None: + args = parse_args() + output_dir = ensure_directory(args.output_dir) + data = generate_synthetic_data(args.num_reads) + + results: list[dict[str, Any]] = [] + if args.benchmark_hdf5: + hdf5_path = output_dir / "benchmark_data.h5" + write_metrics = write_hdf5_direct(data, hdf5_path, compress_level=1) + read_metrics = read_hdf5_via_dimelo(hdf5_path, motifs=DEFAULT_MOTIFS) + results.append( + { + "format": "HDF5+gzip", + **write_metrics, + **read_metrics, + "status": "success", + } + ) + + if args.benchmark_openzl: + openzl_path = output_dir / "benchmark_data.openzl" + write_metrics = write_openzl_standalone(data, openzl_path) + read_metrics: dict[str, Any] = {} + if write_metrics.get("status") != "skipped": + read_metrics = read_openzl_standalone(openzl_path) + results.append( + { + "format": "OpenZL standalone", + **write_metrics, + **read_metrics, + } + ) + + if not results: + print("No benchmark selected. Use --benchmark-hdf5, --benchmark-openzl, or both.") + return + + if args.save_results: + compare_results(results, output_dir) + else: + print("\nResults summary") + for result in results: + print(f"- {result['format']}: {result.get('write_time', 'n/a'):.4f}s write, {result.get('read_time', 'n/a'):.4f}s read, {result.get('file_size', 'n/a')} bytes") + + +if __name__ == "__main__": + main() diff --git a/docs/global-analysis.md b/docs/global-analysis.md new file mode 100644 index 0000000..756dcb2 --- /dev/null +++ b/docs/global-analysis.md @@ -0,0 +1,75 @@ +# Global Analysis + +`dimelo.global_analysis` provides pileup-backed whole-sample summaries and broad tiled-window summaries on top of `parse_bam.pileup()` outputs. + +## What It Covers + +- global motif fractions per sample and condition +- tiled window summaries across genome-size inputs +- reusable normalization factors derived from the global fractions + +## Example + +```python +from dimelo import global_analysis +from dimelo.models import SampleSpec + +result = global_analysis.run_global_analysis( + samples=[ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="output/s1.extract.h5", + metadata={"pileup_path": "output/s1.pileup.sorted.bed.gz"}, + ) + ], + motifs=["A,0"], + genome_sizes={"chr1": 248956422}, + window_size=100000, + step_size=50000, +) +``` + +## Canonical Outputs + +`run_global_analysis(...)` returns a `GlobalAnalysisResult` with data-first tables: + +- `result.summary` +- `result.windows` +- `result.normalization_factors` +- `result.plot_data["global_fraction_bar"]` +- `result.plot_data["window_fraction_table"]` + +The wrapper does not couple the workflow to a plotting backend. If you want to render the results yourself, use the returned tables directly. + +## Plotting Prep Helpers + +Use the shared plotting helpers when you want plot-ready global-analysis tables without committing to a specific renderer: + +```python +from dimelo import plotting + +summary_payload = plotting.prepare_global_analysis_summary_data( + result=result, + aggregate_conditions=True, +) + +window_payload = plotting.prepare_global_analysis_window_data( + result=result, + aggregate_conditions=True, +) +``` + +If you want a built-in Matplotlib figure after preparing the payload, pass it through `dimelo.plotting_matplotlib`: + +```python +from dimelo import plotting_matplotlib + +fig, ax = plotting_matplotlib.plot_global_analysis_summary_matplotlib(summary_payload) +plotting_matplotlib.save_figure(fig, "global-analysis-summary.png") +``` + +- `prepare_global_analysis_summary_data(...)` consumes a `GlobalAnalysisResult` and returns renderer-neutral `sample_summary`, `condition_summary`, `normalization_table`, and `metadata` payloads. The summary payload exposes both sample-level rows and optional condition-level views, while normalization values stay available through `normalization_table`. +- `prepare_global_analysis_window_data(...)` consumes the same `GlobalAnalysisResult` and returns renderer-neutral `window_table`, `condition_window_table`, and `metadata` payloads. Broad-window payloads stay per-contig by default rather than flattening onto a cumulative genome axis. + +For defined-region follow-up, pair these global summaries with [region contrasts](region-contrasts.md). For de novo locus finding first, use [region discovery](region-discovery.md). diff --git a/docs/modkit-dmr.md b/docs/modkit-dmr.md new file mode 100644 index 0000000..760d9bd --- /dev/null +++ b/docs/modkit-dmr.md @@ -0,0 +1,58 @@ +# modkit DMR Workflows + +`dimelo` now exposes native wrappers for `modkit dmr` so region discovery and differential methylation can be driven directly by ONT's maintained DMR implementation. + +## Pairwise DMR (+ optional HMM segmentation) + +Use `workflows.modkit_dmr_pair_workflow(...)` (or `dimelo.dmr.run_dmr_pair(...)`) for two-sample comparison. + +- Site-level output includes `map_pvalue` and `effect_size` columns from `modkit dmr pair`. +- Optional HMM segmentation is enabled by passing `segment_path=...`. +- `regions_bed` and `segment_path` are mutually exclusive in a single run because `modkit dmr pair` enforces that constraint. + +Example: + +```python +from dimelo import workflows + +result = workflows.modkit_dmr_pair_workflow( + control_bed_methyl="sample_a.pileup.sorted.bed.gz", + experiment_bed_methyl="sample_b.pileup.sorted.bed.gz", + ref_genome="ref.fa", + out_path="dmr_sites.bed", + segment_path="dmr_segments.bed", + bases=["A"], + threads=8, +) + +print(result.sites.head()) +print(result.segments.head()) +print(result.high_confidence_sites.head()) +``` + +## Multi-sample regional DMR + +Use `workflows.modkit_dmr_multi_workflow(...)` when comparing all sample pairs over a fixed region BED. + +Example: + +```python +multi = workflows.modkit_dmr_multi_workflow( + samples={ + "s1": "s1.pileup.sorted.bed.gz", + "s2": "s2.pileup.sorted.bed.gz", + "s3": "s3.pileup.sorted.bed.gz", + }, + regions_bed="regions.bed", + ref_genome="ref.fa", + out_dir="dmr_multi", + bases=["A"], + threads=8, +) + +print(multi.pair_files) +``` + +If you already use `SampleSpec`, you can route through: + +`workflows.modkit_dmr_multi_from_samples_workflow(...)` (reads `sample.metadata["pileup_path"]`). diff --git a/docs/region-contrasts.md b/docs/region-contrasts.md new file mode 100644 index 0000000..ab0789a --- /dev/null +++ b/docs/region-contrasts.md @@ -0,0 +1,299 @@ +# Region Contrasts + +`dimelo.region_contrasts` scores known regions from pileup-backed inputs or clustering-derived occupancy tables. It is the defined-region comparison layer for cases where you already know the loci you want to test. + +## When To Use It + +- Use this when you already have a BED or matched region set. +- Use `parse_bam.pileup()` first when you want locus-level motif abundance testing. +- Use `cluster` first when you want read-state or cluster-occupancy follow-up rather than average motif abundance alone. +- Use `region_discovery` when you do not yet know the loci and want de novo locus finding before follow-up contrasts. + +## V1 Supported Paths + +- `analysis_unit="ensemble_region"` +- `representation="modified_fraction"` or `"modified_count"` +- `signal_source="pileup_counts"` +- `test="effect_size_only"` or `"beta_binomial"` + +- `analysis_unit="cluster_occupancy"` +- `signal_source="cluster_occupancy"` +- `representation="cluster_fraction"` with `test="effect_size_only"` or `"fraction_test"` +- `representation="dominant_cluster"` or `"cluster_entropy"` with `test="effect_size_only"` + +Current v1 behavior: + +- `effect_size_only` supports pooled `pairwise` and `group_vs_group` comparisons. +- `beta_binomial` supports the same pooled comparison modes and adds `p_value` / `adjusted_p_value`. +- `multiple_testing="fdr_bh"` is the only supported correction mode for `beta_binomial`. +- The current beta-binomial path is intentionally simple: pooled region counts with a first-pass per-region predictive test, not replicate-aware hierarchical modeling. +- `cluster_occupancy` currently supports `pairwise` and `group_vs_group` only. +- `fraction_test` is only implemented for `representation="cluster_fraction"` and uses sample-level region occupancy fractions rather than pooled reads. +- `dominant_cluster` and `cluster_entropy` are descriptive in v1. They return ranked summaries without p-values. +- Zero-read sample-region groups keep `fraction=0.0`, `cluster_entropy=0.0`, and `dominant_cluster=None` rather than inventing a winning cluster. + +## Example + +```python +from dimelo import region_contrasts +from dimelo.models import ContrastSpec, SampleSpec + +result = region_contrasts.score_regions( + samples=[ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="output/s1.extract.h5", + metadata={"pileup_path": "output/s1.pileup.sorted.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="15min", + extract_h5="output/s2.extract.h5", + metadata={"pileup_path": "output/s2.pileup.sorted.bed.gz"}, + ), + ], + regions="matched_regions.bed", + motifs=["A,0"], + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + analysis_unit="ensemble_region", + representation="modified_fraction", + signal_source="pileup_counts", + test="beta_binomial", +) +``` + +## Cluster Occupancy Example + +Use this path after shared clustering when you want to test whether per-region read-state mixtures shift between conditions. + +```python +from dimelo import region_contrasts, workflows +from dimelo.models import ContrastSpec + +cluster_result = workflows.shared_cluster_distribution( + samples=samples, + mode="region_anchored", + motifs=["A,0"], + matched_regions="matched_regions.bed", + n_clusters=6, +) + +occupancy_result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="fraction_test", + occupancy_table=cluster_result.region_summaries, +) +``` + +## Single-Read Contrasts + +`region_contrasts` now also supports `analysis_unit="single_read"` for +defined-region comparison on extract-backed read data. + +First-version representations: + +- `representation="read_mod_fraction"` with `signal_source="extract_reads"` +- `representation="read_window_features"` with `signal_source="extract_features"` + +Supported contrast modes in v1: + +- `pairwise` +- `group_vs_group` +- `matched_pairwise` + +Important: reads remain observational units, but samples remain the inferential +units. The current single-read paths summarize reads within each `region x sample` +before comparing conditions, and `matched_pairwise` keeps that same boundary while +requiring explicit, non-null pairing metadata and complete pairs only. + +## Canonical Outputs + +The workflow returns a `RegionContrastResult` with canonical tables: + +- `result.regions` +- `result.summary` +- `result.plot_data` + +For `effect_size_only`, the main effect-size columns are: + +- `fraction` +- `reference_fraction` +- `delta_fraction` +- `log2_fc` +- `rank` + +For `representation="modified_count"`, count-based fields are also included: + +- `count` +- `reference_count` +- `delta_count` +- `log2_fc_count` + +For `beta_binomial`, statistical columns are included in both `result.regions` and `result.summary`: + +- `p_value` +- `adjusted_p_value` + +For `analysis_unit="cluster_occupancy"`, the main fields depend on representation: + +- `cluster_fraction` + - `cluster` + - `fraction` + - `reference_fraction` + - `delta_fraction` + - `log2_fc` + - `numerator_replicate_n` + - `denominator_replicate_n` + - optional `p_value` / `adjusted_p_value` for `fraction_test` +- `dominant_cluster` + - `dominant_cluster` + - `reference_dominant_cluster` + - `dominant_cluster_changed` + - `numerator_replicate_n` + - `denominator_replicate_n` +- `cluster_entropy` + - `cluster_entropy` + - `reference_cluster_entropy` + - `delta_cluster_entropy` + - `numerator_replicate_n` + - `denominator_replicate_n` + +## Custom Plotting + +The results are data-first. You can use the built-in `plot_data` payloads or ignore them and plot the returned tables directly. + +When you move from legacy plotters into the newer plotting-axis layer, the intended mapping is: + +- `regions_5to3prime=True` -> `orientation="region_5to3"` +- `relative=True` -> fixed-window plotting around a shared anchor +- single-read views remain coordinate-preserving +- normalized segment maps are reserved for aggregate plots rather than stretched single-read canvases + +```python +import seaborn as sns + +sns.scatterplot( + data=result.summary, + x="delta_fraction", + y="adjusted_p_value", +) +``` + +For region-contrast-specific plots, use the new plotting helpers with a `RegionContrastResult`, an explicit `position_table`, and the shared axis/aggregation spec: + +```python +from dimelo import plotting + +profile_payload = plotting.prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=2000, + downstream_bp=2000, + ), + aggregation=plotting.AggregationSpec(), +) + +heatmap_payload = plotting.prepare_region_contrast_heatmap_data( + result=result, + position_table=position_table, + axis=plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=2000, + downstream_bp=2000, + ), + aggregation=plotting.AggregationSpec(), +) +``` + +If you want a built-in Matplotlib figure after preparing the payload, pass it through `dimelo.plotting_matplotlib`: + +```python +from dimelo import plotting_matplotlib + +fig, ax = plotting_matplotlib.plot_region_contrast_profile_matplotlib( + profile_payload, + value_mode="delta", +) +plotting_matplotlib.save_figure(fig, "region-contrast-profile.png") +``` + +`position_table` must already be an aggregated positional substrate from the parsing/loading layer, not raw per-read events. The current helper contract expects at minimum: + +- `region_id` +- `position` +- `anchor` +- `value` +- `region_strand` +- either `condition` or `sample_id` + +For `prepare_region_contrast_heatmap_data(...)`, `result.summary` must also provide one unambiguous `rank` value per plotted region. + +For de novo locus finding before this stage, use [region discovery](region-discovery.md). For broad whole-sample summaries and normalization factors, use [global analysis](global-analysis.md). + +## Preprocessing Reminder + +- Run `parse_bam.pileup()` when you care about motif abundance, defined-region contrasts, or later de novo discovery. +- Run `parse_bam.extract()` when you care about single-read analysis or clustering. +- Run both when you want formal region-level abundance testing plus downstream read-level follow-up on the same samples. +- Run clustering first when you want occupancy-backed contrasts; then pass `SharedClusterResult.region_summaries` as `occupancy_table`. + +## Discovery-Driven Follow-Up + +When discovery, clustering, and contrasts are chained together, the combined workflow uses the selected loci as the default follow-up contract: + +```python +from dimelo import workflows + +result = workflows.discovery_cluster_contrast_workflow( + samples=samples, + motifs=["A,0"], + genome_sizes={"chr1": 248956422}, + discovery={ + "window_size": 2000, + "step_size": 500, + "score": "beta_binomial", + "contrast": discovery_contrast, + }, + clustering={ + "mode": "region_anchored", + "n_clusters": 6, + }, + contrasts={ + "contrast": region_contrast, + "test": "beta_binomial", + }, + selection={"mode": "top_n", "top_n": 250}, +) +``` + +The data contract is: + +- `result.selected_regions` is the BED-style follow-up set +- clustering receives a serializable region-spec derived from those rows +- clustering-side `result.clustering.assignments["region_id"]` and `result.clustering.region_summaries["region_id"]` are normalized to the same `chr:start-end,strand` key used by `result.contrasts` +- `result.contrasts` scores the same selected loci by default +- pass `contrasts={"regions": ...}` to override that default and set `result.metadata["contrast_scope"] == "custom"` instead +- `result.metadata["full_scan_windows"]` keeps the full discovery table as context diff --git a/docs/region-discovery.md b/docs/region-discovery.md new file mode 100644 index 0000000..c15f84f --- /dev/null +++ b/docs/region-discovery.md @@ -0,0 +1,195 @@ +# Region Discovery + +`dimelo.region_discovery` is the de novo locus discovery layer. Use it when you do not yet know which genomic intervals matter and you want a deterministic tiled scan over pileup-backed inputs. It also supports paired discovery when you have matched samples or ordered time courses and want to keep the output in the same data-first shape as pooled discovery. + +## When To Use Discovery vs Contrasts + +- Use `region_discovery` when you need to find candidate loci first. +- Use `region_contrasts` when you already know the regions and want to test them formally. +- If you already have a BED file or a matched region set, skip discovery and go straight to `region_contrasts`. + +## Paired Discovery + +Paired discovery is still discovery-first. The scan resolves complete matched units from `SampleSpec.metadata[pairing_key]`, filters out incomplete pairs, and returns the same `RegionDiscoveryResult` container as pooled discovery. + +- Use `ContrastSpec(mode="matched_pairwise", numerator=[...], denominator=[...], pairing_key="...")` when you have matched two-condition samples. +- Use `ContrastSpec(mode="time_course", time_order=[...], pairing_key="...")` when you want an ordered trajectory across paired time points. +- Inspect `result.windows` for the full tiled table and `result.hits` for ranked discovery hits. +- Use `result.metadata["pairing_key"]`, `result.metadata["pairing_policy"]`, `result.metadata["n_pairs_used"]`, and `result.metadata["n_pairs_dropped"]` to audit the pairing resolution that was applied. +- `result.plot_data` stays optional; the canonical handoff artifact is still the table data. + +## Minimal Example + +```python +from dimelo import region_discovery +from dimelo.models import ContrastSpec, SampleSpec + +result = region_discovery.scan_genome( + samples=[ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="output/s1.extract.h5", + metadata={"pileup_path": "output/s1.pileup.sorted.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="treated", + extract_h5="output/s2.extract.h5", + metadata={"pileup_path": "output/s2.pileup.sorted.bed.gz"}, + ), + ], + motifs=["A,0"], + genome_sizes={"chr1": 248956422}, + window_size=2000, + step_size=500, + contrast=ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + reference_condition="NS", + ), + score="beta_binomial", + min_coverage=10, +) +``` + +The canonical outputs are data-first: + +- `result.windows` +- `result.hits` +- `result.plot_data["window_score_table"]` +- `result.plot_data["top_hits_table"]` + +As with the newer clustering and contrast workflows, these plotting payloads are renderer-neutral tables first. Legacy orientation flags such as `regions_5to3prime` map onto the shared region-alignment model for downstream follow-up plots, while any scaled segment normalization is kept to aggregate views rather than single-read displays. + +## Plotting Prep Helpers + +Use the shared plotting helpers when you want plot-ready discovery tables without committing to a specific renderer: + +```python +from dimelo import plotting + +scan_payload = plotting.prepare_region_discovery_scan_data( + result=result, + top_n_hits=50, +) + +context_payload = plotting.prepare_region_discovery_hit_context_data( + result=result, + top_n=12, + padding_windows=5, +) +``` + +If you want a built-in Matplotlib figure after preparing the payload, pass it through `dimelo.plotting_matplotlib`: + +```python +from dimelo import plotting_matplotlib + +fig, axes = plotting_matplotlib.plot_region_discovery_scan_matplotlib(scan_payload) +plotting_matplotlib.save_figure(fig, "region-discovery-scan.png") +``` + +- `prepare_region_discovery_scan_data(...)` consumes a `RegionDiscoveryResult` and returns renderer-neutral `scan_table`, `hit_table`, and `metadata` payloads. Scan payloads stay per-contig by default rather than projecting windows onto a cumulative genome axis. +- `prepare_region_discovery_hit_context_data(...)` consumes the same `RegionDiscoveryResult` and returns renderer-neutral `context_table`, `selected_hits`, and `metadata` payloads for small-multiple or local hit-context views. + +For broad whole-sample summaries instead of locus discovery, use [global analysis](global-analysis.md). For defined-region testing after discovery, use [region contrasts](region-contrasts.md). + +## Handoff Guidance + +- For formal region testing, convert discovered hits into BED and write them to disk before passing the BED path into `region_contrasts.score_regions(...)`. +- This handoff works the same for paired discovery outputs; the paired score columns stay in `result.hits`, while `hits_to_bed()` projects the ranked loci into a plain BED table for downstream region testing. +- For downstream clustering, the recommended next step is read-window clustering on the discovered loci, followed by region-level occupancy follow-up from the resulting read-cluster labels. +- If you specifically want shape-based exploratory analysis, you can still pass the discovered loci into the optional `mode="region_anchored"` workflow. +- Keep discovery and contrast roles separate: discovery finds candidates, contrasts tests known regions, and clustering explains state mixtures. + +The combined discovery-to-clustering workflow uses the same handoff, but keeps it in memory. It is most useful when you explicitly want the optional region-anchored exploratory path on discovery hits: + +- discovery produces ranked hit rows +- `hits_to_bed()` turns those rows into BED-style `selected_regions` +- `discovery_cluster_workflow()` derives a serializable region-spec from those rows and passes it to `shared_cluster_distribution(..., matched_regions=...)` +- the workflow returns the original discovery result, the BED-style `selected_regions` table, and the clustering result together + +```python +from dimelo import workflows + +result = workflows.discovery_cluster_workflow( + samples=samples, + motifs=["A,0"], + genome_sizes={"chr1": 248956422}, + discovery={ + "window_size": 2000, + "step_size": 500, + "score": "beta_binomial", + "contrast": contrast, + }, + clustering={"mode": "region_anchored", "n_clusters": 6}, + selection={"mode": "top_n", "top_n": 250}, +) +``` + +When you want discovery, clustering, and defined-region contrasts in one pass, use `workflows.discovery_cluster_contrast_workflow()`: + +```python +result = workflows.discovery_cluster_contrast_workflow( + samples=samples, + motifs=["A,0"], + genome_sizes={"chr1": 248956422}, + discovery={ + "window_size": 2000, + "step_size": 500, + "score": "beta_binomial", + "contrast": discovery_contrast, + }, + clustering={ + "mode": "region_anchored", + "n_clusters": 6, + }, + contrasts={ + "contrast": region_contrast, + "test": "beta_binomial", + }, + selection={"mode": "top_n", "top_n": 250}, +) + +selected = result.selected_regions +clustered = result.clustering.assignments +scored = result.contrasts.regions +windows = result.metadata["full_scan_windows"] +``` + +The contract is intentionally simple: + +- `result.selected_regions` is the BED-style selected follow-up set +- clustering receives a serializable region-spec derived from those rows +- clustering-side `region_id` values in the combined result are normalized to the same `chr:start-end,strand` key used by contrasts +- `result.contrasts` scores the same selected loci by default, or `contrasts["regions"]` if you provide an explicit override +- `result.metadata["full_scan_windows"]` carries the full discovery scan for context + +```python +bed_df = region_discovery.hits_to_bed(result.discovery.hits) +bed_df.to_csv("discovered_hits.bed", sep="\t", header=False, index=False) +``` + +Paired discovery example: + +```python +paired_result = region_discovery.scan_genome( + samples=paired_samples, + motifs=["A,0"], + genome_sizes=genome_sizes, + window_size=2000, + step_size=500, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", +) + +paired_bed = region_discovery.hits_to_bed(paired_result.hits) +paired_bed.to_csv("paired_discovered_hits.bed", sep="\t", header=False, index=False) +``` diff --git a/docs/roadmap-2026-04-wrapup.md b/docs/roadmap-2026-04-wrapup.md new file mode 100644 index 0000000..79ae3df --- /dev/null +++ b/docs/roadmap-2026-04-wrapup.md @@ -0,0 +1,85 @@ +# Dimelo Toolkit Wrap-up And Roadmap (April 2026) + +## What Was Completed + +### 1) Native `modkit dmr` integration +- Added `dimelo.dmr` with native wrappers for: + - pairwise DMR (`run_dmr_pair`) + - multi-sample DMR (`run_dmr_multi`) +- Added support for: + - optional HMM segmentation output in pair mode + - high-confidence site filtering from pair outputs + - direct pass-through of core `modkit dmr` controls (threads, priors, segmentation knobs, coverage constraints, etc.) + +### 2) Workflow-level API integration +- Added new workflow entry points: + - `workflows.modkit_dmr_pair_workflow` + - `workflows.modkit_dmr_multi_workflow` + - `workflows.modkit_dmr_multi_from_samples_workflow` (uses `SampleSpec.metadata["pileup_path"]`) + +### 3) New result models +- Added: + - `ModkitDMRPairResult` + - `ModkitDMRMultiResult` +- Exported via package root. + +### 4) Parse reliability fixes for mixed modkit versions in notebooks +- Hardened `run_modkit` capability caching to invalidate when executable file changes in-place. +- Hardened `parse_bam` pileup targeting so modkit 0.6+ always builds `--modified-bases` path even if stale capability flags are encountered. +- Added/updated regression tests for both. + +### 5) Notebook strategy updates +- Kept `dimelo_test.ipynb` focused on single-sample analysis. +- Added `dmr_multi_sample.ipynb` as a separate multi-sample DMR template notebook. +- Added notebook-side modkit cache reset/reload and executable pinning patterns to reduce kernel-state drift. + +### 6) Documentation updates +- Added `docs/modkit-dmr.md`. +- Updated README analysis guide references and DMR workflow notes. + +### 7) Test coverage added +- New/updated tests across: + - `tests/test_dmr.py` + - `tests/test_workflows.py` + - `tests/test_models.py` + - `tests/test_parse_bam.py` + - `tests/test_run_modkit.py` + + +## Known Current Constraints + +1. `modkit dmr pair` does not allow `--regions-bed` together with `--segment` in a single invocation. +2. Historical bedMethyl files generated by older modkit output schemas may fail modern `modkit dmr` validation. +3. Some demo BAMs still trigger early motif-warning messages (first-100-read motif scan), even when downstream processing succeeds. + + +## Remaining Work + +### High priority +1. Replace exploratory region-discovery notebook paths with a `modkit dmr`-first multi-sample tutorial using real replicate inputs. +2. Add plotting/prep helpers for DMR outputs (site and segment summaries) in `plotting`/`plotting_matplotlib`. +3. Add end-to-end integration tests that run parse -> pileup -> dmr workflow in a deterministic small fixture. + +### Medium priority +1. Add a convenience utility that auto-runs both pair modes when user requests both region-level + segmentation results. +2. Add optional schema normalization for legacy bedMethyl data prior to DMR calls. +3. Expand docs with recommended DMR parameter presets for: + - high-sensitivity segmentation + - high-specificity site calling + - sparse-coverage datasets + +### Lower priority +1. Add richer metadata/provenance outputs to DMR results (input hashes, command runtime, elapsed time). +2. Add optional artifact registration for DMR outputs in workflow provenance tables. + + +## Next Session Quick-start Checklist + +1. Activate the environment and confirm modkit: + - `modkit --version` +2. In notebooks, run cache-reset/reload cell before parse calls if modkit changed. +3. Use `dimelo_test.ipynb` only for single-sample parsing/clustering examples. +4. Use `dmr_multi_sample.ipynb` for multi-sample DMR. +5. Continue roadmap from: + - DMR plotting layer implementation + - full multi-sample tutorial with real sample set diff --git a/docs/shared-clustering.md b/docs/shared-clustering.md new file mode 100644 index 0000000..e6a3bb0 --- /dev/null +++ b/docs/shared-clustering.md @@ -0,0 +1,269 @@ +# Shared Clustering + +`dimelo.workflows.shared_cluster_distribution()` provides a higher-level shared-boundary workflow on top of the existing parsing layer. It does not replace `parse_bam.extract()` or `parse_bam.pileup()`; it consumes those outputs and returns canonical tables that you can plot with Matplotlib, seaborn, Plotly, Altair, or your own stack. + +## Preprocessing Choices + +- Recommended default: run `parse_bam.extract()`, cluster read windows, then summarize the resulting read-cluster labels at the region level with `SharedClusterResult.region_summaries` and `region_contrasts.score_regions(..., analysis_unit="cluster_occupancy")`. +- Run `parse_bam.extract()` when you want read-level clustering or single-read pattern analysis. +- Run `parse_bam.pileup()` when you specifically want matched-region or shape-based exploratory clustering from pileup-derived summaries. +- Run both when you want region-level summaries plus read-level follow-up on the same samples. + +## Workflow Entry Point + +### Read-global clustering + +```python +from dimelo import workflows +from dimelo.models import SampleSpec + +result = workflows.shared_cluster_distribution( + samples=[ + SampleSpec(sample_id="s1", condition="NS", extract_h5="output/s1.extract.h5"), + SampleSpec(sample_id="s2", condition="15min", extract_h5="output/s2.extract.h5"), + ], + mode="read_global", + motifs=["A,0"], + n_clusters=8, +) +``` + +### Optional region-anchored clustering + +Use this when you want region-aligned exploratory clustering on pileup-derived summaries rather than the recommended read-window-first workflow. + +```python +from dimelo import workflows +from dimelo.models import SampleSpec + +result = workflows.shared_cluster_distribution( + samples=[ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="output/s1.extract.h5", + regions_bed="controls/s1_controls.bed", + metadata={"pileup_path": "output/s1.pileup.sorted.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="15min", + extract_h5="output/s2.extract.h5", + regions_bed="controls/s2_controls.bed", + metadata={"pileup_path": "output/s2.pileup.sorted.bed.gz"}, + ), + ], + mode="region_anchored", + motifs=["A,0"], + matched_regions="matched_regions.bed", + n_clusters=6, +) +``` + +`shared_cluster_distribution()` expects `matched_regions` to already be a serializable region spec. In the discovery-driven flow, `discovery_cluster_workflow()` is the caller that derives that spec from BED-style discovery rows and forwards it into this workflow. That keeps discovery output human-readable while still feeding clustering the compact region representation it needs. + +`discovery_cluster_contrast_workflow()` extends the same handoff one step further: it derives the serializable region spec from the BED-style discovery rows, passes that into clustering, normalizes clustering-side `region_id` values to the same `chr:start-end,strand` format used by contrasts, and uses the same selected loci as the default follow-up region set for contrasts unless `contrasts["regions"]` overrides it. + +For defined-region occupancy follow-up, pass `result.region_summaries` into `region_contrasts.score_regions(..., analysis_unit="cluster_occupancy", signal_source="cluster_occupancy", occupancy_table=...)`. That keeps the clustering output as the reusable source of truth for per-region read-state mixtures. + +## Canonical Outputs + +The workflow returns a `SharedClusterResult` with canonical tables: + +- `result.assignments` +- `result.cluster_distribution` +- `result.condition_distribution` +- `result.cluster_profiles` +- `result.region_summaries` for `region_anchored` +- `result.plot_data` + +`result.region_summaries` is also the standard occupancy handoff into `dimelo.region_contrasts` when you want per-region `cluster_fraction`, `dominant_cluster`, or `cluster_entropy` follow-up instead of only global cluster distributions. + +Shared clustering now follows the same layered plotting structure as the other newer analysis families: + +- canonical tables on the result object +- renderer-neutral prep helpers in `dimelo.plotting` +- optional built-in Matplotlib renderers in `dimelo.plotting_matplotlib` + +## Plotting Prep Helpers + +`SharedClusterResult` now has three renderer-neutral plotting helpers in `dimelo.plotting`: + +- `prepare_shared_cluster_distribution_data(result=...)` +- `prepare_shared_cluster_profile_data(result=...)` +- `prepare_shared_cluster_region_data(result=...)` + +These helpers sit on top of the canonical result tables: + +- `result.cluster_distribution` +- `result.condition_distribution` +- `result.distribution_change` +- `result.cluster_profiles` +- `result.region_summaries` + +The older lightweight payloads in `result.plot_data["cluster_distribution_bar"]` and +`result.plot_data["cluster_distribution_heatmap"]` remain supported for backward familiarity. + +The plotting contract stays compatibility-safe with older package versions: + +- legacy Matplotlib wrappers still work +- tables in `result.plot_data` are the stable plotting substrate +- `regions_5to3prime` now maps onto the shared `orientation="region_5to3"` axis concept for region-aligned plots +- fixed-window plotting is available for both aggregate and single-read views +- scaled or segmented metaregion axes are aggregate-only and are not used for single-read plots + +## Built-In Matplotlib Renderers + +`dimelo.plotting_matplotlib` also provides an optional built-in renderer layer on +top of these prepared payloads. This is a convenience layer, not a replacement +for the stable tables in `result.plot_data` or the renderer-neutral prep helpers. + +Cluster distribution example: + +```python +from dimelo import plotting, plotting_matplotlib + +distribution_payload = plotting.prepare_shared_cluster_distribution_data(result=result) +fig, ax = plotting_matplotlib.plot_shared_cluster_distribution_matplotlib( + distribution_payload, + level="sample", +) +plotting_matplotlib.save_figure(fig, "shared-cluster-distribution.png") +``` + +Condition-level cluster-change heatmap: + +```python +fig, ax = plotting_matplotlib.plot_shared_cluster_change_matplotlib( + distribution_payload, +) +``` + +Cluster profile heatmap example: + +```python +profile_payload = plotting.prepare_shared_cluster_profile_data(result=result) +fig, ax = plotting_matplotlib.plot_shared_cluster_profile_heatmap_matplotlib(profile_payload) +``` + +Cluster profile series example: + +```python +fig, ax = plotting_matplotlib.plot_shared_cluster_profile_series_matplotlib(profile_payload) +``` + +Region occupancy example: + +```python +region_payload = plotting.prepare_shared_cluster_region_data(result=result) +fig, ax = plotting_matplotlib.plot_shared_cluster_region_matplotlib( + region_payload, + level="condition", +) +``` + +The default region occupancy view is condition-level because it is usually the +main biological summary. Sample-level occupancy remains available when you want +replicate inspection or QC: + +```python +fig, ax = plotting_matplotlib.plot_shared_cluster_region_matplotlib( + region_payload, + level="sample", +) +``` + +The older `result.plot_data["cluster_distribution_bar"]` and +`result.plot_data["cluster_distribution_heatmap"]` payloads remain supported for +users updating from earlier package versions. + +## Custom Plotting + +```python +import seaborn as sns + +bar_data = result.plot_data["cluster_distribution_bar"] +sns.barplot(data=bar_data, x="sample_id", y="fraction", hue="cluster") +``` + +For heatmaps: + +```python +heatmap = result.plot_data["cluster_distribution_heatmap"].set_index("condition") +``` + +## Global Composition Inference + +Shared clustering now includes a dedicated inference helper for global +composition changes: + +```python +from dimelo import shared_cluster_tests +from dimelo.models import ContrastSpec + +contrast_result = shared_cluster_tests.shared_cluster_tests( + result=result, + contrast=ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + ), + test="permutation", +) +``` + +Matched pairwise example: + +```python +contrast_result = shared_cluster_tests.shared_cluster_tests( + result=result, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["treated"], + denominator=["NS"], + pairing_key="subject_id", + ), + test="permutation", +) +``` + +Time-course example: + +```python +contrast_result = shared_cluster_tests.shared_cluster_tests( + result=result, + contrast=ContrastSpec(mode="time_course", time_order=["t0", "t1", "t2"]), + test="permutation", + include_pairwise=True, +) +``` + +Pooled screening example: + +```python +screen_result = shared_cluster_tests.shared_cluster_tests( + result=result, + contrast=ContrastSpec(mode="pairwise", numerator=["treated"], denominator=["NS"]), + test="chi_squared", +) +``` + +Inference boundary: + +- `shared_cluster_tests(...)` is for global cluster-composition inference from + `SharedClusterResult`. +- `region_contrasts.score_regions(..., analysis_unit="cluster_occupancy")` + remains the path for region-level occupancy inference. +- Pooled `chi_squared` and `g_test` are screening-oriented outputs; use + replicate-aware permutation paths as the primary inferential mode when + replicate structure exists. + +## Current V1 Scope + +- `mode="read_global"` supports shared read clustering from extract outputs. +- `mode="region_anchored"` supports optional matched-region exploratory clustering from pileup-derived region vectors. +- `discovery_cluster_workflow()` composes discovery and optional `region_anchored` clustering by passing derived matched regions into this workflow. +- `discovery_cluster_contrast_workflow()` composes discovery, optional `region_anchored` clustering, and defined-region contrasts on the selected loci by default, with an explicit custom contrast-region override. +- `result.region_summaries` is the v1 handoff table for `cluster_occupancy` region contrasts after read-window clustering. +- The default supported clusterer is `minibatch_kmeans`. +- Results are data-first: tables are the stable contract, while plotting is optional. diff --git a/docs/superpowers/README.md b/docs/superpowers/README.md new file mode 100644 index 0000000..d03be88 --- /dev/null +++ b/docs/superpowers/README.md @@ -0,0 +1,93 @@ +# Superpowers Docs Index + +This directory tracks internal design and implementation work for the current branch. + +- `specs/` contains design documents. +- `plans/` contains executable implementation breakdowns. +- Status is tracked here centrally so historical docs can remain stable snapshots. +- Historical plan checkboxes may remain unchecked; treat this index and the + unified remaining-work plan as the canonical execution status. + +## Status Labels + +- `implemented`: the planned slice has landed in the repo. +- `partially implemented`: some related work has landed, but the broader feature family still has follow-on work. +- `planned`: the design exists, but the slice is not yet implemented. +- `not started`: a plan exists, but execution has not started. + +## Specs + +### Shared Clustering + +- [2026-03-31-shared-cluster-distribution-design.md](specs/2026-03-31-shared-cluster-distribution-design.md) - shared-boundary clustering architecture and artifact model. Status: `partially implemented` +- [2026-04-12-shared-cluster-tests-design.md](specs/2026-04-12-shared-cluster-tests-design.md) - global shared-cluster composition inference layer that consumes `SharedClusterResult` and `ContrastSpec`. Status: `partially implemented` + +### Region Analysis + +- [2026-03-31-region-analysis-architecture-design.md](specs/2026-03-31-region-analysis-architecture-design.md) - common architecture for region discovery, global analysis, and contrasts. Status: `partially implemented` +- [2026-03-31-pre-plan-decisions-addendum.md](specs/2026-03-31-pre-plan-decisions-addendum.md) - cross-cutting policy decisions for shared clustering and region analysis follow-on work. Status: `partially implemented` +- [2026-04-01-paired-region-discovery-design.md](specs/2026-04-01-paired-region-discovery-design.md) - paired discovery support for matched and ordered comparisons. Status: `implemented` +- [2026-04-03-single-read-region-contrasts-design.md](specs/2026-04-03-single-read-region-contrasts-design.md) - first `single_read` contrast family for defined-region read-level summaries. Status: `implemented` + +### Plotting + +- [2026-04-01-plotting-axis-architecture-design.md](specs/2026-04-01-plotting-axis-architecture-design.md) - shared plotting-axis and renderer-neutral payload architecture. Status: `implemented` +- [2026-04-01-region-contrasts-plotting-design.md](specs/2026-04-01-region-contrasts-plotting-design.md) - plotting prep for region contrast profiles and heatmaps. Status: `implemented` +- [2026-04-02-region-discovery-plotting-design.md](specs/2026-04-02-region-discovery-plotting-design.md) - plotting prep for discovery scan-overview and hit-context payloads. Status: `implemented` +- [2026-04-02-global-analysis-plotting-design.md](specs/2026-04-02-global-analysis-plotting-design.md) - plotting prep for global summary and broad-window payloads. Status: `implemented` + +### Cleanup And Docs + +- [2026-04-02-superpowers-docs-cleanup-design.md](specs/2026-04-02-superpowers-docs-cleanup-design.md) - central index and status-map cleanup for internal planning docs. Status: `implemented` +- [2026-04-02-docs-and-integration-cleanup-design.md](specs/2026-04-02-docs-and-integration-cleanup-design.md) - path portability and discoverability cleanup for tracked superpowers docs and related integration surfaces. Status: `implemented` +- [2026-04-02-docs-coherence-and-discoverability-design.md](specs/2026-04-02-docs-coherence-and-discoverability-design.md) - coherence pass across internal and user-facing docs plus narrow discoverability cleanup. Status: `partially implemented` + +## Plans + +### Shared Clustering + +- [2026-03-31-shared-clustering-foundations.md](plans/2026-03-31-shared-clustering-foundations.md) - shared clustering models, artifacts, plotting payloads, and workflows. Status: `implemented` +- [2026-04-12-shared-cluster-tests.md](plans/2026-04-12-shared-cluster-tests.md) - shared-cluster global composition inference with permutation paths plus pooled screening options. Status: `implemented` + +### Region Analysis + +- [2026-03-31-global-analysis-foundations.md](plans/2026-03-31-global-analysis-foundations.md) - global analysis models, workflows, and artifact plumbing. Status: `implemented` +- [2026-03-31-region-contrasts-foundations.md](plans/2026-03-31-region-contrasts-foundations.md) - defined-region contrast foundations and scoring paths. Status: `implemented` +- [2026-04-01-cluster-occupancy-region-contrasts.md](plans/2026-04-01-cluster-occupancy-region-contrasts.md) - cluster-occupancy evidence and scoring for region contrasts. Status: `implemented` +- [2026-04-03-single-read-region-contrasts.md](plans/2026-04-03-single-read-region-contrasts.md) - `single_read` evidence builders and sample-aware scoring for read-level region contrasts. Status: `implemented` + +### Region Discovery + +- [2026-03-31-region-discovery-foundations.md](plans/2026-03-31-region-discovery-foundations.md) - genome scan foundations, result models, and plotting payloads. Status: `implemented` +- [2026-04-01-paired-region-discovery.md](plans/2026-04-01-paired-region-discovery.md) - paired discovery support for matched pairwise and paired time-course scans. Status: `implemented` +- [2026-04-01-region-discovery-cluster-workflow.md](plans/2026-04-01-region-discovery-cluster-workflow.md) - workflow that chains discovery into shared clustering. Status: `implemented` +- [2026-04-01-discovery-cluster-contrast-workflow.md](plans/2026-04-01-discovery-cluster-contrast-workflow.md) - workflow that chains discovery, clustering, and contrasts. Status: `implemented` + +### Plotting + +- [2026-04-01-plotting-axis-architecture.md](plans/2026-04-01-plotting-axis-architecture.md) - shared plotting-axis implementation slice. Status: `implemented` +- [2026-04-01-region-contrasts-plotting.md](plans/2026-04-01-region-contrasts-plotting.md) - region contrast plotting prep for profiles and heatmaps. Status: `implemented` +- [2026-04-02-region-discovery-plotting.md](plans/2026-04-02-region-discovery-plotting.md) - region discovery plotting prep for per-contig scans and local hit-context views. Status: `implemented` +- [2026-04-02-global-analysis-plotting.md](plans/2026-04-02-global-analysis-plotting.md) - global analysis plotting prep for summary and broad-window payloads. Status: `implemented` +- [2026-04-02-shared-clustering-plotting.md](plans/2026-04-02-shared-clustering-plotting.md) - shared clustering plotting prep for distribution, profile, and occupancy payloads. Status: `implemented` +- [2026-04-08-matplotlib-renderers.md](plans/2026-04-08-matplotlib-renderers.md) - Matplotlib renderers and export helpers for region contrasts, discovery, and global analysis payloads. Status: `implemented` +- [2026-04-10-shared-clustering-matplotlib-renderers.md](plans/2026-04-10-shared-clustering-matplotlib-renderers.md) - Matplotlib renderers for shared-clustering payloads. Status: `implemented` + +### Cleanup And Docs + +- [2026-04-02-superpowers-docs-cleanup.md](plans/2026-04-02-superpowers-docs-cleanup.md) - central docs index and tracked-plan cleanup. Status: `implemented` +- [2026-04-02-docs-and-integration-cleanup.md](plans/2026-04-02-docs-and-integration-cleanup.md) - historical path normalization plus narrow discoverability cleanup around completed analysis and plotting work. Status: `implemented` +- [2026-04-02-docs-coherence-and-discoverability.md](plans/2026-04-02-docs-coherence-and-discoverability.md) - coherence pass across internal and user-facing docs plus narrow package-surface cleanup if needed. Status: `partially implemented` +- [2026-04-13-legacy-cleanup-and-shared-cluster-tests-execution.md](plans/2026-04-13-legacy-cleanup-and-shared-cluster-tests-execution.md) - sprintized completion pass for shared-cluster follow-up and legacy cleanup tracks. Status: `partially implemented` +- [2026-04-14-unified-remaining-work-plan.md](plans/2026-04-14-unified-remaining-work-plan.md) - consolidated, current backlog across all historical plan docs. Status: `partially implemented` + +## Current Themes + +- Shared clustering: start with the shared clustering design spec, then the foundations plan, then the discovery-to-clustering workflows. +- Region contrasts: start with the region analysis architecture spec, then the contrasts foundations plan, cluster-occupancy follow-on work, and the region-contrasts plotting plan. +- Single-read contrasts: use the single-read region-contrasts design and plan after the region-contrasts foundations work when you need extract-backed defined-region comparison. +- Region discovery: use the region analysis architecture spec, the paired discovery spec, and the discovery workflow plans together. +- Global analysis: use the region analysis architecture spec first, then the global analysis foundations plan. +- Plotting: start with the plotting-axis spec, then the plotting-axis implementation plan, then the region-contrasts, region-discovery, and global-analysis plotting plans for helper-level coverage. +- Docs cleanup: use the docs cleanup design and plan for the central status map and tracked historical plans, then the docs-and-integration and docs-coherence follow-on docs passes for portability and discoverability cleanup. +- Legacy cleanup: use the legacy cleanup execution plan for debt-lane context, then treat the unified remaining-work plan as the canonical active backlog. diff --git a/docs/superpowers/plans/2026-03-31-global-analysis-foundations.md b/docs/superpowers/plans/2026-03-31-global-analysis-foundations.md new file mode 100644 index 0000000..3f1fffd --- /dev/null +++ b/docs/superpowers/plans/2026-03-31-global-analysis-foundations.md @@ -0,0 +1,688 @@ +# Global Analysis Foundations Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a first `global_analysis` layer that produces pileup-backed global summaries, window-level summaries, and reusable normalization factors without changing the current parsing architecture. + +**Architecture:** Build `dimelo.global_analysis` directly on top of `parse_bam.pileup()` outputs and existing `load_processed` pileup parsing helpers. Keep the v1 scope narrow: one result type, one global-summary path, one tiled-window path, and one normalization workflow that later modules can consume. + +**Tech Stack:** Python 3.11, pandas, numpy, pysam, pytest + +--- + +## File Map + +- `dimelo/models.py` + Add `GlobalAnalysisResult` as the canonical data-first output contract. +- `dimelo/global_analysis.py` + New module for whole-pileup summaries, genome tiling, window summaries, and normalization-factor computation. +- `dimelo/__init__.py` + Export `global_analysis` once the module exists. +- `tests/test_models.py` + Add model-level validation for `GlobalAnalysisResult`. +- `tests/test_global_analysis.py` + Add focused tests for summary, tiling, window aggregation, and normalization behavior. +- `docs/global-analysis.md` + Add the user-facing guide for broad summaries and preprocessing handoff. +- `README.md` + Link the new guide from the analysis section. + +Scope note: + +- This plan intentionally covers `global_analysis` foundations only. +- It does not implement `region_discovery`. +- It does not change `parse_bam.pileup()` or `load_processed.pileup_counts_from_bedmethyl()` semantics. +- It keeps plotting data-first and renderer-thin. + +### Task 1: Add Global Analysis Result Model + +**Files:** +- Modify: `dimelo/models.py` +- Modify: `tests/test_models.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_models.py +from dimelo.models import GlobalAnalysisResult + + +def test_global_analysis_result_supports_summary_windows_and_normalization(): + result = GlobalAnalysisResult( + summary=pd.DataFrame({"sample_id": ["s1"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + normalization_factors=pd.DataFrame({"sample_id": ["s1"], "global_offset": [0.1]}), + plot_data={"global_fraction_bar": pd.DataFrame({"sample_id": ["s1"]})}, + metadata={"normalization_mode": "per_sample_global"}, + figures={}, + ) + + assert list(result.summary["sample_id"]) == ["s1"] + assert list(result.windows["window_id"]) == ["chr1:0-1000"] + assert list(result.normalization_factors["sample_id"]) == ["s1"] + + +def test_global_analysis_result_rejects_none_core_outputs(): + with pytest.raises(ValueError, match="summary, normalization_factors, plot_data"): + GlobalAnalysisResult( + summary=None, + windows=None, + normalization_factors=None, + plot_data=None, + metadata={}, + figures={}, + ) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_models.py::test_global_analysis_result_supports_summary_windows_and_normalization tests/test_models.py::test_global_analysis_result_rejects_none_core_outputs -q` +Expected: FAIL with `ImportError` or `AttributeError` because `GlobalAnalysisResult` does not exist yet + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/models.py +@dataclass +class GlobalAnalysisResult: + summary: pd.DataFrame + windows: pd.DataFrame | None + normalization_factors: pd.DataFrame + plot_data: dict[str, pd.DataFrame | dict[str, Any]] + metadata: dict[str, Any] = field(default_factory=dict) + figures: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "summary": self.summary, + "normalization_factors": self.normalization_factors, + "plot_data": self.plot_data, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "GlobalAnalysisResult requires non-None values for: " + f"{', '.join(missing)}" + ) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_models.py::test_global_analysis_result_supports_summary_windows_and_normalization tests/test_models.py::test_global_analysis_result_rejects_none_core_outputs -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/models.py tests/test_models.py +git commit -m "feat: add global analysis result model" +``` + +### Task 2: Add Whole-Pileup Global Summary Functions + +**Files:** +- Create: `dimelo/global_analysis.py` +- Modify: `dimelo/__init__.py` +- Create: `tests/test_global_analysis.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_global_analysis.py +import pandas as pd + +from dimelo import global_analysis +from dimelo.models import SampleSpec + + +def test_summarize_global_samples_from_pileup(monkeypatch): + samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="15min", + extract_h5="s2.h5", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + + counts = { + ("s1.bed.gz", "A,0"): (10, 100), + ("s1.bed.gz", "CG,0"): (20, 200), + ("s2.bed.gz", "A,0"): (30, 100), + ("s2.bed.gz", "CG,0"): (40, 200), + } + + monkeypatch.setattr( + global_analysis, + "_global_counts_from_bedmethyl", + lambda bedmethyl_file, motif, quiet=True: counts[(bedmethyl_file, motif)], + ) + + summary = global_analysis.summarize_global_samples( + samples=samples, + motifs=["A,0", "CG,0"], + ) + + assert set(summary.columns) >= { + "sample_id", + "condition", + "motif", + "modified_count", + "valid_count", + "global_fraction", + } + assert summary.loc[(summary["sample_id"] == "s2") & (summary["motif"] == "A,0"), "global_fraction"].iloc[0] == 0.3 + + +def test_summarize_global_samples_requires_pileup_path(): + with pytest.raises(ValueError, match="pileup_path"): + global_analysis.summarize_global_samples( + samples=[SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5")], + motifs=["A,0"], + ) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_global_analysis.py::test_summarize_global_samples_from_pileup tests/test_global_analysis.py::test_summarize_global_samples_requires_pileup_path -q` +Expected: FAIL with `ModuleNotFoundError: No module named 'dimelo.global_analysis'` + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/global_analysis.py +from __future__ import annotations + +from pathlib import Path +from typing import Iterable + +import pandas as pd +import pysam + +from . import load_processed, utils + + +def _global_counts_from_bedmethyl( + bedmethyl_file: str | Path, + motif: str, + quiet: bool = True, +) -> tuple[int, int]: + parsed_motif = utils.ParsedMotif(motif) + tabix = pysam.TabixFile(str(bedmethyl_file)) + + modified_count = 0 + valid_count = 0 + for contig in tabix.contigs: + for row in tabix.fetch(contig): + keep_basemod, _, modified_in_row, valid_in_row = load_processed.process_pileup_row( + row, + parsed_motif, + ".", + False, + ) + if keep_basemod: + modified_count += modified_in_row + valid_count += valid_in_row + return modified_count, valid_count + + +def summarize_global_samples( + *, + samples, + motifs: Iterable[str], + quiet: bool = True, +) -> pd.DataFrame: + rows = [] + for sample in samples: + metadata = sample.metadata or {} + if "pileup_path" not in metadata: + raise ValueError( + f"Sample {sample.sample_id} is missing metadata['pileup_path']." + ) + for motif in motifs: + modified_count, valid_count = _global_counts_from_bedmethyl( + metadata["pileup_path"], + motif, + quiet=quiet, + ) + global_fraction = 0.0 if valid_count == 0 else modified_count / valid_count + rows.append( + { + "sample_id": sample.sample_id, + "condition": sample.condition, + "replicate": sample.replicate, + "motif": motif, + "modified_count": modified_count, + "valid_count": valid_count, + "global_fraction": global_fraction, + } + ) + return pd.DataFrame(rows) +``` + +```python +# dimelo/__init__.py +from . import global_analysis + +__all__.append("global_analysis") +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_global_analysis.py::test_summarize_global_samples_from_pileup tests/test_global_analysis.py::test_summarize_global_samples_requires_pileup_path -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/global_analysis.py dimelo/__init__.py tests/test_global_analysis.py +git commit -m "feat: add global pileup summary workflow" +``` + +### Task 3: Add Window Summaries And Normalization Factors + +**Files:** +- Modify: `dimelo/global_analysis.py` +- Modify: `tests/test_global_analysis.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_global_analysis.py +def test_tile_windows_from_genome_sizes_dict(): + windows = global_analysis.tile_genome_windows( + genome_sizes={"chr1": 2500}, + window_size=1000, + step_size=500, + ) + + assert windows["window_id"].tolist() == [ + "chr1:0-1000", + "chr1:500-1500", + "chr1:1000-2000", + "chr1:1500-2500", + ] + + +def test_build_window_summary_from_regions_to_list(monkeypatch): + samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + ] + windows = pd.DataFrame( + { + "window_id": ["chr1:0-1000", "chr1:500-1500"], + "chromosome": ["chr1", "chr1"], + "start": [0, 500], + "end": [1000, 1500], + "strand": [".", "."], + } + ) + + monkeypatch.setattr( + global_analysis, + "tile_genome_windows", + lambda genome_sizes, window_size, step_size, include_contigs=None, exclude_contigs=None: windows, + ) + monkeypatch.setattr( + global_analysis.load_processed, + "regions_to_list", + lambda function_handle, regions, window_size, quiet, cores, split_large_regions=False: [(5, 10), (1, 10)], + ) + + summary = global_analysis.build_window_summary( + samples=samples, + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + window_size=1000, + step_size=500, + ) + + assert summary["window_fraction"].tolist() == [0.5, 0.1] + + +def test_compute_global_normalization_factors_from_summary(): + summary = pd.DataFrame( + { + "sample_id": ["s1", "s2"], + "condition": ["NS", "15min"], + "replicate": [None, None], + "motif": ["A,0", "A,0"], + "modified_count": [10, 30], + "valid_count": [100, 100], + "global_fraction": [0.1, 0.3], + } + ) + + factors = global_analysis.compute_global_normalization_factors(summary) + + assert set(factors.columns) >= { + "sample_id", + "motif", + "global_fraction", + "reference_fraction", + "global_offset", + } + assert factors.loc[factors["sample_id"] == "s1", "global_offset"].iloc[0] == pytest.approx(-0.1) + assert factors.loc[factors["sample_id"] == "s2", "global_offset"].iloc[0] == pytest.approx(0.1) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_global_analysis.py::test_tile_windows_from_genome_sizes_dict tests/test_global_analysis.py::test_build_window_summary_from_regions_to_list tests/test_global_analysis.py::test_compute_global_normalization_factors_from_summary -q` +Expected: FAIL because the functions do not exist yet + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/global_analysis.py +from functools import partial + +import numpy as np + + +def tile_genome_windows( + *, + genome_sizes: dict[str, int], + window_size: int, + step_size: int, + include_contigs: Iterable[str] | None = None, + exclude_contigs: Iterable[str] | None = None, +) -> pd.DataFrame: + if window_size <= 0 or step_size <= 0: + raise ValueError("window_size and step_size must be positive.") + + include = set(include_contigs or genome_sizes.keys()) + exclude = set(exclude_contigs or []) + rows = [] + for chromosome, contig_length in genome_sizes.items(): + if chromosome not in include or chromosome in exclude: + continue + start = 0 + while start < contig_length: + end = min(start + window_size, contig_length) + rows.append( + { + "window_id": f"{chromosome}:{start}-{end}", + "chromosome": chromosome, + "start": start, + "end": end, + "strand": ".", + } + ) + if end == contig_length: + break + start += step_size + return pd.DataFrame(rows) + + +def build_window_summary( + *, + samples, + motifs: Iterable[str], + genome_sizes: dict[str, int], + window_size: int, + step_size: int, + include_contigs: Iterable[str] | None = None, + exclude_contigs: Iterable[str] | None = None, + quiet: bool = True, + cores: int | None = None, +) -> pd.DataFrame: + motifs = list(motifs) + if len(motifs) != 1: + raise ValueError("build_window_summary currently supports exactly one motif.") + + motif = motifs[0] + windows = tile_genome_windows( + genome_sizes=genome_sizes, + window_size=window_size, + step_size=step_size, + include_contigs=include_contigs, + exclude_contigs=exclude_contigs, + ) + region_strings = [ + f"{row.chromosome}:{row.start}-{row.end},{row.strand}" + for row in windows.itertuples(index=False) + ] + + rows = [] + for sample in samples: + metadata = sample.metadata or {} + if "pileup_path" not in metadata: + raise ValueError( + f"Sample {sample.sample_id} is missing metadata['pileup_path']." + ) + pileup_loader = partial( + load_processed.pileup_counts_from_bedmethyl, + bedmethyl_file=metadata["pileup_path"], + motif=motif, + ) + counts_by_window = load_processed.regions_to_list( + function_handle=pileup_loader, + regions=region_strings, + window_size=None, + quiet=quiet, + cores=cores, + ) + for window_row, (modified_count, valid_count) in zip( + windows.itertuples(index=False), + counts_by_window, + ): + window_fraction = 0.0 if valid_count == 0 else modified_count / valid_count + rows.append( + { + "sample_id": sample.sample_id, + "condition": sample.condition, + "replicate": sample.replicate, + "motif": motif, + "window_id": window_row.window_id, + "chromosome": window_row.chromosome, + "start": window_row.start, + "end": window_row.end, + "modified_count": modified_count, + "valid_count": valid_count, + "window_fraction": window_fraction, + } + ) + return pd.DataFrame(rows) + + +def compute_global_normalization_factors(summary: pd.DataFrame) -> pd.DataFrame: + grouped_reference = ( + summary.groupby("motif", sort=True)["global_fraction"] + .mean() + .rename("reference_fraction") + .reset_index() + ) + merged = summary.merge(grouped_reference, on="motif", how="left") + merged["global_offset"] = merged["global_fraction"] - merged["reference_fraction"] + return merged.loc[ + :, + ["sample_id", "condition", "replicate", "motif", "global_fraction", "reference_fraction", "global_offset"], + ].copy() +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_global_analysis.py::test_tile_windows_from_genome_sizes_dict tests/test_global_analysis.py::test_build_window_summary_from_regions_to_list tests/test_global_analysis.py::test_compute_global_normalization_factors_from_summary -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/global_analysis.py tests/test_global_analysis.py +git commit -m "feat: add global window summaries and normalization" +``` + +### Task 4: Add Global Analysis Result Builder And Docs + +**Files:** +- Modify: `dimelo/global_analysis.py` +- Modify: `tests/test_global_analysis.py` +- Create: `docs/global-analysis.md` +- Modify: `README.md` + +- [ ] **Step 1: Write the failing test** + +```python +# tests/test_global_analysis.py +def test_run_global_analysis_returns_result(monkeypatch): + samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + ] + + monkeypatch.setattr( + global_analysis, + "summarize_global_samples", + lambda samples, motifs, quiet=True: pd.DataFrame( + { + "sample_id": ["s1"], + "condition": ["NS"], + "replicate": [None], + "motif": ["A,0"], + "modified_count": [10], + "valid_count": [100], + "global_fraction": [0.1], + } + ), + ) + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **kwargs: pd.DataFrame({"window_id": ["chr1:0-1000"]}), + ) + + result = global_analysis.run_global_analysis( + samples=samples, + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=1000, + step_size=1000, + ) + + assert list(result.summary["sample_id"]) == ["s1"] + assert list(result.windows["window_id"]) == ["chr1:0-1000"] + assert "global_fraction_bar" in result.plot_data +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_global_analysis.py::test_run_global_analysis_returns_result -q` +Expected: FAIL with `AttributeError` because `run_global_analysis` does not exist yet + +- [ ] **Step 3: Write minimal implementation and docs** + +```python +# dimelo/global_analysis.py +from .models import GlobalAnalysisResult + + +def run_global_analysis( + *, + samples, + motifs: Iterable[str], + genome_sizes: dict[str, int], + window_size: int, + step_size: int, + include_contigs: Iterable[str] | None = None, + exclude_contigs: Iterable[str] | None = None, + quiet: bool = True, + cores: int | None = None, +) -> GlobalAnalysisResult: + summary = summarize_global_samples(samples=samples, motifs=motifs, quiet=quiet) + windows = build_window_summary( + samples=samples, + motifs=motifs, + genome_sizes=genome_sizes, + window_size=window_size, + step_size=step_size, + include_contigs=include_contigs, + exclude_contigs=exclude_contigs, + quiet=quiet, + cores=cores, + ) + normalization_factors = compute_global_normalization_factors(summary) + plot_data = { + "global_fraction_bar": summary.loc[ + :, + ["sample_id", "condition", "motif", "global_fraction"], + ].copy(), + "window_fraction_table": windows.copy(), + } + return GlobalAnalysisResult( + summary=summary, + windows=windows, + normalization_factors=normalization_factors, + plot_data=plot_data, + metadata={ + "normalization_mode": "per_sample_global", + "window_size": window_size, + "step_size": step_size, + "motifs": list(motifs), + }, + figures={}, + ) +``` + +```markdown +# Global Analysis + +`dimelo.global_analysis` provides broad pileup-backed summaries on top of `parse_bam.pileup()` outputs. + +## What It Covers + +- whole-pileup motif abundance summaries +- tiled window summaries across broad genomic space +- reusable per-sample global normalization offsets + +## Example + +```python +from dimelo import global_analysis +from dimelo.models import SampleSpec + +result = global_analysis.run_global_analysis( + samples=[ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="output/s1.extract.h5", + metadata={"pileup_path": "output/s1.pileup.sorted.bed.gz"}, + ) + ], + motifs=["A,0"], + genome_sizes={"chr1": 248956422}, + window_size=100000, + step_size=50000, +) +``` + +The canonical outputs are `result.summary`, `result.windows`, and `result.normalization_factors`. +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_global_analysis.py -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/global_analysis.py tests/test_global_analysis.py docs/global-analysis.md README.md +git commit -m "docs: add global analysis workflow guide" +``` diff --git a/docs/superpowers/plans/2026-03-31-region-contrasts-foundations.md b/docs/superpowers/plans/2026-03-31-region-contrasts-foundations.md new file mode 100644 index 0000000..ae9f163 --- /dev/null +++ b/docs/superpowers/plans/2026-03-31-region-contrasts-foundations.md @@ -0,0 +1,763 @@ +# Region Contrasts Foundations Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a stable, backward-compatible `region_contrasts` layer for known-region comparisons over pileup-backed counts, including explicit contrast models, effect-size summaries, and a first beta-binomial testing path. + +**Architecture:** Build `dimelo.region_contrasts` on top of the existing `parse_bam.pileup()` and `load_processed.pileup_counts_from_bedmethyl()` flow without changing parsing semantics. Keep the v1 scope intentionally narrow: fully support only `analysis_unit="ensemble_region"` + `signal_source="pileup_counts"` with `effect_size_only` and `beta_binomial`, while validating and rejecting unsupported combinations explicitly. + +**Tech Stack:** Python 3.11, pandas, numpy, scipy, pytest + +--- + +Scope note: + +- This plan intentionally covers the first executable `region_contrasts` slice only. +- It does not implement `region_discovery`, single-read contrasts, cluster-occupancy contrasts, or time-course inference. +- It preserves continuity by consuming existing pileup outputs rather than altering `parse_bam` or `load_processed` semantics. + +### Task 1: Add Contrast Models And Result Types + +**Files:** +- Modify: `dimelo/models.py` +- Modify: `tests/test_models.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_models.py +from dimelo.models import ContrastSpec, RegionContrastResult + + +def test_contrast_spec_accepts_pairwise_mode(): + contrast = ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ) + assert contrast.mode == "pairwise" + assert contrast.numerator == ["15min"] + + +def test_contrast_spec_rejects_missing_groups_for_pairwise(): + try: + ContrastSpec(mode="pairwise") + except ValueError as exc: + assert "numerator" in str(exc) + else: + raise AssertionError("Expected ContrastSpec to validate pairwise groups") + + +def test_region_contrast_result_requires_regions_and_summary(): + contrast = ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ) + try: + RegionContrastResult( + regions=None, + summary=None, + contrast=contrast, + plot_data={}, + metadata={}, + figures={}, + ) + except ValueError as exc: + assert "regions" in str(exc) + assert "summary" in str(exc) + else: + raise AssertionError("Expected RegionContrastResult to require core tables") +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_models.py -q` +Expected: FAIL with `ImportError` or `AttributeError` for missing `ContrastSpec` / `RegionContrastResult` + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/models.py +from dataclasses import dataclass, field +from typing import Any + + +@dataclass +class ContrastSpec: + mode: str + numerator: list[str] | None = None + denominator: list[str] | None = None + background: list[str] | None = None + time_order: list[str] | None = None + pairing_key: str | None = None + reference_condition: str | None = None + metadata: dict[str, Any] | None = None + + def __post_init__(self) -> None: + if self.mode not in { + "single_dataset", + "pairwise", + "matched_pairwise", + "group_vs_group", + "background_adjusted", + "time_course", + }: + raise ValueError(f"Unsupported contrast mode: {self.mode}") + if self.mode in {"pairwise", "group_vs_group"}: + if not self.numerator or not self.denominator: + raise ValueError( + "ContrastSpec pairwise/group_vs_group modes require numerator and denominator." + ) + if self.mode == "time_course" and not self.time_order: + raise ValueError("ContrastSpec time_course mode requires time_order.") + + +@dataclass +class RegionContrastResult: + regions: pd.DataFrame + summary: pd.DataFrame + contrast: ContrastSpec + plot_data: dict[str, pd.DataFrame | dict[str, Any]] + metadata: dict[str, Any] = field(default_factory=dict) + figures: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "regions": self.regions, + "summary": self.summary, + "plot_data": self.plot_data, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "RegionContrastResult requires non-None values for: " + f"{', '.join(missing)}" + ) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_models.py -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/models.py tests/test_models.py +git commit -m "feat: add region contrast models" +``` + +### Task 2: Add Region Contrast Validation And Evidence Table Construction + +**Files:** +- Create: `dimelo/region_contrasts.py` +- Create: `tests/test_region_contrasts.py` +- Modify: `dimelo/__init__.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_region_contrasts.py +import pandas as pd +import pytest + +from dimelo.models import ContrastSpec, SampleSpec +from dimelo import region_contrasts + + +def test_validate_supported_v1_combination(): + region_contrasts.validate_region_contrast_request( + analysis_unit="ensemble_region", + representation="modified_fraction", + signal_source="pileup_counts", + test="beta_binomial", + ) + + +def test_validate_rejects_unsupported_single_read_beta_binomial(): + with pytest.raises(ValueError, match="ensemble_region"): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="pileup_counts", + test="beta_binomial", + ) + + +def test_build_region_evidence_table_from_pileup_counts(monkeypatch): + fake_samples = [ + SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5", metadata={"pileup_path": "s1.bed.gz"}), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5", metadata={"pileup_path": "s2.bed.gz"}), + ] + + monkeypatch.setattr( + region_contrasts.utils, + "regions_dict_from_input", + lambda regions, window_size=None: {"chr1": [(0, 10, "+")], "chr2": [(20, 30, "-")]}, + ) + counts_by_file = { + "s1.bed.gz": [(2, 10), (6, 10)], + "s2.bed.gz": [(7, 10), (8, 10)], + } + monkeypatch.setattr( + region_contrasts.load_processed, + "regions_to_list", + lambda function_handle, regions, window_size, quiet, cores, split_large_regions=False: counts_by_file[function_handle.keywords["bedmethyl_file"]], + ) + + evidence = region_contrasts.build_region_evidence_table( + samples=fake_samples, + regions="matched.bed", + motifs=["A,0"], + ) + + assert list(evidence.columns[:7]) == [ + "region_id", + "chromosome", + "start", + "end", + "strand", + "sample_id", + "condition", + ] + assert set(evidence["modified_count"]) == {2, 6, 7, 8} + assert set(evidence["valid_count"]) == {10} + assert "mod_fraction" in evidence.columns +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_region_contrasts.py -q` +Expected: FAIL with `ModuleNotFoundError: No module named 'dimelo.region_contrasts'` + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/region_contrasts.py +from __future__ import annotations + +from functools import partial + +import pandas as pd + +from . import load_processed, utils + + +def validate_region_contrast_request( + *, + analysis_unit: str, + representation: str, + signal_source: str, + test: str, +) -> None: + if analysis_unit != "ensemble_region": + raise ValueError("V1 inferential region_contrasts requires analysis_unit='ensemble_region'.") + if signal_source != "pileup_counts": + raise ValueError("V1 inferential region_contrasts requires signal_source='pileup_counts'.") + if representation not in {"modified_fraction", "modified_count"}: + raise ValueError("V1 inferential region_contrasts requires modified_fraction or modified_count.") + if test not in {"beta_binomial", "effect_size_only"}: + raise ValueError("Unsupported region contrast test.") + + +def build_region_evidence_table( + *, + samples, + regions, + motifs, + window_size=None, + quiet: bool = True, + cores=None, +): + if len(motifs) != 1: + raise ValueError("build_region_evidence_table currently supports exactly one motif.") + motif = motifs[0] + regions_dict = utils.regions_dict_from_input(regions, window_size) + region_metadata = [ + (chromosome, start, end, strand) + for chromosome, region_list in regions_dict.items() + for start, end, strand in region_list + ] + rows = [] + for sample in samples: + if not sample.metadata or "pileup_path" not in sample.metadata: + raise ValueError(f"Sample {sample.sample_id!r} is missing metadata['pileup_path'].") + loader = partial( + load_processed.pileup_counts_from_bedmethyl, + bedmethyl_file=sample.metadata["pileup_path"], + motif=motif, + window_size=window_size, + quiet=quiet, + cores=cores, + ) + per_region_counts = load_processed.regions_to_list( + function_handle=loader, + regions=regions, + window_size=window_size, + quiet=quiet, + cores=cores, + split_large_regions=False, + ) + for (chrom, start, end, strand), (modified_count, valid_count) in zip( + region_metadata, per_region_counts + ): + mod_fraction = 0.0 if valid_count == 0 else modified_count / valid_count + rows.append( + { + "region_id": f"{chrom}:{start}-{end}:{strand}", + "chromosome": chrom, + "start": start, + "end": end, + "strand": strand, + "sample_id": sample.sample_id, + "condition": sample.condition, + "replicate": sample.replicate, + "modified_count": int(modified_count), + "valid_count": int(valid_count), + "mod_fraction": float(mod_fraction), + } + ) + return pd.DataFrame(rows) +``` + +```python +# dimelo/__init__.py +from . import ( + cluster, + distribution, + export, + load_processed, + models, + parse_bam, + plotting, + plot_depth_histogram, + plot_depth_profile, + plot_enrichment, + plot_enrichment_profile, + plot_read_browser, + plot_reads, + region_analysis, + region_contrasts, + workflows, +) + +__all__ = [ + "cluster", + "distribution", + "export", + "load_processed", + "models", + "parse_bam", + "plotting", + "plot_depth_histogram", + "plot_depth_profile", + "plot_enrichment", + "plot_enrichment_profile", + "plot_read_browser", + "plot_reads", + "region_analysis", + "region_contrasts", + "workflows", +] +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_region_contrasts.py -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/region_contrasts.py dimelo/__init__.py tests/test_region_contrasts.py +git commit -m "feat: add region contrast evidence builders" +``` + +### Task 3: Add Effect-Size Region Scoring Workflow + +**Files:** +- Modify: `dimelo/region_contrasts.py` +- Modify: `tests/test_region_contrasts.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_region_contrasts.py +from dimelo.models import ContrastSpec + + +def test_score_regions_effect_size_pairwise(monkeypatch): + evidence = pd.DataFrame( + { + "region_id": ["reg1", "reg1", "reg2", "reg2"], + "chromosome": ["chr1", "chr1", "chr2", "chr2"], + "start": [0, 0, 20, 20], + "end": [10, 10, 30, 30], + "strand": ["+", "+", "-", "-"], + "sample_id": ["s1", "s2", "s1", "s2"], + "condition": ["NS", "15min", "NS", "15min"], + "replicate": [1, 1, 1, 1], + "modified_count": [2, 8, 6, 6], + "valid_count": [10, 10, 10, 10], + "mod_fraction": [0.2, 0.8, 0.6, 0.6], + } + ) + monkeypatch.setattr(region_contrasts, "build_region_evidence_table", lambda **kwargs: evidence) + + result = region_contrasts.score_regions( + samples=[], + regions="matched.bed", + motifs=["A,0"], + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + analysis_unit="ensemble_region", + representation="modified_fraction", + signal_source="pileup_counts", + test="effect_size_only", + ) + + assert set(result.regions.columns) >= { + "region_id", + "fraction", + "reference_fraction", + "delta_fraction", + "log2_fc", + "rank", + } + top = result.regions.sort_values("rank").iloc[0] + assert top["region_id"] == "reg1" + assert top["delta_fraction"] > 0 +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_region_contrasts.py::test_score_regions_effect_size_pairwise -q` +Expected: FAIL with `AttributeError` for missing `score_regions` + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/region_contrasts.py +from .models import ContrastSpec, RegionContrastResult + + +def _pool_region_groups(evidence: pd.DataFrame, contrast: ContrastSpec) -> pd.DataFrame: + if contrast.mode not in {"pairwise", "group_vs_group"}: + raise NotImplementedError( + "V1 effect-size region_contrasts supports pairwise and group_vs_group only." + ) + condition_groups = { + "numerator": set(contrast.numerator or []), + "denominator": set(contrast.denominator or []), + } + pooled_rows = [] + for label, conditions in condition_groups.items(): + subset = evidence[evidence["condition"].isin(conditions)].copy() + grouped = ( + subset.groupby(["region_id", "chromosome", "start", "end", "strand"], sort=True) + .agg( + modified_count=("modified_count", "sum"), + valid_count=("valid_count", "sum"), + replicate_n=("sample_id", "nunique"), + ) + .reset_index() + ) + grouped["group"] = label + grouped["fraction"] = grouped["modified_count"] / grouped["valid_count"].where( + grouped["valid_count"] > 0, + 1, + ) + pooled_rows.append(grouped) + return pd.concat(pooled_rows, ignore_index=True) + + +def score_regions( + *, + samples, + regions, + motifs, + contrast: ContrastSpec, + analysis_unit: str = "ensemble_region", + representation: str = "modified_fraction", + signal_source: str = "pileup_counts", + test: str = "beta_binomial", + multiple_testing: str = "fdr_bh", +): + validate_region_contrast_request( + analysis_unit=analysis_unit, + representation=representation, + signal_source=signal_source, + test=test, + ) + evidence = build_region_evidence_table(samples=samples, regions=regions, motifs=motifs) + pooled = _pool_region_groups(evidence, contrast) + numerator = pooled[pooled["group"] == "numerator"].rename( + columns={ + "modified_count": "modified_count", + "valid_count": "valid_count", + "fraction": "fraction", + "replicate_n": "replicate_n", + } + ) + denominator = pooled[pooled["group"] == "denominator"].rename( + columns={ + "modified_count": "reference_modified_count", + "valid_count": "reference_valid_count", + "fraction": "reference_fraction", + "replicate_n": "reference_replicate_n", + } + ) + merged = numerator.merge( + denominator, + on=["region_id", "chromosome", "start", "end", "strand"], + how="left", + ) + merged["delta_fraction"] = merged["fraction"] - merged["reference_fraction"] + merged["log2_fc"] = np.log2( + (merged["fraction"] + 1e-6) / (merged["reference_fraction"] + 1e-6) + ) + merged["rank"] = ( + merged["delta_fraction"].abs().rank(method="first", ascending=False).astype(int) + ) + summary = merged.loc[:, ["region_id", "fraction", "reference_fraction", "delta_fraction", "log2_fc", "rank"]] + return RegionContrastResult( + regions=merged.sort_values("rank", kind="stable").reset_index(drop=True), + summary=summary.sort_values("rank", kind="stable").reset_index(drop=True), + contrast=contrast, + plot_data={"region_effect_sizes": summary.copy()}, + metadata={ + "contrast_mode": contrast.mode, + "analysis_unit": analysis_unit, + "representation": representation, + "signal_source": signal_source, + "test": test, + "normalization_mode": "none", + "biological_interpretation": "Region-level motif abundance contrast.", + "renderer": None, + }, + figures={}, + ) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_region_contrasts.py::test_score_regions_effect_size_pairwise -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/region_contrasts.py tests/test_region_contrasts.py +git commit -m "feat: add effect-size region contrast workflow" +``` + +### Task 4: Add Beta-Binomial Testing For Pooled Region Counts + +**Files:** +- Modify: `dimelo/region_contrasts.py` +- Modify: `tests/test_region_contrasts.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_region_contrasts.py + +def test_score_regions_beta_binomial_adds_pvalues(monkeypatch): + evidence = pd.DataFrame( + { + "region_id": ["reg1", "reg1", "reg2", "reg2"], + "chromosome": ["chr1", "chr1", "chr2", "chr2"], + "start": [0, 0, 20, 20], + "end": [10, 10, 30, 30], + "strand": ["+", "+", "-", "-"], + "sample_id": ["s1", "s2", "s1", "s2"], + "condition": ["NS", "15min", "NS", "15min"], + "replicate": [1, 1, 1, 1], + "modified_count": [1, 9, 5, 6], + "valid_count": [10, 10, 10, 10], + "mod_fraction": [0.1, 0.9, 0.5, 0.6], + } + ) + monkeypatch.setattr(region_contrasts, "build_region_evidence_table", lambda **kwargs: evidence) + + result = region_contrasts.score_regions( + samples=[], + regions="matched.bed", + motifs=["A,0"], + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + analysis_unit="ensemble_region", + representation="modified_fraction", + signal_source="pileup_counts", + test="beta_binomial", + ) + + assert "p_value" in result.regions.columns + assert "adjusted_p_value" in result.regions.columns + assert result.regions["p_value"].between(0, 1).all() +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_region_contrasts.py::test_score_regions_beta_binomial_adds_pvalues -q` +Expected: FAIL because p-value columns do not exist + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/region_contrasts.py +from scipy.stats import betabinom + + +def _estimate_beta_binomial_prior(reference_modified: pd.Series, reference_valid: pd.Series) -> tuple[float, float]: + fractions = np.divide( + reference_modified, + reference_valid, + out=np.zeros_like(reference_modified, dtype=float), + where=reference_valid > 0, + ) + mean = float(fractions.mean()) + var = float(fractions.var(ddof=0)) + if var <= 0: + concentration = 100.0 + else: + max_var = max(mean * (1.0 - mean), 1e-9) + shrink = min(max(var / max_var, 1e-6), 0.99) + concentration = max((1.0 / shrink) - 1.0, 1.0) + alpha = max(mean * concentration, 1e-6) + beta = max((1.0 - mean) * concentration, 1e-6) + return alpha, beta + + +def _adjust_pvalues_bh(p_values: pd.Series) -> pd.Series: + order = np.argsort(p_values.to_numpy()) + ranked = p_values.to_numpy()[order] + n = len(ranked) + adjusted = np.empty(n, dtype=float) + running = 1.0 + for i in range(n - 1, -1, -1): + rank = i + 1 + running = min(running, ranked[i] * n / rank) + adjusted[i] = running + out = np.empty(n, dtype=float) + out[order] = adjusted + return pd.Series(out, index=p_values.index) + + +def _beta_binomial_pvalues(merged: pd.DataFrame) -> pd.Series: + alpha, beta = _estimate_beta_binomial_prior( + merged["reference_modified_count"], + merged["reference_valid_count"], + ) + pmf = betabinom.pmf(merged["modified_count"], merged["valid_count"], alpha, beta) + p_values = 1.0 - betabinom.cdf( + merged["modified_count"] - 1, + merged["valid_count"], + alpha, + beta, + ) + return pd.Series(np.clip(np.maximum(pmf, p_values), 0.0, 1.0), index=merged.index) +``` + +Add to `score_regions(...)`: + +```python + if test == "beta_binomial": + merged["p_value"] = _beta_binomial_pvalues(merged) + if multiple_testing != "fdr_bh": + raise ValueError("V1 beta-binomial region_contrasts supports multiple_testing='fdr_bh' only.") + merged["adjusted_p_value"] = _adjust_pvalues_bh(merged["p_value"]) + else: + merged["p_value"] = pd.NA + merged["adjusted_p_value"] = pd.NA +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_region_contrasts.py::test_score_regions_beta_binomial_adds_pvalues -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/region_contrasts.py tests/test_region_contrasts.py +git commit -m "feat: add beta-binomial region contrast scoring" +``` + +### Task 5: Add User-Facing Docs For Known-Region Contrasts + +**Files:** +- Create: `docs/region-contrasts.md` +- Modify: `README.md` + +- [ ] **Step 1: Write the docs content** + +```markdown +# Region Contrasts + +`dimelo.region_contrasts` scores known regions from pileup-backed inputs. + +## When To Use It + +- use this when you already know the regions you want to compare +- use `region_discovery` later for unknown loci +- use `cluster` first if you want read-state or cluster-occupancy summaries instead of average motif abundance + +## V1 Supported Path + +- `analysis_unit="ensemble_region"` +- `representation="modified_fraction"` or `"modified_count"` +- `signal_source="pileup_counts"` +- `test="effect_size_only"` or `"beta_binomial"` + +## Example + +```python +from dimelo import region_contrasts +from dimelo.models import ContrastSpec, SampleSpec + +result = region_contrasts.score_regions( + samples=[ + SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5", metadata={"pileup_path": "s1.bed.gz"}), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5", metadata={"pileup_path": "s2.bed.gz"}), + ], + regions="matched_regions.bed", + motifs=["A,0"], + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + analysis_unit="ensemble_region", + representation="modified_fraction", + signal_source="pileup_counts", + test="beta_binomial", +) +``` + +The canonical outputs are `result.regions`, `result.summary`, and `result.plot_data`. +``` + +- [ ] **Step 2: Save the docs and link them from the README clustering/analysis section** + +Run: `sed -n '1,220p' docs/region-contrasts.md` +Expected: shows the new guide with the v1 support matrix and example + +- [ ] **Step 3: Commit** + +```bash +git add docs/region-contrasts.md README.md +git commit -m "docs: add region contrast workflow guide" +``` diff --git a/docs/superpowers/plans/2026-03-31-region-discovery-foundations.md b/docs/superpowers/plans/2026-03-31-region-discovery-foundations.md new file mode 100644 index 0000000..9a8abf4 --- /dev/null +++ b/docs/superpowers/plans/2026-03-31-region-discovery-foundations.md @@ -0,0 +1,199 @@ +# Region Discovery Foundations Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a first `region_discovery` layer for deterministic de novo locus finding from pileup-backed inputs, with clean handoff into downstream known-region workflows. + +**Architecture:** Build `dimelo.region_discovery` on top of existing parsing + loader behavior (`parse_bam.pileup`, `load_processed.pileup_counts_from_bedmethyl`, `load_processed.regions_to_list`) and the new `global_analysis` windowing utilities. Keep v1 deterministic and tiled. + +**Tech Stack:** Python 3.11, pandas, numpy, pytest + +--- + +## File Map + +- `dimelo/models.py` + Add `RegionDiscoveryResult` as the canonical data-first result container. +- `dimelo/region_discovery.py` + New module for scanning, ranking, hit merging, and BED export. +- `dimelo/__init__.py` + Export `region_discovery`. +- `tests/test_models.py` + Add model-level validation for `RegionDiscoveryResult`. +- `tests/test_region_discovery.py` + Add focused tests for scan, filtering, ranking, merge, and export helpers. +- `docs/region-discovery.md` + Add user-facing guide for de novo discovery and handoff. +- `README.md` + Link the new guide from the analysis guides section. + +Scope note: + +- This plan covers `region_discovery` foundations only. +- It does not implement `region_contrasts` inferential expansions. +- It does not modify existing parse/load semantics. +- It keeps outputs data-first and plotting-optional. + +### Task 1: Add RegionDiscoveryResult Model + +**Files:** +- Modify: `dimelo/models.py` +- Modify: `tests/test_models.py` + +- [ ] **Step 1: Write failing tests** + - Add a test that constructs `RegionDiscoveryResult` with `hits`, `windows`, `plot_data`, and metadata. + - Add a test that rejects `None` for required fields (`hits`, `windows`, `plot_data`). + +- [ ] **Step 2: Verify tests fail** + - Run: + - `pytest tests/test_models.py::test_region_discovery_result_accepts_tables_and_plot_data tests/test_models.py::test_region_discovery_result_rejects_missing_required_fields -q` + +- [ ] **Step 3: Implement minimal model** + - Add dataclass: + - `hits: pd.DataFrame` + - `windows: pd.DataFrame` + - `contrast: ContrastSpec | None` + - `plot_data: dict[str, pd.DataFrame | dict[str, Any]]` + - `metadata: dict[str, Any] = field(default_factory=dict)` + - `figures: dict[str, Any] = field(default_factory=dict)` + - Add `__post_init__` required field checks. + +- [ ] **Step 4: Verify tests pass** + - Re-run the focused tests above. + +- [ ] **Step 5: Commit** + - `git add dimelo/models.py tests/test_models.py` + - `git commit -m "feat: add region discovery result model"` + +### Task 2: Add Deterministic Genome Scan And Window Scoring + +**Files:** +- Create: `dimelo/region_discovery.py` +- Modify: `dimelo/__init__.py` +- Create: `tests/test_region_discovery.py` + +- [ ] **Step 1: Write failing tests** + - Add tests for: + - `scan_genome(...)` basic behavior with a mocked window summary table. + - `scan_genome(...)` minimum coverage filtering. + - `scan_genome(...)` contig include/exclude handoff to tiled window builder. + - significance ranking fields (`score_value`, `p_value`, `adjusted_p_value`, `rank`). + +- [ ] **Step 2: Verify tests fail** + - Run the focused test subset from `tests/test_region_discovery.py`. + +- [ ] **Step 3: Implement minimal discovery scan** + - Add in `dimelo/region_discovery.py`: + - `scan_genome(...)` + - uses `global_analysis.build_window_summary(...)` + - requires exactly one motif in v1 + - supports `window_size`, `step_size`, `include_contigs`, `exclude_contigs` + - applies `min_coverage` filter (`valid_count >= min_coverage`) + - computes a simple v1 discovery score: + - default `score="effect_size_only"`: rank by window fraction across contrast groups + - support `score="beta_binomial"` pooled approximation for matched group comparisons + - computes BH-adjusted p-values when p-values are present + - returns a `RegionDiscoveryResult` with: + - `windows`: per-window evidence table + - `hits`: ranked significant windows + - `plot_data`: at least `window_score_table`, `top_hits_table` + - metadata with scan policy and thresholds + - Keep deterministic ordering with stable sort keys. + +- [ ] **Step 4: Verify tests pass** + - Run: + - `pytest tests/test_region_discovery.py -q` + +- [ ] **Step 5: Commit** + - `git add dimelo/region_discovery.py dimelo/__init__.py tests/test_region_discovery.py` + - `git commit -m "feat: add deterministic region discovery scan"` + +### Task 3: Add Hit Merging And BED Export Helpers + +**Files:** +- Modify: `dimelo/region_discovery.py` +- Modify: `tests/test_region_discovery.py` + +- [ ] **Step 1: Write failing tests** + - Add tests for: + - `merge_adjacent_hits(...)` merges neighboring significant windows by chromosome and merge distance. + - `merge_adjacent_hits(...)` preserves score/rank summary fields deterministically. + - `hits_to_bed(...)` exports required BED columns and ordering. + +- [ ] **Step 2: Verify tests fail** + - Run focused tests for merge/export helpers. + +- [ ] **Step 3: Implement minimal helpers** + - Add: + - `merge_adjacent_hits(hits: pd.DataFrame, merge_distance: int) -> pd.DataFrame` + - `hits_to_bed(hits: pd.DataFrame) -> pd.DataFrame` + - Ensure merge happens after per-window scoring/FDR as per spec policy. + +- [ ] **Step 4: Verify tests pass** + - Run: + - `pytest tests/test_region_discovery.py -q` + +- [ ] **Step 5: Commit** + - `git add dimelo/region_discovery.py tests/test_region_discovery.py` + - `git commit -m "feat: add region discovery merge and bed export helpers"` + +### Task 4: Add User Guide And End-to-End Result Wrapper Polish + +**Files:** +- Modify: `dimelo/region_discovery.py` +- Modify: `tests/test_region_discovery.py` +- Create: `docs/region-discovery.md` +- Modify: `README.md` + +- [ ] **Step 1: Write failing tests** + - Add/extend a test to verify `scan_genome(...)` returns `RegionDiscoveryResult` with expected `plot_data` keys and metadata policy fields. + +- [ ] **Step 2: Verify tests fail** + - Run the focused test(s) first. + +- [ ] **Step 3: Implement wrapper polish and docs** + - Ensure `scan_genome(...)` includes metadata fields for: + - `analysis_unit` + - `representation` + - `signal_source` + - `score` + - `window_size` + - `step_size` + - `min_coverage` + - `merge_hits` + - `merge_distance` + - Add `docs/region-discovery.md` with: + - when to use discovery vs contrasts + - minimal usage example + - handoff to `region_contrasts`/clustering + - Link the guide from README analysis guides. + +- [ ] **Step 4: Verify tests pass** + - Run: + - `pytest tests/test_region_discovery.py tests/test_models.py -q` + +- [ ] **Step 5: Commit** + - `git add dimelo/region_discovery.py tests/test_region_discovery.py docs/region-discovery.md README.md` + - `git commit -m "docs: add region discovery workflow guide"` + +--- + +## Verification And Review Gates + +- After each task: + - Run the task-focused pytest subset. + - Run spec-compliance review. + - Run code-quality review. +- After Task 4: + - Run: + - `pytest tests/test_region_discovery.py tests/test_models.py tests/test_global_analysis.py tests/test_region_contrasts.py tests/test_workflows.py -q` + +## V1 Defaults To Preserve + +- deterministic tiled-window discovery +- canonical contigs by default with include/exclude override +- minimum coverage threshold before scoring +- FDR on raw windows before merge +- merge significant windows afterward with fixed merge distance +- data-first result tables with optional Matplotlib-agnostic plot payloads +- additive architecture around existing parsing/load modules diff --git a/docs/superpowers/plans/2026-03-31-shared-clustering-foundations.md b/docs/superpowers/plans/2026-03-31-shared-clustering-foundations.md new file mode 100644 index 0000000..5b8c5b4 --- /dev/null +++ b/docs/superpowers/plans/2026-03-31-shared-clustering-foundations.md @@ -0,0 +1,988 @@ +# Shared Clustering Foundations Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a stable, backward-compatible foundation for shared clustering across datasets, including result models, artifact resolution, data-first plotting payloads, and a usable `shared_cluster_distribution()` workflow covering `read_global` and `region_anchored`. + +**Architecture:** Build new orchestration and data-model modules around the existing `parse_bam`, `load_processed`, and `cluster` code without changing their semantics. Keep canonical outputs as DataFrames plus `plot_data`, with Matplotlib remaining a thin compatibility renderer instead of the core contract. + +**Tech Stack:** Python 3.11, pandas, numpy, scikit-learn, matplotlib, pytest + +--- + +Scope note: + +- This plan intentionally covers the first executable slice only: + shared clustering foundations and orchestration. +- Follow-on plans should handle: + `region_contrasts`, + `region_discovery`, + and `global_analysis`. + +### Task 1: Add Shared Clustering Models And Public Exports + +**Files:** +- Create: `dimelo/models.py` +- Create: `tests/test_models.py` +- Modify: `dimelo/__init__.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_models.py +from pathlib import Path + +from dimelo.models import ( + CohortSpec, + DatasetArtifact, + SampleSpec, + SharedClusterModel, + SharedClusterResult, +) + + +def test_sample_spec_basic_fields(): + sample = SampleSpec( + sample_id="s1", + condition="NS", + extract_h5=Path("reads.h5"), + regions_bed=Path("regions.bed"), + replicate=1, + ) + assert sample.sample_id == "s1" + assert sample.condition == "NS" + assert sample.extract_h5 == Path("reads.h5") + + +def test_dataset_artifact_carries_core_metadata(): + artifact = DatasetArtifact( + sample_id="s1", + artifact_type="read_features", + path=Path("features.parquet"), + format="parquet", + params={"window_size": 1000}, + provenance={"schema_version": 1, "package_version": "1.0.0"}, + ) + assert artifact.artifact_type == "read_features" + assert artifact.provenance["schema_version"] == 1 + + +def test_shared_cluster_result_holds_plot_data(): + model = SharedClusterModel( + mode="read_global", + motifs=["A,0"], + feature_names=["feat1"], + preprocessing={}, + estimator=None, + cluster_labels=["C0"], + fit_metadata={}, + ) + result = SharedClusterResult( + model=model, + assignments=None, + cluster_distribution=None, + condition_distribution=None, + distribution_change=None, + cluster_profiles=None, + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={}, + ) + assert "cluster_distribution_bar" in result.plot_data + + +def test_cohort_spec_tracks_workflow_and_params(): + cohort = CohortSpec( + cohort_id="cohort-1", + sample_ids=["s1", "s2"], + workflow="shared_cluster_distribution", + params={"mode": "read_global"}, + ) + assert cohort.workflow == "shared_cluster_distribution" + assert cohort.params["mode"] == "read_global" +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_models.py -q` +Expected: FAIL with `ModuleNotFoundError: No module named 'dimelo.models'` + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/models.py +from __future__ import annotations + +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +import pandas as pd + + +@dataclass +class SampleSpec: + sample_id: str + condition: str + extract_h5: str | Path + regions_bed: str | Path | None = None + replicate: int | None = None + metadata: dict[str, Any] | None = None + + +@dataclass +class DatasetArtifact: + sample_id: str + artifact_type: str + path: str | Path + format: str + params: dict[str, Any] + provenance: dict[str, Any] + + +@dataclass +class SharedClusterModel: + mode: str + motifs: list[str] + feature_names: list[str] + preprocessing: dict[str, Any] + estimator: Any + cluster_labels: list[str] + fit_metadata: dict[str, Any] + + +@dataclass +class SharedClusterResult: + model: SharedClusterModel + assignments: pd.DataFrame | None + cluster_distribution: pd.DataFrame | None + condition_distribution: pd.DataFrame | None + distribution_change: pd.DataFrame | None + cluster_profiles: pd.DataFrame | None + region_summaries: pd.DataFrame | None + plot_data: dict[str, pd.DataFrame | dict[str, Any]] + figures: dict[str, Any] = field(default_factory=dict) + metadata: dict[str, Any] = field(default_factory=dict) + + +@dataclass +class CohortSpec: + cohort_id: str + sample_ids: list[str] + workflow: str + params: dict[str, Any] + metadata: dict[str, Any] | None = None + + +@dataclass +class BatchJob: + job_id: str + workflow: str + cohorts: list[CohortSpec] + artifact_policy: str = "prefer_cached" + metadata: dict[str, Any] | None = None +``` + +```python +# dimelo/__init__.py +from . import ( + cluster, + export, + load_processed, + models, + parse_bam, + plot_depth_histogram, + plot_depth_profile, + plot_enrichment, + plot_enrichment_profile, + plot_read_browser, + plot_reads, +) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_models.py -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/models.py dimelo/__init__.py tests/test_models.py +git commit -m "feat: add shared clustering model types" +``` + +### Task 2: Add Artifact Compatibility And Resolver Foundations + +**Files:** +- Create: `dimelo/artifacts.py` +- Create: `tests/test_artifacts.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_artifacts.py +from pathlib import Path + +from dimelo.artifacts import artifact_fingerprint, artifact_is_compatible, resolve_artifact +from dimelo.models import DatasetArtifact + + +def test_artifact_fingerprint_includes_required_keys(): + fp = artifact_fingerprint( + schema_version=1, + package_version="1.0.0", + source_files=[Path("reads.h5")], + params={"window_size": 1000, "motifs": ["A,0"]}, + ) + assert fp["schema_version"] == 1 + assert "params_hash" in fp + assert "source_files" in fp + + +def test_artifact_is_compatible_rejects_param_mismatch(): + artifact = DatasetArtifact( + sample_id="s1", + artifact_type="read_features", + path=Path("features.parquet"), + format="parquet", + params={"window_size": 1000, "motifs": ["A,0"]}, + provenance={"schema_version": 1, "package_version": "1.0.0"}, + ) + assert not artifact_is_compatible( + artifact, + required_params={"window_size": 2000, "motifs": ["A,0"]}, + required_provenance={"schema_version": 1}, + ) + + +def test_resolve_artifact_prefers_matching_artifact(): + artifact = DatasetArtifact( + sample_id="s1", + artifact_type="read_features", + path=Path("features.parquet"), + format="parquet", + params={"window_size": 1000}, + provenance={"schema_version": 1, "package_version": "1.0.0"}, + ) + resolved = resolve_artifact( + artifacts=[artifact], + artifact_type="read_features", + sample_id="s1", + required_params={"window_size": 1000}, + required_provenance={"schema_version": 1}, + policy="prefer_cached", + ) + assert resolved is artifact +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_artifacts.py -q` +Expected: FAIL with `ModuleNotFoundError: No module named 'dimelo.artifacts'` + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/artifacts.py +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +from typing import Any, Iterable + +from .models import DatasetArtifact + + +def _stable_json(value: Any) -> str: + return json.dumps(value, sort_keys=True, separators=(",", ":")) + + +def artifact_fingerprint( + *, + schema_version: int, + package_version: str, + source_files: Iterable[str | Path], + params: dict[str, Any], +) -> dict[str, Any]: + source_list = [str(Path(p)) for p in source_files] + params_hash = hashlib.sha256(_stable_json(params).encode()).hexdigest() + return { + "schema_version": schema_version, + "package_version": package_version, + "source_files": source_list, + "params_hash": params_hash, + } + + +def artifact_is_compatible( + artifact: DatasetArtifact, + *, + required_params: dict[str, Any], + required_provenance: dict[str, Any], +) -> bool: + for key, value in required_params.items(): + if artifact.params.get(key) != value: + return False + for key, value in required_provenance.items(): + if artifact.provenance.get(key) != value: + return False + return True + + +def resolve_artifact( + *, + artifacts: list[DatasetArtifact], + artifact_type: str, + sample_id: str, + required_params: dict[str, Any], + required_provenance: dict[str, Any], + policy: str = "prefer_cached", +) -> DatasetArtifact | None: + matches = [ + artifact + for artifact in artifacts + if artifact.sample_id == sample_id and artifact.artifact_type == artifact_type + ] + if policy == "rebuild": + return None + compatible = [ + artifact + for artifact in matches + if artifact_is_compatible( + artifact, + required_params=required_params, + required_provenance=required_provenance, + ) + ] + if compatible: + return compatible[0] + if policy == "require_cached": + raise FileNotFoundError( + f"No compatible cached artifact found for sample={sample_id}, type={artifact_type}" + ) + return None +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_artifacts.py -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/artifacts.py tests/test_artifacts.py +git commit -m "feat: add artifact compatibility helpers" +``` + +### Task 3: Add Distribution Summaries And Data-First Plot Payloads + +**Files:** +- Create: `dimelo/distribution.py` +- Create: `dimelo/plotting.py` +- Create: `tests/test_distribution.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_distribution.py +import pandas as pd + +from dimelo.distribution import ( + build_cluster_distribution, + build_condition_distribution, + build_distribution_change, +) +from dimelo.plotting import ( + prepare_cluster_distribution_bar_data, + prepare_cluster_distribution_heatmap_data, +) + + +def test_build_cluster_distribution_counts_and_fractions(): + assignments = pd.DataFrame( + { + "sample_id": ["s1", "s1", "s1", "s2"], + "condition": ["NS", "NS", "NS", "15min"], + "cluster": ["C0", "C0", "C1", "C1"], + } + ) + result = build_cluster_distribution(assignments) + s1 = result[result["sample_id"] == "s1"].sort_values("cluster").reset_index(drop=True) + assert list(s1["count"]) == [2, 1] + assert list(s1["fraction"]) == [2 / 3, 1 / 3] + + +def test_build_distribution_change_against_reference(): + condition_distribution = pd.DataFrame( + { + "condition": ["NS", "NS", "15min", "15min"], + "cluster": ["C0", "C1", "C0", "C1"], + "fraction": [0.75, 0.25, 0.25, 0.75], + } + ) + result = build_distribution_change(condition_distribution, reference_condition="NS") + row = result[(result["condition"] == "15min") & (result["cluster"] == "C1")].iloc[0] + assert row["delta_fraction"] == 0.5 + + +def test_plot_data_helpers_return_dataframes(): + cluster_distribution = pd.DataFrame( + { + "sample_id": ["s1", "s1"], + "condition": ["NS", "NS"], + "cluster": ["C0", "C1"], + "count": [2, 1], + "fraction": [2 / 3, 1 / 3], + } + ) + bar_data = prepare_cluster_distribution_bar_data(cluster_distribution) + heatmap_data = prepare_cluster_distribution_heatmap_data(cluster_distribution) + assert not bar_data.empty + assert not heatmap_data.empty +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_distribution.py -q` +Expected: FAIL with `ModuleNotFoundError` for `dimelo.distribution` + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/distribution.py +from __future__ import annotations + +import numpy as np +import pandas as pd + + +def build_cluster_distribution(assignments: pd.DataFrame) -> pd.DataFrame: + grouped = ( + assignments.groupby(["sample_id", "condition", "cluster"]) + .size() + .reset_index(name="count") + ) + totals = grouped.groupby("sample_id")["count"].transform("sum") + grouped["fraction"] = grouped["count"] / totals + return grouped + + +def build_condition_distribution(cluster_distribution: pd.DataFrame) -> pd.DataFrame: + grouped = ( + cluster_distribution.groupby(["condition", "cluster"])["count"] + .sum() + .reset_index() + ) + totals = grouped.groupby("condition")["count"].transform("sum") + grouped["fraction"] = grouped["count"] / totals + grouped["replicate_n"] = ( + cluster_distribution.groupby("condition")["sample_id"].nunique().reindex(grouped["condition"]).to_numpy() + ) + return grouped + + +def build_distribution_change( + condition_distribution: pd.DataFrame, + *, + reference_condition: str, + pseudocount: float = 1e-6, +) -> pd.DataFrame: + ref = ( + condition_distribution[condition_distribution["condition"] == reference_condition] + .loc[:, ["cluster", "fraction"]] + .rename(columns={"fraction": "reference_fraction"}) + ) + merged = condition_distribution.merge(ref, on="cluster", how="left") + merged = merged[merged["condition"] != reference_condition].copy() + merged["delta_fraction"] = merged["fraction"] - merged["reference_fraction"] + merged["log2_fc"] = np.log2( + (merged["fraction"] + pseudocount) / (merged["reference_fraction"] + pseudocount) + ) + return merged +``` + +```python +# dimelo/plotting.py +from __future__ import annotations + +import pandas as pd + + +def prepare_cluster_distribution_bar_data(cluster_distribution: pd.DataFrame) -> pd.DataFrame: + return cluster_distribution.copy() + + +def prepare_cluster_distribution_heatmap_data(cluster_distribution: pd.DataFrame) -> pd.DataFrame: + return cluster_distribution.pivot_table( + index="condition", + columns="cluster", + values="fraction", + fill_value=0.0, + ) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_distribution.py -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/distribution.py dimelo/plotting.py tests/test_distribution.py +git commit -m "feat: add shared cluster summary and plot payload helpers" +``` + +### Task 4: Implement `shared_cluster_distribution()` For `read_global` + +**Files:** +- Create: `dimelo/workflows.py` +- Modify: `dimelo/cluster.py` +- Modify: `dimelo/__init__.py` +- Create: `tests/test_workflows.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_workflows.py +import numpy as np +import pandas as pd + +from dimelo.models import SampleSpec +from dimelo import workflows + + +def test_shared_cluster_distribution_read_global(monkeypatch): + fake_samples = [ + SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5"), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5"), + ] + + def fake_extract(*args, **kwargs): + class R: + data_matrix = np.array([[0, 1, 0, 1], [1, 0, 1, 0]], dtype=float) + val_matrix = np.ones((2, 4), dtype=float) + metadata = [ + {"read_name": "r1", "chromosome": "chr1", "region_start": 0, "region_end": 4}, + {"read_name": "r2", "chromosome": "chr1", "region_start": 10, "region_end": 14}, + ] + datasets = [] + regions_dict = None + + return R() + + def fake_features(result, **kwargs): + return result.data_matrix, ["f0", "f1", "f2", "f3"] + + monkeypatch.setattr(workflows.cluster, "extract_read_windows", fake_extract) + monkeypatch.setattr(workflows.cluster, "read_window_feature_matrix", fake_features) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="read_global", + motifs=["A,0"], + n_clusters=2, + training_sample_per_dataset=2, + make_plots=False, + ) + + assert not result.assignments.empty + assert set(result.assignments["sample_id"]) == {"s1", "s2"} + assert "cluster_distribution_bar" in result.plot_data +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_workflows.py -q` +Expected: FAIL with `ModuleNotFoundError: No module named 'dimelo.workflows'` + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/workflows.py +from __future__ import annotations + +import numpy as np +import pandas as pd + +from . import cluster, distribution, plotting +from .models import SharedClusterModel, SharedClusterResult + + +def shared_cluster_distribution( + *, + samples, + mode: str, + motifs, + matched_regions=None, + signal_normalization: str = "none", + feature_scaling: str = "robust_zscore", + cluster_basis: str = "shape_plus_level", + clusterer: str = "minibatch_kmeans", + n_clusters: int = 8, + training_sample_per_dataset: int = 100_000, + artifact_policy: str = "prefer_cached", + random_state: int = 42, + make_plots: bool = True, +) -> SharedClusterResult: + if mode != "read_global": + raise NotImplementedError("First slice implements read_global only.") + + assignments = [] + feature_names = None + matrices = [] + sample_meta = [] + + for sample in samples: + extracted = cluster.extract_read_windows( + hdf5_file=sample.extract_h5, + motifs=motifs, + ) + feature_matrix, feature_names = cluster.read_window_feature_matrix(extracted) + matrices.append(feature_matrix) + sample_meta.extend( + [ + { + "sample_id": sample.sample_id, + "condition": sample.condition, + "replicate": sample.replicate, + **meta, + } + for meta in extracted.metadata + ] + ) + + all_features = pd.DataFrame( + data=np.vstack(matrices), + columns=feature_names, + ) + result = cluster.cluster_read_windows( + all_features.to_numpy(), + method=clusterer, + n_clusters=n_clusters, + random_state=random_state, + ) + + for meta, label in zip(sample_meta, result.labels_size_ordered): + assignments.append({**meta, "cluster": f"C{int(label)}"}) + + assignments_df = pd.DataFrame(assignments) + cluster_distribution = distribution.build_cluster_distribution(assignments_df) + condition_distribution = distribution.build_condition_distribution(cluster_distribution) + plot_data = { + "cluster_distribution_bar": plotting.prepare_cluster_distribution_bar_data( + cluster_distribution + ), + "cluster_distribution_heatmap": plotting.prepare_cluster_distribution_heatmap_data( + cluster_distribution + ), + } + model = SharedClusterModel( + mode=mode, + motifs=list(motifs), + feature_names=list(feature_names or []), + preprocessing={ + "signal_normalization": signal_normalization, + "feature_scaling": feature_scaling, + "cluster_basis": cluster_basis, + }, + estimator=result.model, + cluster_labels=sorted(assignments_df["cluster"].unique()), + fit_metadata={ + "clusterer": clusterer, + "n_clusters": n_clusters, + "artifact_policy": artifact_policy, + "training_sample_per_dataset": training_sample_per_dataset, + }, + ) + return SharedClusterResult( + model=model, + assignments=assignments_df, + cluster_distribution=cluster_distribution, + condition_distribution=condition_distribution, + distribution_change=None, + cluster_profiles=None, + region_summaries=None, + plot_data=plot_data, + figures={}, + metadata={"mode": mode}, + ) +``` + +```python +# dimelo/__init__.py +from . import ( + cluster, + distribution, + export, + load_processed, + models, + parse_bam, + plotting, + workflows, + plot_depth_histogram, + plot_depth_profile, + plot_enrichment, + plot_enrichment_profile, + plot_read_browser, + plot_reads, +) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_workflows.py -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/workflows.py tests/test_workflows.py +git commit -m "feat: add read-global shared clustering workflow" +``` + +### Task 5: Add `region_anchored` Support And Backward-Compatibility Regression Coverage + +**Files:** +- Create: `dimelo/region_analysis.py` +- Modify: `dimelo/workflows.py` +- Modify: `tests/test_workflows.py` +- Modify: `tests/test_cluster.py` + +- [ ] **Step 1: Write the failing tests** + +```python +# tests/test_workflows.py +import numpy as np + +from dimelo.models import SampleSpec +from dimelo import workflows + + +def test_shared_cluster_distribution_region_anchored(monkeypatch): + fake_samples = [ + SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5", regions_bed="r1.bed"), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5", regions_bed="r2.bed"), + ] + + def fake_region_table(*args, **kwargs): + return np.array([[0.2, 0.8], [0.7, 0.3]]), [ + {"region_id": "reg1", "sample_id": "s1", "condition": "NS"}, + {"region_id": "reg1", "sample_id": "s2", "condition": "15min"}, + ] + + monkeypatch.setattr(workflows.region_analysis, "build_region_feature_table", fake_region_table) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="region_anchored", + motifs=["A,0"], + matched_regions="matched.bed", + n_clusters=2, + make_plots=False, + ) + assert not result.assignments.empty + assert "region_id" in result.assignments.columns +``` + +```python +# tests/test_cluster.py +def test_existing_cluster_result_api_still_returns_metrics(): + rng = np.random.default_rng(0) + feature_matrix = rng.normal(size=(10, 4)) + result = cluster.cluster_read_windows(feature_matrix, method="kmeans", n_clusters=2, random_state=0) + assert "silhouette" in result.metrics +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pytest tests/test_workflows.py tests/test_cluster.py -q` +Expected: FAIL with `AttributeError` for missing `build_region_feature_table` or missing `region_anchored` branch + +- [ ] **Step 3: Write minimal implementation** + +```python +# dimelo/region_analysis.py +from __future__ import annotations + +from typing import Any, Sequence + +import numpy as np +import pandas as pd + +from . import cluster + + +def build_region_feature_table( + *, + samples, + motifs: Sequence[str], + matched_regions, +): + matrices = [] + metadata = [] + for sample in samples: + matrix, region_meta = cluster.region_feature_matrix_from_pileup( + bedmethyl_file=sample.metadata["pileup_path"], + motif=motifs[0], + regions=matched_regions or sample.regions_bed, + ) + matrices.append(matrix) + metadata.extend( + [ + { + "sample_id": sample.sample_id, + "condition": sample.condition, + "replicate": sample.replicate, + "region_id": f"{chrom}:{start}-{end}", + "chromosome": chrom, + "start": start, + "end": end, + "strand": strand, + } + for chrom, start, end, strand in region_meta + ] + ) + return np.vstack(matrices), metadata +``` + +```python +# dimelo/workflows.py + if mode == "region_anchored": + feature_matrix, metadata_rows = region_analysis.build_region_feature_table( + samples=samples, + motifs=motifs, + matched_regions=matched_regions, + ) + result = cluster.cluster_read_windows( + feature_matrix, + method=clusterer, + n_clusters=n_clusters, + random_state=random_state, + ) + assignments_df = pd.DataFrame(metadata_rows) + assignments_df["cluster"] = [f"C{int(x)}" for x in result.labels_size_ordered] + cluster_distribution = distribution.build_cluster_distribution(assignments_df) + condition_distribution = distribution.build_condition_distribution(cluster_distribution) + return SharedClusterResult( + model=SharedClusterModel( + mode=mode, + motifs=list(motifs), + feature_names=[f"pos_{i}" for i in range(feature_matrix.shape[1])], + preprocessing={ + "signal_normalization": signal_normalization, + "feature_scaling": feature_scaling, + "cluster_basis": cluster_basis, + }, + estimator=result.model, + cluster_labels=sorted(assignments_df["cluster"].unique()), + fit_metadata={"clusterer": clusterer, "n_clusters": n_clusters}, + ), + assignments=assignments_df, + cluster_distribution=cluster_distribution, + condition_distribution=condition_distribution, + distribution_change=None, + cluster_profiles=None, + region_summaries=assignments_df, + plot_data={ + "cluster_distribution_bar": plotting.prepare_cluster_distribution_bar_data(cluster_distribution), + "cluster_distribution_heatmap": plotting.prepare_cluster_distribution_heatmap_data(cluster_distribution), + }, + figures={}, + metadata={"mode": mode}, + ) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pytest tests/test_workflows.py tests/test_cluster.py -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add dimelo/region_analysis.py dimelo/workflows.py tests/test_workflows.py tests/test_cluster.py +git commit -m "feat: add region-anchored shared clustering workflow" +``` + +### Task 6: Add Usage Docs For Backward-Compatible Workflow Entry + +**Files:** +- Create: `docs/shared-clustering.md` +- Modify: `README.md` + +- [ ] **Step 1: Write the failing docs check** + +```markdown + +# Shared Clustering + +This file should explain: +- when to run `parse_bam.extract()` +- when to run `parse_bam.pileup()` +- when to run both +- how to call `shared_cluster_distribution()` +- how to plot from returned tables without package renderers +``` + +- [ ] **Step 2: Verify the docs do not yet exist** + +Run: `test -f docs/shared-clustering.md` +Expected: non-zero exit status + +- [ ] **Step 3: Write the docs** + +```markdown +# Shared Clustering + +## Preprocessing Choices + +- Run `parse_bam.extract()` when you want read-level clustering or single-read pattern analysis. +- Run `parse_bam.pileup()` when you want region-anchored clustering from pileup-derived summaries. +- Run both when you want abundance-style region summaries plus deeper read-level follow-up. + +## Workflow Entry Point + +```python +from dimelo import workflows +from dimelo.models import SampleSpec + +result = workflows.shared_cluster_distribution( + samples=[ + SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5"), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5"), + ], + mode="read_global", + motifs=["A,0"], + n_clusters=8, +) +``` + +## Custom Plotting + +The canonical outputs are: + +- `result.assignments` +- `result.cluster_distribution` +- `result.condition_distribution` +- `result.plot_data` + +You can use these directly in Matplotlib, seaborn, Plotly, Altair, or your own plotting stack. +``` +``` + +- [ ] **Step 4: Review the docs render and match the implemented API** + +Run: `sed -n '1,220p' docs/shared-clustering.md` +Expected: shows preprocessing guidance, workflow entry point, and custom plotting guidance + +- [ ] **Step 5: Commit** + +```bash +git add docs/shared-clustering.md README.md +git commit -m "docs: add shared clustering usage guide" +``` diff --git a/docs/superpowers/plans/2026-04-01-cluster-occupancy-region-contrasts.md b/docs/superpowers/plans/2026-04-01-cluster-occupancy-region-contrasts.md new file mode 100644 index 0000000..7f89c20 --- /dev/null +++ b/docs/superpowers/plans/2026-04-01-cluster-occupancy-region-contrasts.md @@ -0,0 +1,296 @@ +# Cluster Occupancy Region Contrasts Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Extend `dimelo.region_contrasts` to support region-level cluster occupancy contrasts over sample-level occupancy summaries, including per-cluster fractions plus descriptive dominant-cluster and entropy outputs, with inferential support for per-cluster fractions in `pairwise` and `group_vs_group` modes. + +**Architecture:** Keep the current `score_regions(...)` entry point and extend it with a second validated path for `analysis_unit="cluster_occupancy"`. Build one occupancy evidence table from clustering-derived region summaries, then score per-cluster fraction contrasts across `region x sample` observations while also emitting descriptive dominant-cluster and entropy summaries in the result tables. + +**Tech Stack:** Python, pandas, existing dimelo contrast models, pytest + +--- + +### Task 1: Extend Validation And Occupancy Evidence Builders + +**Files:** +- Modify: `dimelo/region_contrasts.py` +- Test: `tests/test_region_contrasts.py` + +- [ ] **Step 1: Write the failing validation and evidence-table tests** + +```python +def test_validate_region_contrast_request_accepts_cluster_occupancy_fraction_mode(): + region_contrasts.validate_region_contrast_request( + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="effect_size_only", + ) + + +def test_validate_region_contrast_request_rejects_beta_binomial_for_cluster_occupancy(): + with pytest.raises(ValueError, match="cluster_occupancy"): + region_contrasts.validate_region_contrast_request( + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="beta_binomial", + ) + + +def test_build_cluster_occupancy_evidence_table_summarizes_region_sample_clusters(): + evidence = region_contrasts.build_cluster_occupancy_evidence_table( + region_summaries=_mock_region_summaries(), + ) + assert {"region_id", "sample_id", "condition", "cluster", "fraction"} <= set(evidence.columns) + assert "dominant_cluster" in evidence.columns + assert "cluster_entropy" in evidence.columns +``` + +- [ ] **Step 2: Run the targeted tests to verify they fail** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -q` +Expected: FAIL with missing builder / validation support + +- [ ] **Step 3: Extend request validation** + +```python +def validate_region_contrast_request(...): + if analysis_unit == "ensemble_region": + ... + return + if analysis_unit == "cluster_occupancy": + if signal_source != "cluster_occupancy": + raise ValueError(...) + if representation not in {"cluster_fraction", "dominant_cluster", "cluster_entropy"}: + raise ValueError(...) + if test not in {"effect_size_only", "fraction_test"}: + raise ValueError(...) + return + raise ValueError(...) +``` + +- [ ] **Step 4: Add the occupancy evidence builder** + +```python +def build_cluster_occupancy_evidence_table(*, region_summaries: pd.DataFrame) -> pd.DataFrame: + required = {"region_id", "sample_id", "condition", "cluster", "count", "fraction"} + missing = required - set(region_summaries.columns) + if missing: + raise ValueError(...) + + evidence = region_summaries.copy() + totals = evidence.groupby(["region_id", "sample_id", "condition"])["count"].transform("sum") + evidence["fraction"] = evidence["count"].div(totals.where(totals != 0), fill_value=0).fillna(0.0) + + dominant = ( + evidence.sort_values(["region_id", "sample_id", "condition", "fraction", "cluster"], + ascending=[True, True, True, False, True]) + .drop_duplicates(["region_id", "sample_id", "condition"]) + .loc[:, ["region_id", "sample_id", "condition", "cluster"]] + .rename(columns={"cluster": "dominant_cluster"}) + ) + entropy = ( + evidence.groupby(["region_id", "sample_id", "condition"], as_index=False) + .agg(cluster_entropy=("fraction", lambda values: float(-(values * np.log2(values.where(values > 0, 1))).sum()))) + ) + return evidence.merge(dominant, ...).merge(entropy, ...) +``` + +- [ ] **Step 5: Run the targeted tests to verify they pass** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -q` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add dimelo/region_contrasts.py tests/test_region_contrasts.py +git commit -m "feat: add cluster occupancy contrast evidence builders" +``` + +### Task 2: Add Sample-Level Occupancy Scoring + +**Files:** +- Modify: `dimelo/region_contrasts.py` +- Test: `tests/test_region_contrasts.py` + +- [ ] **Step 1: Write the failing scoring tests** + +```python +def test_score_regions_cluster_fraction_effect_size_only_ranks_largest_fraction_shift_first(): + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec(mode="pairwise", numerator=["15min"], denominator=["NS"]), + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=_mock_cluster_occupancy_evidence(), + ) + assert result.regions.iloc[0]["cluster"] == "C1" + assert "delta_fraction" in result.regions.columns + + +def test_score_regions_cluster_fraction_fraction_test_adds_p_values(): + result = region_contrasts.score_regions( + ..., + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="fraction_test", + occupancy_table=_mock_cluster_occupancy_evidence(), + ) + assert {"p_value", "adjusted_p_value"} <= set(result.regions.columns) + + +def test_score_regions_cluster_entropy_returns_descriptive_summary_only(): + result = region_contrasts.score_regions( + ..., + analysis_unit="cluster_occupancy", + representation="cluster_entropy", + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=_mock_cluster_occupancy_evidence(), + ) + assert "cluster_entropy" in result.regions.columns + assert "p_value" not in result.regions.columns +``` + +- [ ] **Step 2: Run the targeted tests to verify they fail** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -q` +Expected: FAIL with unsupported cluster occupancy path + +- [ ] **Step 3: Implement occupancy scoring helpers** + +```python +def _pool_cluster_occupancy_groups(...): + ... + + +def _add_fraction_test_scores(regions_table: pd.DataFrame, *, multiple_testing: str) -> pd.DataFrame: + ... + + +def _score_cluster_occupancy(...): + if representation == "cluster_fraction": + ... + elif representation == "dominant_cluster": + ... + else: + ... +``` + +Rules for v1: +- inferential support only for `representation="cluster_fraction"` +- only `pairwise` and `group_vs_group` +- sample-level region occupancy is the unit; do not pool reads as pseudo-replicates +- `dominant_cluster` and `cluster_entropy` remain descriptive + +- [ ] **Step 4: Extend score_regions(...) with the new path** + +```python +def score_regions(..., occupancy_table: pd.DataFrame | None = None, ...): + validate_region_contrast_request(...) + if analysis_unit == "cluster_occupancy": + evidence = occupancy_table if occupancy_table is not None else build_cluster_occupancy_evidence_table(...) + ... + return RegionContrastResult(...) + ... +``` + +- [ ] **Step 5: Add rejection-path tests** + +```python +def test_score_regions_cluster_occupancy_rejects_matched_pairwise(): + ... + + +def test_score_regions_cluster_occupancy_rejects_beta_binomial(): + ... + + +def test_score_regions_cluster_occupancy_rejects_missing_occupancy_columns(): + ... +``` + +- [ ] **Step 6: Run the targeted tests to verify they pass** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -q` +Expected: PASS + +- [ ] **Step 7: Commit** + +```bash +git add dimelo/region_contrasts.py tests/test_region_contrasts.py +git commit -m "feat: add cluster occupancy region scoring" +``` + +### Task 3: Document Occupancy Contrasts And Verify The Slice + +**Files:** +- Modify: `docs/region-contrasts.md` +- Modify: `docs/shared-clustering.md` +- Modify: `README.md` +- Test: `tests/test_region_contrasts.py` + +- [ ] **Step 1: Add one occupancy-driven example** + +```python +result = region_contrasts.score_regions( + samples=samples, + regions=selected_regions, + motifs=["A,0"], + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + ), + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="fraction_test", + occupancy_table=occupancy_table, +) +``` + +- [ ] **Step 2: Make the v1 occupancy contract explicit** + +```markdown +- sample-level `region x sample` occupancy summaries are the unit of comparison +- `cluster_fraction` supports `effect_size_only` and `fraction_test` +- `dominant_cluster` and `cluster_entropy` are descriptive-only in v1 +- supported contrast modes are `pairwise` and `group_vs_group` +``` + +- [ ] **Step 3: Add/adjust one metadata test if needed** + +```python +def test_score_regions_cluster_occupancy_metadata_marks_signal_source(): + ... + assert result.metadata["analysis_unit"] == "cluster_occupancy" + assert result.metadata["signal_source"] == "cluster_occupancy" +``` + +- [ ] **Step 4: Run the verification slice** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py tests/test_workflows.py -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add docs/region-contrasts.md docs/shared-clustering.md README.md tests/test_region_contrasts.py +git commit -m "docs: add cluster occupancy contrast guide" +``` + +--- + +## Self-Review + +- Spec coverage: this plan adds the occupancy evidence builder, scoring path, inference/descriptive split, and docs for the new cluster-occupancy contrasts. +- Placeholder scan: every task names exact files, tests, and commands; there are no TODO placeholders. +- Type consistency: the same API terms are used throughout: `analysis_unit="cluster_occupancy"`, `representation in {"cluster_fraction", "dominant_cluster", "cluster_entropy"}`, `signal_source="cluster_occupancy"`, and `test in {"effect_size_only", "fraction_test"}`. diff --git a/docs/superpowers/plans/2026-04-01-discovery-cluster-contrast-workflow.md b/docs/superpowers/plans/2026-04-01-discovery-cluster-contrast-workflow.md new file mode 100644 index 0000000..1cba180 --- /dev/null +++ b/docs/superpowers/plans/2026-04-01-discovery-cluster-contrast-workflow.md @@ -0,0 +1,351 @@ +# Discovery Cluster Contrast Workflow Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add one end-to-end workflow that discovers loci, clusters the selected loci, runs defined-region contrasts on that same selected set, and returns all four outputs together with the full scan table as context. + +**Architecture:** Extend the current `discovery_cluster_workflow(...)` path instead of inventing a parallel orchestration stack. Add one wrapper result model for the combined outputs, then add one workflow entry point in `dimelo.workflows` that composes the existing `region_discovery.scan_genome(...)`, `shared_cluster_distribution(...)`, and `region_contrasts.score_regions(...)` functions in that order, using the selected loci as the default contrast region set. + +**Tech Stack:** Python dataclasses, pandas, existing dimelo discovery/clustering/contrast modules, pytest + +--- + +### Task 1: Add The Combined Result Model + +**Files:** +- Modify: `dimelo/models.py` +- Modify: `dimelo/__init__.py` +- Test: `tests/test_models.py` + +- [ ] **Step 1: Write the failing model tests** + +```python +def test_region_discovery_cluster_contrast_result_requires_non_none_values(): + with pytest.raises(ValueError, match="discovery"): + RegionDiscoveryClusterContrastResult( + discovery=None, + clustering=_dummy_shared_cluster_result(), + contrasts=_dummy_region_contrast_result(), + selected_regions=pd.DataFrame([{"chrom": "chr1", "start": 0, "end": 100}]), + metadata={}, + ) + + +def test_region_discovery_cluster_contrast_result_validates_wrapped_types(): + with pytest.raises(TypeError, match="RegionDiscoveryResult"): + RegionDiscoveryClusterContrastResult( + discovery={}, + clustering=_dummy_shared_cluster_result(), + contrasts=_dummy_region_contrast_result(), + selected_regions=pd.DataFrame([{"chrom": "chr1", "start": 0, "end": 100}]), + metadata={}, + ) + + +def test_region_discovery_cluster_contrast_result_accepts_valid_payloads(): + result = RegionDiscoveryClusterContrastResult( + discovery=_dummy_region_discovery_result(), + clustering=_dummy_shared_cluster_result(), + contrasts=_dummy_region_contrast_result(), + selected_regions=pd.DataFrame([{"chrom": "chr1", "start": 0, "end": 100}]), + metadata={"contrast_scope": "selected"}, + ) + assert result.metadata["contrast_scope"] == "selected" +``` + +- [ ] **Step 2: Run the model tests to verify they fail** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_models.py -q` +Expected: FAIL with `NameError` / import errors for `RegionDiscoveryClusterContrastResult` + +- [ ] **Step 3: Write the minimal model implementation** + +```python +@dataclass +class RegionDiscoveryClusterContrastResult: + discovery: RegionDiscoveryResult + clustering: SharedClusterResult + contrasts: RegionContrastResult + selected_regions: pd.DataFrame + metadata: dict[str, Any] = field(default_factory=dict) + figures: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "discovery": self.discovery, + "clustering": self.clustering, + "contrasts": self.contrasts, + "selected_regions": self.selected_regions, + "metadata": self.metadata, + "figures": self.figures, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "RegionDiscoveryClusterContrastResult requires non-None values for: " + f"{', '.join(missing)}" + ) + if not isinstance(self.discovery, RegionDiscoveryResult): + raise TypeError( + "RegionDiscoveryClusterContrastResult.discovery must be a RegionDiscoveryResult" + ) + if not isinstance(self.clustering, SharedClusterResult): + raise TypeError( + "RegionDiscoveryClusterContrastResult.clustering must be a SharedClusterResult" + ) + if not isinstance(self.contrasts, RegionContrastResult): + raise TypeError( + "RegionDiscoveryClusterContrastResult.contrasts must be a RegionContrastResult" + ) +``` + +- [ ] **Step 4: Export the new model from the package root** + +```python +from .models import RegionDiscoveryClusterContrastResult +... +__all__ = [ + ... + "RegionDiscoveryClusterContrastResult", +] +``` + +- [ ] **Step 5: Run the model tests to verify they pass** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_models.py -q` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add dimelo/models.py dimelo/__init__.py tests/test_models.py +git commit -m "feat: add discovery cluster contrast result" +``` + +### Task 2: Add The End-To-End Workflow + +**Files:** +- Modify: `dimelo/workflows.py` +- Test: `tests/test_workflows.py` + +- [ ] **Step 1: Write the failing workflow tests** + +```python +def test_discovery_cluster_contrast_workflow_returns_all_results(monkeypatch): + monkeypatch.setattr(workflows.region_discovery, "scan_genome", _mock_discovery_result) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + monkeypatch.setattr(workflows.region_contrasts, "score_regions", _mock_region_contrast_result) + + result = workflows.discovery_cluster_contrast_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + contrasts={ + "contrast": ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + ), + "test": "effect_size_only", + }, + ) + + assert result.discovery.hits.shape[0] == 3 + assert result.clustering.model.mode == "region_anchored" + assert result.contrasts.metadata["test"] == "effect_size_only" + assert result.metadata["contrast_scope"] == "selected" +``` + +```python +def test_discovery_cluster_contrast_workflow_scores_selected_regions_by_default(monkeypatch): + captured = {} + ... + def fake_score_regions(**kwargs): + captured["regions"] = kwargs["regions"] + return _mock_region_contrast_result(**kwargs) + + ... + result = workflows.discovery_cluster_contrast_workflow(...) + assert captured["regions"] == ["chr1:0-500,+", "chr1:500-1000,-"] + assert result.selected_regions["name"].tolist() == ["chr1:0-500", "chr1:500-1000"] +``` + +```python +def test_discovery_cluster_contrast_workflow_rejects_missing_contrast_config(monkeypatch): + ... + with pytest.raises(ValueError, match="requires contrasts\\['contrast'\\]"): + workflows.discovery_cluster_contrast_workflow(...) +``` + +- [ ] **Step 2: Run the workflow tests to verify they fail** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_workflows.py -q` +Expected: FAIL with missing `discovery_cluster_contrast_workflow` + +- [ ] **Step 3: Implement the orchestration workflow** + +```python +def discovery_cluster_contrast_workflow( + *, + samples: Iterable[SampleSpec], + motifs: Iterable[str], + genome_sizes: dict[str, int], + discovery: dict[str, Any], + clustering: dict[str, Any], + contrasts: dict[str, Any], + selection: dict[str, Any] | None = None, +) -> RegionDiscoveryClusterContrastResult: + sample_list = list(samples) + motif_list = list(motifs) + contrast_config = dict(contrasts) + if "contrast" not in contrast_config: + raise ValueError("discovery_cluster_contrast_workflow requires contrasts['contrast'].") + + discovery_cluster_result = discovery_cluster_workflow( + samples=sample_list, + motifs=motif_list, + genome_sizes=genome_sizes, + discovery=discovery, + clustering=clustering, + selection=selection, + ) + selected_region_spec = _selected_regions_to_region_spec( + discovery_cluster_result.selected_regions + ) + contrast_result = region_contrasts.score_regions( + samples=sample_list, + regions=selected_region_spec, + motifs=motif_list, + **contrast_config, + ) + return RegionDiscoveryClusterContrastResult( + discovery=discovery_cluster_result.discovery, + clustering=discovery_cluster_result.clustering, + contrasts=contrast_result, + selected_regions=discovery_cluster_result.selected_regions, + metadata={ + **discovery_cluster_result.metadata, + "contrast_scope": "selected", + "full_scan_windows": discovery_cluster_result.discovery.windows.copy(), + }, + ) +``` + +- [ ] **Step 4: Add the remaining workflow tests** + +```python +def test_discovery_cluster_contrast_workflow_preserves_full_scan_windows_context(monkeypatch): + ... + result = workflows.discovery_cluster_contrast_workflow(...) + assert result.metadata["full_scan_windows"].equals(result.discovery.windows) +``` + +```python +def test_discovery_cluster_contrast_workflow_fast_fails_invalid_contrast_before_reuse(monkeypatch): + called = {"scan": False} + def fake_scan_genome(**kwargs): + called["scan"] = True + return _mock_discovery_result() + + monkeypatch.setattr(workflows.region_discovery, "scan_genome", fake_scan_genome) + with pytest.raises(ValueError, match="analysis_unit='ensemble_region'"): + workflows.discovery_cluster_contrast_workflow( + ..., + contrasts={ + "contrast": ContrastSpec(mode="pairwise", numerator=["15min"], denominator=["NS"]), + "analysis_unit": "single_read", + }, + ) + assert called["scan"] is False +``` + +- [ ] **Step 5: Run the workflow tests to verify they pass** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_workflows.py -q` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add dimelo/workflows.py tests/test_workflows.py +git commit -m "feat: add discovery cluster contrast workflow" +``` + +### Task 3: Document The New Handoff And Run The Broader Slice + +**Files:** +- Modify: `docs/region-contrasts.md` +- Modify: `docs/region-discovery.md` +- Modify: `docs/shared-clustering.md` +- Modify: `README.md` +- Test: `tests/test_workflows.py` + +- [ ] **Step 1: Add one end-to-end doc example** + +```python +result = workflows.discovery_cluster_contrast_workflow( + samples=samples, + motifs=["A,0"], + genome_sizes={"chr1": 248_956_422}, + discovery={ + "window_size": 2000, + "step_size": 500, + "score": "beta_binomial", + "contrast": discovery_contrast, + }, + clustering={ + "mode": "region_anchored", + "n_clusters": 6, + }, + contrasts={ + "contrast": region_contrast, + "test": "beta_binomial", + }, + selection={"mode": "top_n", "top_n": 250}, +) + +selected = result.selected_regions +clustered = result.clustering.assignments +scored = result.contrasts.regions +windows = result.metadata["full_scan_windows"] +``` + +- [ ] **Step 2: Clarify the contract in docs** + +```markdown +- `result.selected_regions` is the BED-style selected follow-up set +- clustering receives a serializable region-spec derived from those rows +- `result.contrasts` scores the same selected loci by default +- `result.metadata["full_scan_windows"]` carries the full discovery scan for context +``` + +- [ ] **Step 3: Add/adjust one test if docs exposed a missing behavior** + +```python +def test_discovery_cluster_contrast_workflow_metadata_marks_selected_scope(monkeypatch): + ... + result = workflows.discovery_cluster_contrast_workflow(...) + assert result.metadata["contrast_scope"] == "selected" +``` + +- [ ] **Step 4: Run the broader verification slice** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_models.py tests/test_region_contrasts.py tests/test_region_discovery.py tests/test_workflows.py -q` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add docs/region-contrasts.md docs/region-discovery.md docs/shared-clustering.md README.md tests/test_workflows.py +git commit -m "docs: add discovery cluster contrast workflow guide" +``` + +--- + +## Self-Review + +- Spec coverage: this plan adds the combined result object, the selected-set-first orchestration workflow, validation for contrast config, and docs for the end-to-end discovery -> cluster -> contrast path. +- Placeholder scan: every task includes concrete files, tests, commands, and implementation snippets; there are no TODO placeholders. +- Type consistency: the plan consistently uses `RegionDiscoveryClusterContrastResult`, `discovery_cluster_contrast_workflow`, `selected_regions`, and `full_scan_windows` across tasks. diff --git a/docs/superpowers/plans/2026-04-01-paired-region-discovery.md b/docs/superpowers/plans/2026-04-01-paired-region-discovery.md new file mode 100644 index 0000000..a73cb23 --- /dev/null +++ b/docs/superpowers/plans/2026-04-01-paired-region-discovery.md @@ -0,0 +1,588 @@ +# Paired Region Discovery Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Extend `dimelo.region_discovery.scan_genome(...)` with explicit paired discovery support for matched two-condition scans and ordered paired time-course scans. + +**Architecture:** Keep the current tiled pileup-backed discovery path intact, then add a shared pairing layer inside `dimelo.region_discovery` that resolves complete matched units and builds an internal per-window paired table. Implement `matched_pairwise` first and `time_course` second, both returning the existing `RegionDiscoveryResult` shape with paired metadata and paired score columns. + +**Tech Stack:** Python 3.11, pandas, pytest + +--- + +## File Map + +- `dimelo/region_discovery.py` + Add pairing-resolution helpers, paired-window table helpers, `matched_pairwise` scoring, paired `time_course` scoring, and paired metadata emission. +- `tests/test_region_discovery.py` + Add regression tests for complete-pair filtering, strict missing-pair errors, paired ranking columns, paired metadata, time-order validation, and downstream handoff. +- `docs/region-discovery.md` + Document when to use pooled discovery vs paired discovery, required sample metadata, and paired examples. +- `README.md` + Update the discovery guide link text to mention paired discovery support. + +Scope note: + +- This plan only extends `region_discovery`. +- It does not add paired inferential models. +- It does not change `parse_bam`, `global_analysis`, `region_contrasts`, or existing pooled discovery semantics. +- It keeps `RegionDiscoveryResult` data-first and plotting-optional. + +### Task 1: Add Pair Resolution And Paired-Window Helpers + +**Files:** +- Modify: `dimelo/region_discovery.py` +- Modify: `tests/test_region_discovery.py` + +- [ ] **Step 1: Write the failing tests** + +```python +def test_scan_genome_matched_pairwise_uses_only_complete_pairs(monkeypatch): + monkeypatch.setattr(global_analysis, "build_window_summary", lambda **_: _mock_paired_window_summary()) + + result = region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + assert result.metadata["pairing_policy"] == "complete_pairs_only" + assert result.metadata["n_pairs_used"] == 2 + assert result.metadata["n_pairs_dropped"] == 1 + + +def test_scan_genome_time_course_errors_on_missing_pairing_key(monkeypatch): + monkeypatch.setattr(global_analysis, "build_window_summary", lambda **_: _mock_paired_window_summary()) + + with pytest.raises(ValueError, match="pairing_key"): + region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="time_course", + time_order=["0min", "15min"], + ), + score="effect_size_only", + ) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +pytest tests/test_region_discovery.py::test_scan_genome_matched_pairwise_uses_only_complete_pairs tests/test_region_discovery.py::test_scan_genome_time_course_errors_on_missing_pairing_key -q +``` + +Expected: FAIL because `scan_genome(...)` still rejects paired contrast modes and has no paired metadata or pairing validation path. + +- [ ] **Step 3: Write minimal implementation** + +```python +def _pairing_policy_value(pairing_policy: str | None) -> str: + return pairing_policy or "complete_pairs_only" + + +def _is_paired_contrast(contrast: ContrastSpec | None) -> bool: + return contrast is not None and contrast.mode in {"matched_pairwise", "time_course"} + + +def _resolve_pair_ids(samples, pairing_key: str) -> dict[str, object]: + pair_ids: dict[str, object] = {} + for sample in samples: + metadata = sample.metadata or {} + if pairing_key not in metadata: + raise ValueError( + f"scan_genome paired discovery requires sample.metadata['{pairing_key}'] for every sample." + ) + pair_ids[sample.sample_id] = metadata[pairing_key] + return pair_ids + + +def _build_paired_window_table( + summary: pd.DataFrame, + *, + samples, + pairing_key: str, + required_conditions: list[str], + pairing_policy: str, +) -> tuple[pd.DataFrame, dict[str, int]]: + sample_to_pair = _resolve_pair_ids(samples, pairing_key) + paired = summary.copy() + paired["pair_id"] = paired["sample_id"].map(sample_to_pair) + paired = paired.dropna(subset=["pair_id"]) + + present = ( + paired.loc[:, ["pair_id", "condition"]] + .drop_duplicates() + .groupby("pair_id")["condition"] + .agg(lambda values: set(values)) + ) + complete_pair_ids = sorted( + pair_id for pair_id, values in present.items() if set(required_conditions).issubset(values) + ) + dropped_pair_count = int(len(present) - len(complete_pair_ids)) + + if pairing_policy == "error_on_missing" and dropped_pair_count: + raise ValueError("scan_genome paired discovery found incomplete matched units.") + if not complete_pair_ids: + raise ValueError("scan_genome paired discovery found no complete matched units.") + + paired = paired.loc[paired["pair_id"].isin(complete_pair_ids)].copy() + aggregated = ( + paired.groupby(_WINDOW_KEY_COLUMNS + ["pair_id", "condition"], as_index=False, sort=False) + .agg(modified_count=("modified_count", "sum"), valid_count=("valid_count", "sum")) + .copy() + ) + aggregated["window_fraction"] = _safe_fraction( + aggregated["modified_count"], aggregated["valid_count"] + ) + return aggregated, { + "n_pairs_used": len(complete_pair_ids), + "n_pairs_dropped": dropped_pair_count, + } +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: + +```bash +pytest tests/test_region_discovery.py::test_scan_genome_matched_pairwise_uses_only_complete_pairs tests/test_region_discovery.py::test_scan_genome_time_course_errors_on_missing_pairing_key -q +``` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add tests/test_region_discovery.py dimelo/region_discovery.py +git commit -m "feat: add paired region discovery pair resolution" +``` + +### Task 2: Add `matched_pairwise` Scoring And Paired Metadata + +**Files:** +- Modify: `dimelo/region_discovery.py` +- Modify: `tests/test_region_discovery.py` + +- [ ] **Step 1: Write the failing tests** + +```python +def test_scan_genome_matched_pairwise_ranks_by_mean_abs_delta(monkeypatch): + monkeypatch.setattr(global_analysis, "build_window_summary", lambda **_: _mock_paired_pairwise_window_summary()) + + result = region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + assert list(result.hits["window_id"]) == ["chr1:0-500", "chr1:500-1000"] + assert result.hits.loc[0, "mean_delta"] == pytest.approx(0.35) + assert result.hits.loc[0, "mean_abs_delta"] == pytest.approx(0.35) + assert result.hits.loc[0, "sign_agreement"] == pytest.approx(1.0) + assert result.metadata["paired_mode"] == "matched_pairwise" + assert result.metadata["rank_by"] == "mean_abs_delta" + + +def test_scan_genome_matched_pairwise_errors_on_missing_pairs_in_strict_mode(monkeypatch): + monkeypatch.setattr(global_analysis, "build_window_summary", lambda **_: _mock_paired_window_summary()) + + with pytest.raises(ValueError, match="incomplete matched units"): + region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + pairing_policy="error_on_missing", + ) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +pytest tests/test_region_discovery.py::test_scan_genome_matched_pairwise_ranks_by_mean_abs_delta tests/test_region_discovery.py::test_scan_genome_matched_pairwise_errors_on_missing_pairs_in_strict_mode -q +``` + +Expected: FAIL because paired windows are not yet summarized into `mean_delta`, `mean_abs_delta`, `delta_sd`, or `sign_agreement`. + +- [ ] **Step 3: Write minimal implementation** + +```python +def _score_matched_pairwise( + paired_window_table: pd.DataFrame, + *, + contrast: ContrastSpec, +) -> pd.DataFrame: + numerator = paired_window_table.loc[ + paired_window_table["condition"].isin(contrast.numerator) + ].copy() + denominator = paired_window_table.loc[ + paired_window_table["condition"].isin(contrast.denominator) + ].copy() + + numerator = numerator.rename( + columns={ + "modified_count": "numerator_modified_count", + "valid_count": "numerator_valid_count", + "window_fraction": "numerator_fraction", + } + ) + denominator = denominator.rename( + columns={ + "modified_count": "denominator_modified_count", + "valid_count": "denominator_valid_count", + "window_fraction": "denominator_fraction", + } + ) + + merged = numerator.merge( + denominator[ + _WINDOW_KEY_COLUMNS + + ["pair_id", "denominator_modified_count", "denominator_valid_count", "denominator_fraction"] + ], + on=_WINDOW_KEY_COLUMNS + ["pair_id"], + how="inner", + ) + merged["delta"] = merged["numerator_fraction"] - merged["denominator_fraction"] + + scored = ( + merged.groupby(_WINDOW_KEY_COLUMNS, as_index=False, sort=False) + .agg( + mean_delta=("delta", "mean"), + mean_abs_delta=("delta", lambda values: float(values.abs().mean())), + delta_sd=("delta", lambda values: float(values.std(ddof=0))), + sign_agreement=( + "delta", + lambda values: float( + max((values.gt(0)).mean(), (values.lt(0)).mean()) if len(values) else 0.0 + ), + ), + n_pairs_used=("pair_id", "nunique"), + ) + .copy() + ) + scored["score_value"] = scored["mean_abs_delta"] + scored["p_value"] = pd.NA + scored["adjusted_p_value"] = pd.NA + return scored + + +def scan_genome(..., pairing_policy: str | None = None, rank_by: str | None = None, ...): + ... + if contrast is not None and contrast.mode == "matched_pairwise": + paired_table, pairing_meta = _build_paired_window_table( + window_summary, + samples=samples, + pairing_key=contrast.pairing_key, + required_conditions=list(contrast.numerator or []) + list(contrast.denominator or []), + pairing_policy=_pairing_policy_value(pairing_policy), + ) + ranked = _rank_windows( + _score_matched_pairwise(paired_table, contrast=contrast), + score="effect_size_only", + primary_score_column="mean_abs_delta", + ) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: + +```bash +pytest tests/test_region_discovery.py::test_scan_genome_matched_pairwise_ranks_by_mean_abs_delta tests/test_region_discovery.py::test_scan_genome_matched_pairwise_errors_on_missing_pairs_in_strict_mode -q +``` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add tests/test_region_discovery.py dimelo/region_discovery.py +git commit -m "feat: add matched pairwise region discovery scoring" +``` + +### Task 3: Add Paired Ordered `time_course` Discovery + +**Files:** +- Modify: `dimelo/region_discovery.py` +- Modify: `tests/test_region_discovery.py` + +- [ ] **Step 1: Write the failing tests** + +```python +def test_scan_genome_time_course_ranks_by_trajectory_amplitude_mean(monkeypatch): + monkeypatch.setattr(global_analysis, "build_window_summary", lambda **_: _mock_paired_time_course_window_summary()) + + result = region_discovery.scan_genome( + samples=_paired_time_course_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="time_course", + time_order=["0min", "15min", "30min"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + assert result.hits.loc[0, "trajectory_amplitude_mean"] == pytest.approx(0.55) + assert result.hits.loc[0, "trajectory_amplitude_sd"] == pytest.approx(0.05) + assert result.metadata["paired_mode"] == "time_course" + assert result.metadata["time_order"] == ["0min", "15min", "30min"] + assert result.metadata["rank_by"] == "trajectory_amplitude_mean" + + +def test_scan_genome_time_course_errors_when_time_order_conditions_are_missing(monkeypatch): + monkeypatch.setattr(global_analysis, "build_window_summary", lambda **_: _mock_paired_time_course_window_summary()) + + with pytest.raises(ValueError, match="time_order"): + region_discovery.scan_genome( + samples=_paired_time_course_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="time_course", + time_order=["0min", "15min", "60min"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +pytest tests/test_region_discovery.py::test_scan_genome_time_course_ranks_by_trajectory_amplitude_mean tests/test_region_discovery.py::test_scan_genome_time_course_errors_when_time_order_conditions_are_missing -q +``` + +Expected: FAIL because `time_course` is still rejected by `scan_genome(...)`. + +- [ ] **Step 3: Write minimal implementation** + +```python +def _validate_time_order(paired_window_table: pd.DataFrame, time_order: list[str]) -> None: + available = set(paired_window_table["condition"].dropna().tolist()) + missing = [condition for condition in time_order if condition not in available] + if missing: + raise ValueError( + "scan_genome paired time_course requested missing time_order condition(s): " + + ", ".join(missing) + ) + + +def _score_paired_time_course( + paired_window_table: pd.DataFrame, + *, + time_order: list[str], +) -> pd.DataFrame: + _validate_time_order(paired_window_table, time_order) + ordered = paired_window_table.copy() + ordered["condition"] = pd.Categorical( + ordered["condition"], + categories=time_order, + ordered=True, + ) + ordered = ordered.sort_values(_WINDOW_KEY_COLUMNS + ["pair_id", "condition"]) + + per_pair = ( + ordered.groupby(_WINDOW_KEY_COLUMNS + ["pair_id"], as_index=False, sort=False) + .agg( + trajectory_amplitude=("window_fraction", lambda values: float(values.max() - values.min())), + ) + .copy() + ) + scored = ( + per_pair.groupby(_WINDOW_KEY_COLUMNS, as_index=False, sort=False) + .agg( + trajectory_amplitude_mean=("trajectory_amplitude", "mean"), + trajectory_amplitude_median=("trajectory_amplitude", "median"), + trajectory_amplitude_sd=("trajectory_amplitude", lambda values: float(values.std(ddof=0))), + n_pairs_used=("pair_id", "nunique"), + ) + .copy() + ) + scored["score_value"] = scored["trajectory_amplitude_mean"] + scored["p_value"] = pd.NA + scored["adjusted_p_value"] = pd.NA + return scored +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: + +```bash +pytest tests/test_region_discovery.py::test_scan_genome_time_course_ranks_by_trajectory_amplitude_mean tests/test_region_discovery.py::test_scan_genome_time_course_errors_when_time_order_conditions_are_missing -q +``` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add tests/test_region_discovery.py dimelo/region_discovery.py +git commit -m "feat: add paired time-course region discovery" +``` + +### Task 4: Add Downstream Handoff Coverage And User Docs + +**Files:** +- Modify: `tests/test_region_discovery.py` +- Modify: `docs/region-discovery.md` +- Modify: `README.md` + +- [ ] **Step 1: Write the failing tests** + +```python +def test_paired_discovery_hits_to_bed_and_region_contrasts_handoff(monkeypatch, tmp_path): + monkeypatch.setattr(global_analysis, "build_window_summary", lambda **_: _mock_paired_pairwise_window_summary()) + + discovery = region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + bed = region_discovery.hits_to_bed(discovery.hits.head(1)) + assert list(bed.columns) == ["chrom", "start", "end", "name", "score", "strand"] + assert bed.loc[0, "name"] == "chr1:0-500" +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +pytest tests/test_region_discovery.py::test_paired_discovery_hits_to_bed_and_region_contrasts_handoff -q +``` + +Expected: FAIL because the paired hit columns and metadata are not yet exercised through the downstream handoff path. + +- [ ] **Step 3: Implement wrapper polish and docs** + +```python +metadata.update( + { + "pairing_key": contrast.pairing_key, + "pairing_policy": active_pairing_policy, + "n_pairs_used": pairing_meta["n_pairs_used"], + "n_pairs_dropped": pairing_meta["n_pairs_dropped"], + "paired_mode": contrast.mode, + "rank_by": active_rank_by, + } +) +if contrast.mode == "time_course": + metadata["time_order"] = list(contrast.time_order or []) +``` + +```markdown +## Paired discovery + +Use paired discovery when your samples are matched across conditions and pooling would hide the design: + +- nontargeting versus targeting with matched replicates +- before/after on the same sample +- ordered time courses across the same matched units + +`scan_genome(...)` now supports: + +- `ContrastSpec(mode="matched_pairwise", ...)` +- `ContrastSpec(mode="time_course", ...)` + +For paired modes, every `SampleSpec.metadata` entry must include the requested `pairing_key`. +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: + +```bash +pytest tests/test_region_discovery.py tests/test_global_analysis.py tests/test_region_contrasts.py -q +``` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add tests/test_region_discovery.py docs/region-discovery.md README.md dimelo/region_discovery.py +git commit -m "docs: add paired region discovery guide" +``` + +--- + +## Verification And Review Gates + +- After each task: + - Run the task-focused pytest subset. + - Confirm existing pooled discovery tests still pass. + - Review for continuity with current `scan_genome(...)` behavior. +- After Task 4: + - Run: + +```bash +pytest tests/test_region_discovery.py tests/test_global_analysis.py tests/test_region_contrasts.py tests/test_models.py tests/test_workflows.py -q +``` + +- If the full subset fails: + - fix regressions before proceeding + - do not merge a paired mode that changes current pooled semantics + +## V1 Defaults To Preserve + +- pooled `pairwise` and `group_vs_group` behavior stays unchanged +- paired discovery requires explicit `pairing_key` +- default `pairing_policy="complete_pairs_only"` +- strict `pairing_policy="error_on_missing"` is opt-in +- `matched_pairwise` ranks by `mean_abs_delta` +- paired `time_course` ranks by `trajectory_amplitude_mean` +- no silent fallback from paired discovery to pooled discovery +- `RegionDiscoveryResult` remains data-first with optional figures diff --git a/docs/superpowers/plans/2026-04-01-plotting-axis-architecture.md b/docs/superpowers/plans/2026-04-01-plotting-axis-architecture.md new file mode 100644 index 0000000..750dd1e --- /dev/null +++ b/docs/superpowers/plans/2026-04-01-plotting-axis-architecture.md @@ -0,0 +1,749 @@ +# Plotting Axis Architecture Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a shared plotting-axis core that supports compatibility-safe `5'->3'` orientation, fixed-window prep, aggregate segment-map prep, and legacy wrapper integration without breaking existing plotting entry points. + +**Architecture:** Extend `dimelo/plotting.py` into the canonical plot-data preparation core with explicit axis and aggregation specs. Keep legacy plotting modules public and behaviorally stable by translating existing arguments such as `relative` and `regions_5to3prime` into the new shared prep layer. Restrict scaled/segment-normalized axes to aggregate plots, while keeping single-read plotting coordinate-preserving. + +**Tech Stack:** Python 3.11, `dataclasses`, `pandas`, existing `dimelo` plotting/load utilities, `pytest` + +--- + +## File Map + +- Modify: `dimelo/plotting.py` + - Add shared plotting spec dataclasses, validation, and plot-data prep helpers. +- Modify: `dimelo/plot_enrichment_profile.py` + - Route one aggregate legacy plotter through the shared plotting core without changing public defaults. +- Modify: `dimelo/plot_reads.py` + - Route one single-read legacy plotter through the shared plotting core without allowing scaled continuous axes. +- Modify: `docs/region-contrasts.md` + - Document aggregate positional plotting semantics and compatibility notes. +- Modify: `docs/shared-clustering.md` + - Document how the shared plotting layer applies to workflow `plot_data`. +- Modify: `README.md` + - Add compatibility mapping between old plotting flags and new axis concepts. +- Create: `tests/test_plotting.py` + - Add focused tests for spec validation and plot-data preparation. +- Modify: `dimelo/test/dimelo_test.py` + - Preserve regression coverage for existing plotter entry points where needed. + +### Task 1: Add Shared Plotting Specs And Validation + +**Files:** +- Modify: `dimelo/plotting.py` +- Create: `tests/test_plotting.py` + +- [ ] **Step 1: Write the failing tests for axis and aggregation spec validation** + +```python +from dataclasses import replace + +import pytest + +from dimelo import plotting + + +def test_axis_spec_accepts_fixed_window_region_5to3(): + spec = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=1000, + downstream_bp=1000, + ) + + plotting.validate_axis_spec(spec, plot_family="aggregate_profile") + + +def test_axis_spec_rejects_segment_map_without_segments(): + spec = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=None, + ) + + with pytest.raises(ValueError, match="segment_map requires segments"): + plotting.validate_axis_spec(spec, plot_family="aggregate_profile") + + +def test_single_read_rejects_scaled_segments(): + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec( + segment_id="body", + label="Body", + start_ref=100, + end_ref=500, + mode="scaled", + bins=50, + ) + ], + ) + + with pytest.raises(ValueError, match="single_read"): + plotting.validate_axis_spec(axis, plot_family="single_read_raster") + + +def test_aggregation_spec_accepts_equal_region_default(): + spec = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="faceted", + ) + + plotting.validate_aggregation_spec(spec) +``` + +- [ ] **Step 2: Run the new tests to verify they fail** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q` + +Expected: FAIL with import or attribute errors for `AxisSpec`, `SegmentSpec`, `AggregationSpec`, or validation helpers not yet existing. + +- [ ] **Step 3: Add the shared plotting spec dataclasses and validation helpers** + +```python +from dataclasses import dataclass + + +@dataclass(frozen=True) +class SegmentSpec: + segment_id: str + label: str + start_ref: int + end_ref: int + mode: str # "raw" | "scaled" + bins: int | None = None + plot_gap_after: bool = False + contiguous_with_previous: bool = True + + +@dataclass(frozen=True) +class AxisSpec: + orientation: str # "genomic" | "region_5to3" + coordinate_mode: str # "fixed_window" | "segment_map" + anchor: str | None = None + upstream_bp: int | None = None + downstream_bp: int | None = None + segments: list[SegmentSpec] | None = None + + +@dataclass(frozen=True) +class AggregationSpec: + weighting: str # "equal_region" | "equal_observation" | "coverage_weighted" + within_region_summary: str # "mean" | "fraction" | "density" + signal_normalization: str # "none" | "per_region" | "global" | "control_regions" + layout: str # "concatenated" | "faceted" + + +def validate_axis_spec(axis: AxisSpec, *, plot_family: str) -> None: + if axis.orientation not in {"genomic", "region_5to3"}: + raise ValueError("AxisSpec.orientation must be 'genomic' or 'region_5to3'.") + if axis.coordinate_mode not in {"fixed_window", "segment_map"}: + raise ValueError("AxisSpec.coordinate_mode must be 'fixed_window' or 'segment_map'.") + if axis.coordinate_mode == "segment_map" and not axis.segments: + raise ValueError("AxisSpec coordinate_mode='segment_map' requires segments.") + if plot_family == "single_read_raster" and axis.segments: + if any(segment.mode == "scaled" for segment in axis.segments): + raise ValueError( + "single_read_raster plots must preserve coordinates and cannot use scaled segments." + ) + + +def validate_aggregation_spec(spec: AggregationSpec) -> None: + if spec.weighting not in {"equal_region", "equal_observation", "coverage_weighted"}: + raise ValueError("Unsupported weighting mode.") + if spec.within_region_summary not in {"mean", "fraction", "density"}: + raise ValueError("Unsupported within_region_summary.") + if spec.signal_normalization not in {"none", "per_region", "global", "control_regions"}: + raise ValueError("Unsupported signal_normalization.") + if spec.layout not in {"concatenated", "faceted"}: + raise ValueError("Unsupported layout mode.") +``` + +- [ ] **Step 4: Run the spec-validation tests to verify they pass** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q` + +Expected: PASS for the new validation tests. + +- [ ] **Step 5: Commit the shared plotting spec scaffolding** + +```bash +git add dimelo/plotting.py \ + tests/test_plotting.py +git commit -m "feat: add plotting axis spec validation" +``` + +### Task 2: Implement Fixed-Window Plot Data Prep + +**Files:** +- Modify: `dimelo/plotting.py` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Write failing tests for fixed-window orientation prep** + +```python +import pandas as pd + +from dimelo import plotting + + +def test_prepare_single_read_plot_data_flips_negative_regions_to_5to3(): + reads = pd.DataFrame( + [ + { + "region_id": "reg1", + "region_strand": "-", + "event_pos": 110, + "anchor": 100, + "read_id": "r1", + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="custom", + upstream_bp=20, + downstream_bp=20, + ) + + payload = plotting.prepare_single_read_plot_data( + reads, + plot_family="single_read_raster", + axis=axis, + position_column="event_pos", + anchor_column="anchor", + region_strand_column="region_strand", + ) + + assert payload["plot_table"].loc[0, "plot_x"] == -10 + + +def test_prepare_aggregate_plot_data_retains_metadata_for_fixed_window(): + table = pd.DataFrame( + [ + {"region_id": "reg1", "region_strand": "+", "event_pos": 95, "anchor": 100, "signal": 1.0}, + {"region_id": "reg1", "region_strand": "+", "event_pos": 105, "anchor": 100, "signal": 3.0}, + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=10, + downstream_bp=10, + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="faceted", + ) + + payload = plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + position_column="event_pos", + anchor_column="anchor", + region_strand_column="region_strand", + ) + + assert {"plot_table", "axis_table", "metadata"} <= set(payload) + assert payload["metadata"]["orientation"] == "region_5to3" +``` + +- [ ] **Step 2: Run the fixed-window tests to verify they fail** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k fixed_window -q` + +Expected: FAIL because `prepare_single_read_plot_data()` and `prepare_aggregate_plot_data()` do not yet exist. + +- [ ] **Step 3: Implement shared fixed-window prep helpers** + +```python +def _relative_position(position: float, anchor: float) -> float: + return float(position) - float(anchor) + + +def _orient_position(relative_position: float, region_strand: str, orientation: str) -> float: + if orientation == "region_5to3" and region_strand == "-": + return -relative_position + return relative_position + + +def prepare_single_read_plot_data( + table: pd.DataFrame, + *, + plot_family: str, + axis: AxisSpec, + position_column: str, + anchor_column: str, + region_strand_column: str, +) -> dict[str, pd.DataFrame | dict[str, object]]: + validate_axis_spec(axis, plot_family=plot_family) + if axis.coordinate_mode != "fixed_window": + raise ValueError("single_read_raster currently supports fixed_window only.") + + plot_table = table.copy() + plot_table["plot_x"] = plot_table.apply( + lambda row: _orient_position( + _relative_position(row[position_column], row[anchor_column]), + row[region_strand_column], + axis.orientation, + ), + axis=1, + ) + axis_table = pd.DataFrame( + [{"axis_min": -axis.upstream_bp, "axis_max": axis.downstream_bp, "segment_id": "window"}] + ) + metadata = {"plot_family": plot_family, "orientation": axis.orientation, "coordinate_mode": axis.coordinate_mode} + return {"plot_table": plot_table, "axis_table": axis_table, "metadata": metadata} + + +def prepare_aggregate_plot_data( + table: pd.DataFrame, + *, + plot_family: str, + axis: AxisSpec, + aggregation: AggregationSpec, + value_column: str, + position_column: str, + anchor_column: str, + region_strand_column: str, +) -> dict[str, pd.DataFrame | dict[str, object]]: + validate_axis_spec(axis, plot_family=plot_family) + validate_aggregation_spec(aggregation) + plot_table = table.copy() + plot_table["plot_x"] = plot_table.apply( + lambda row: _orient_position( + _relative_position(row[position_column], row[anchor_column]), + row[region_strand_column], + axis.orientation, + ), + axis=1, + ) + axis_table = pd.DataFrame( + [{"axis_min": -axis.upstream_bp, "axis_max": axis.downstream_bp, "segment_id": "window"}] + ) + metadata = { + "plot_family": plot_family, + "orientation": axis.orientation, + "coordinate_mode": axis.coordinate_mode, + "weighting": aggregation.weighting, + "within_region_summary": aggregation.within_region_summary, + } + return {"plot_table": plot_table, "axis_table": axis_table, "metadata": metadata} +``` + +- [ ] **Step 4: Run the fixed-window tests to verify they pass** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k fixed_window -q` + +Expected: PASS. + +- [ ] **Step 5: Commit the fixed-window prep slice** + +```bash +git add dimelo/plotting.py \ + tests/test_plotting.py +git commit -m "feat: add fixed-window plotting prep" +``` + +### Task 3: Implement Aggregate Segment-Map Prep + +**Files:** +- Modify: `dimelo/plotting.py` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Write failing tests for contiguous and non-contiguous aggregate segment maps** + +```python +import pandas as pd + +from dimelo import plotting + + +def test_prepare_aggregate_plot_data_builds_concatenated_segment_axis(): + table = pd.DataFrame( + [ + {"region_id": "reg1", "segment_id": "upstream", "segment_pos": 0, "signal": 1.0}, + {"region_id": "reg1", "segment_id": "body", "segment_pos": 10, "signal": 2.0}, + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("upstream", "Upstream", 0, 100, "raw", bins=20), + plotting.SegmentSpec("body", "Body", 100, 400, "scaled", bins=50, contiguous_with_previous=True), + ], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="concatenated", + ) + + payload = plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + assert payload["axis_table"]["segment_id"].tolist() == ["upstream", "body"] + assert payload["axis_table"]["plot_start"].is_monotonic_increasing + + +def test_prepare_aggregate_plot_data_marks_non_contiguous_segment_breaks(): + table = pd.DataFrame( + [{"region_id": "reg1", "segment_id": "exon1", "segment_pos": 5, "signal": 1.0}] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("exon1", "Exon 1", 100, 200, "scaled", bins=20), + plotting.SegmentSpec( + "exon3", + "Exon 3", + 500, + 650, + "scaled", + bins=20, + contiguous_with_previous=False, + plot_gap_after=True, + ), + ], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="faceted", + ) + + payload = plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + assert payload["axis_table"].loc[1, "contiguous_with_previous"] is False +``` + +- [ ] **Step 2: Run the segment-map tests to verify they fail** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k segment -q` + +Expected: FAIL because segment-map prep is not yet implemented. + +- [ ] **Step 3: Implement aggregate-only segment-map preparation** + +```python +def _build_segment_axis_table(segments: list[SegmentSpec]) -> pd.DataFrame: + rows = [] + running_start = 0 + for segment in segments: + span = segment.bins if segment.bins is not None else segment.end_ref - segment.start_ref + rows.append( + { + "segment_id": segment.segment_id, + "label": segment.label, + "plot_start": running_start, + "plot_end": running_start + span, + "contiguous_with_previous": segment.contiguous_with_previous, + "plot_gap_after": segment.plot_gap_after, + "mode": segment.mode, + } + ) + running_start += span + return pd.DataFrame(rows) + + +def prepare_aggregate_plot_data(..., segment_id_column: str | None = None, segment_position_column: str | None = None) -> dict[str, pd.DataFrame | dict[str, object]]: + validate_axis_spec(axis, plot_family=plot_family) + validate_aggregation_spec(aggregation) + + if axis.coordinate_mode == "segment_map": + if not axis.segments: + raise ValueError("segment_map requires segments.") + axis_table = _build_segment_axis_table(axis.segments) + if segment_id_column is None or segment_position_column is None: + raise ValueError("segment_map plotting requires segment_id_column and segment_position_column.") + plot_table = table.copy() + plot_table = plot_table.merge( + axis_table.loc[:, ["segment_id", "plot_start"]], + on="segment_id", + how="left", + ) + plot_table["plot_x"] = plot_table["plot_start"] + plot_table[segment_position_column] + metadata = { + "plot_family": plot_family, + "orientation": axis.orientation, + "coordinate_mode": axis.coordinate_mode, + "layout": aggregation.layout, + } + return {"plot_table": plot_table, "axis_table": axis_table, "metadata": metadata} +``` + +- [ ] **Step 4: Run the segment-map tests to verify they pass** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k segment -q` + +Expected: PASS. + +- [ ] **Step 5: Commit the aggregate segment-map slice** + +```bash +git add dimelo/plotting.py \ + tests/test_plotting.py +git commit -m "feat: add aggregate segment-map plotting prep" +``` + +### Task 4: Route One Aggregate Legacy Plotter Through The Shared Core + +**Files:** +- Modify: `dimelo/plot_enrichment_profile.py` +- Modify: `tests/test_plotting.py` +- Modify: `dimelo/test/dimelo_test.py` + +- [ ] **Step 1: Write a regression test that legacy `regions_5to3prime` maps through the new axis logic** + +```python +def test_legacy_enrichment_profile_retains_regions_5to3prime_behavior(monkeypatch): + called = {} + + def fake_prepare_aggregate_plot_data(*args, **kwargs): + called["axis"] = kwargs["axis"] + return { + "plot_table": kwargs["table"].copy() if "table" in kwargs else args[0].copy(), + "axis_table": pd.DataFrame(), + "metadata": {}, + } + + monkeypatch.setattr("dimelo.plotting.prepare_aggregate_plot_data", fake_prepare_aggregate_plot_data) + # call the lowest-level trace helper with regions_5to3prime=True + # and assert the translated axis carries orientation='region_5to3' +``` + +- [ ] **Step 2: Run the regression test to verify it fails** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k enrichment_profile -q` + +Expected: FAIL because the legacy plotter does not yet call the shared prep layer. + +- [ ] **Step 3: Translate legacy aggregate plotter arguments into shared specs** + +```python +axis = plotting.AxisSpec( + orientation="region_5to3" if regions_5to3prime else "genomic", + coordinate_mode="fixed_window" if relative else "fixed_window", + anchor="center" if relative else "absolute", + upstream_bp=window_size // 2 if window_size else None, + downstream_bp=window_size // 2 if window_size else None, +) +aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="faceted", +) +payload = plotting.prepare_aggregate_plot_data( + table=trace_table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="mod_fraction", + position_column="position", + anchor_column="center", + region_strand_column="region_strand", +) +``` + +- [ ] **Step 4: Run the aggregate wrapper tests** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k enrichment_profile -q` + +Expected: PASS. + +- [ ] **Step 5: Commit the aggregate wrapper integration** + +```bash +git add dimelo/plot_enrichment_profile.py \ + tests/test_plotting.py \ + dimelo/test/dimelo_test.py +git commit -m "refactor: route aggregate plotter through plotting core" +``` + +### Task 5: Route One Single-Read Legacy Plotter Through The Shared Core + +**Files:** +- Modify: `dimelo/plot_reads.py` +- Modify: `tests/test_plotting.py` +- Modify: `dimelo/test/dimelo_test.py` + +- [ ] **Step 1: Write a failing regression test for single-read compatibility** + +```python +def test_legacy_plot_reads_translates_regions_5to3prime_to_axis_orientation(monkeypatch): + called = {} + + def fake_prepare_single_read_plot_data(*args, **kwargs): + called["axis"] = kwargs["axis"] + return {"plot_table": pd.DataFrame(), "axis_table": pd.DataFrame(), "metadata": {}} + + monkeypatch.setattr("dimelo.plotting.prepare_single_read_plot_data", fake_prepare_single_read_plot_data) + # invoke the legacy read plotting prep/helper and assert: + # called["axis"].orientation == "region_5to3" + + +def test_prepare_single_read_plot_data_rejects_scaled_segment_axes(): + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("body", "Body", 10, 100, "scaled", bins=20) + ], + ) + with pytest.raises(ValueError, match="single_read_raster"): + plotting.prepare_single_read_plot_data( + pd.DataFrame(), + plot_family="single_read_raster", + axis=axis, + position_column="position", + anchor_column="anchor", + region_strand_column="region_strand", + ) +``` + +- [ ] **Step 2: Run the single-read tests to verify they fail** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k single_read -q` + +Expected: FAIL because the legacy single-read plotter is not yet routed through the shared prep helper. + +- [ ] **Step 3: Translate legacy single-read plot arguments into shared fixed-window prep** + +```python +axis = plotting.AxisSpec( + orientation="region_5to3" if regions_5to3prime else "genomic", + coordinate_mode="fixed_window", + anchor="center" if relative else "absolute", + upstream_bp=window_size // 2 if window_size else None, + downstream_bp=window_size // 2 if window_size else None, +) +payload = plotting.prepare_single_read_plot_data( + table=read_table, + plot_family="single_read_raster", + axis=axis, + position_column="plot_position", + anchor_column="region_center", + region_strand_column="region_strand", +) +``` + +- [ ] **Step 4: Run the single-read compatibility tests** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k single_read -q` + +Expected: PASS. + +- [ ] **Step 5: Commit the single-read wrapper integration** + +```bash +git add dimelo/plot_reads.py \ + tests/test_plotting.py \ + dimelo/test/dimelo_test.py +git commit -m "refactor: route single-read plotter through plotting core" +``` + +### Task 6: Update User-Facing Plotting Documentation And Run Final Verification + +**Files:** +- Modify: `docs/region-contrasts.md` +- Modify: `docs/shared-clustering.md` +- Modify: `README.md` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Write a failing documentation-backed test for compatibility mapping if needed** + +```python +def test_plotting_core_exports_shared_specs(): + assert hasattr(plotting, "AxisSpec") + assert hasattr(plotting, "prepare_single_read_plot_data") + assert hasattr(plotting, "prepare_aggregate_plot_data") +``` + +- [ ] **Step 2: Run the focused plotting suite before doc edits** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q` + +Expected: PASS before final docs, confirming the plotting core is stable. + +- [ ] **Step 3: Update the docs with the compatibility mapping and safe-use rules** + +```markdown +- `regions_5to3prime=True` maps to `AxisSpec(orientation="region_5to3")` +- fixed-window positional plotting is available for both aggregate and single-read views +- scaled or segmented metaregion plotting is aggregate-only +- single-read plots preserve geometry and reject continuous scaled axes +- `result.plot_data` remains the canonical renderer-neutral contract +``` + +- [ ] **Step 4: Run the branch verification slice** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py tests/test_region_contrasts.py tests/test_workflows.py -q` + +Expected: PASS with no new failures. Existing non-blocking warnings are acceptable if unchanged and understood. + +- [ ] **Step 5: Commit docs and final verification state** + +```bash +git add tests/test_plotting.py \ + docs/region-contrasts.md \ + docs/shared-clustering.md \ + README.md +git commit -m "docs: add shared plotting axis guidance" +``` + +## Self-Review + +### Spec coverage + +- Shared axis and aggregation specs: covered by Task 1. +- Fixed-window prep with `region_5to3`: covered by Task 2. +- Aggregate-only segment maps, including contiguous and non-contiguous segments: covered by Task 3. +- Compatibility-safe legacy wrappers: covered by Tasks 4 and 5. +- Documentation and compatibility guidance: covered by Task 6. + +No spec sections are currently unassigned. + +### Placeholder scan + +- No `TBD` or `TODO` placeholders remain. +- Each task includes concrete file paths, test cases, commands, and commit steps. + +### Type consistency + +- `AxisSpec`, `SegmentSpec`, `AggregationSpec`, `prepare_single_read_plot_data`, and `prepare_aggregate_plot_data` are defined once and reused consistently across tasks. +- `single_read_raster` is consistently treated as coordinate-preserving. +- `segment_map` remains aggregate-enabled, with scaled segments blocked for single-read usage. diff --git a/docs/superpowers/plans/2026-04-01-region-contrasts-plotting.md b/docs/superpowers/plans/2026-04-01-region-contrasts-plotting.md new file mode 100644 index 0000000..7239768 --- /dev/null +++ b/docs/superpowers/plans/2026-04-01-region-contrasts-plotting.md @@ -0,0 +1,576 @@ +# Region Contrasts Plotting Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add fixed-window, data-first plotting helpers for `dimelo.region_contrasts` that prepare profile and heatmap payloads from a `RegionContrastResult` plus an explicit positional source table. + +**Architecture:** Extend [plotting.py](../../../dimelo/plotting.py) with a small internal join/validation layer for contrast results and positional source tables, then expose two public helpers: one for aggregate profiles and one for heatmaps. Keep `region_contrasts` unchanged; plotting prep consumes existing `RegionContrastResult` tables and parse/load-derived positional summaries, uses the shared `AxisSpec` / `AggregationSpec` fixed-window path, and returns renderer-neutral payloads. + +**Tech Stack:** Python 3.11, pandas, pytest, existing `dimelo.plotting` shared axis helpers, existing `RegionContrastResult` models + +--- + +## File Structure + +- Modify: `dimelo/plotting.py` + - Add internal helpers for validating/joining `RegionContrastResult` against a positional source table. + - Add `prepare_region_contrast_profile_data(...)`. + - Add `prepare_region_contrast_heatmap_data(...)`. +- Modify: `tests/test_plotting.py` + - Add regression tests for the new plot-prep helpers. + - Cover both numerator/denominator/delta payload generation and failure modes. +- Modify: `docs/region-contrasts.md` + - Document the new helper APIs and the required `position_table` contract. +- Modify: `README.md` + - Add a short compatibility note pointing users from `region_contrasts` into the new plotting helpers. + +### Task 1: Add minimal failing tests for contrast-to-position joins + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/models.py` +- Reference: `dimelo/plotting.py` + +- [ ] **Step 1: Write the failing tests for a minimal profile payload** + +Add these tests near the other plotting helper tests in `tests/test_plotting.py`: + +```python +from dimelo.models import ContrastSpec, RegionContrastResult + + +def _minimal_region_contrast_result() -> RegionContrastResult: + regions = pd.DataFrame( + [ + {"region_id": "chr1:90-110,+", "condition": "NS", "fraction": 0.20, "rank": 2}, + {"region_id": "chr1:90-110,+", "condition": "15min", "fraction": 0.55, "rank": 2}, + {"region_id": "chr1:190-210,-", "condition": "NS", "fraction": 0.30, "rank": 1}, + {"region_id": "chr1:190-210,-", "condition": "15min", "fraction": 0.70, "rank": 1}, + ] + ) + summary = pd.DataFrame( + [ + { + "region_id": "chr1:90-110,+", + "fraction": 0.55, + "reference_fraction": 0.20, + "delta_fraction": 0.35, + "rank": 2, + }, + { + "region_id": "chr1:190-210,-", + "fraction": 0.70, + "reference_fraction": 0.30, + "delta_fraction": 0.40, + "rank": 1, + }, + ] + ) + return RegionContrastResult( + regions=regions, + summary=summary, + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + metadata={ + "analysis_unit": "ensemble_region", + "representation": "modified_fraction", + "signal_source": "pileup_counts", + "test": "effect_size_only", + }, + plot_data={}, + ) + + +def test_prepare_region_contrast_profile_data_returns_all_value_modes(): + result = _minimal_region_contrast_result() + position_table = pd.DataFrame( + [ + {"region_id": "chr1:90-110,+", "condition": "NS", "position": 95, "anchor": 100, "value": 0.1, "region_strand": "+"}, + {"region_id": "chr1:90-110,+", "condition": "15min", "position": 95, "anchor": 100, "value": 0.6, "region_strand": "+"}, + {"region_id": "chr1:190-210,-", "condition": "NS", "position": 205, "anchor": 200, "value": 0.2, "region_strand": "-"}, + {"region_id": "chr1:190-210,-", "condition": "15min", "position": 205, "anchor": 200, "value": 0.8, "region_strand": "-"}, + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=20, + downstream_bp=20, + ) + aggregation = plotting.AggregationSpec() + + payload = plotting.prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + + assert set(payload["plot_table"]["value_mode"]) == {"numerator", "denominator", "delta"} + assert payload["metadata"]["plot_family"] == "region_contrast_profile" +``` + +- [ ] **Step 2: Add the failing heatmap and validation tests** + +Append these tests in the same file: + +```python +def test_prepare_region_contrast_heatmap_data_orders_rows_by_rank(): + result = _minimal_region_contrast_result() + position_table = pd.DataFrame( + [ + {"region_id": "chr1:90-110,+", "condition": "NS", "position": 95, "anchor": 100, "value": 0.1, "region_strand": "+"}, + {"region_id": "chr1:90-110,+", "condition": "15min", "position": 95, "anchor": 100, "value": 0.6, "region_strand": "+"}, + {"region_id": "chr1:190-210,-", "condition": "NS", "position": 205, "anchor": 200, "value": 0.2, "region_strand": "-"}, + {"region_id": "chr1:190-210,-", "condition": "15min", "position": 205, "anchor": 200, "value": 0.8, "region_strand": "-"}, + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=20, + downstream_bp=20, + ) + aggregation = plotting.AggregationSpec() + + payload = plotting.prepare_region_contrast_heatmap_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + + rank_rows = payload["plot_table"].loc[:, ["region_id", "row_order"]].drop_duplicates() + assert list(rank_rows.sort_values("row_order")["region_id"]) == [ + "chr1:190-210,-", + "chr1:90-110,+", + ] + assert payload["metadata"]["plot_family"] == "region_contrast_heatmap" + + +def test_prepare_region_contrast_profile_data_requires_joinable_grouping_key(): + result = _minimal_region_contrast_result() + position_table = pd.DataFrame( + [ + {"region_id": "chr1:90-110,+", "position": 95, "anchor": 100, "value": 0.1, "region_strand": "+"} + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=20, + downstream_bp=20, + ) + aggregation = plotting.AggregationSpec() + + with pytest.raises(ValueError, match="sample_id or condition"): + plotting.prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) +``` + +- [ ] **Step 3: Run the new tests to verify they fail** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "region_contrast_profile_data or region_contrast_heatmap_data" -q +``` + +Expected: FAIL with `AttributeError` or `NameError` because the new plotting helpers do not exist yet. + +- [ ] **Step 4: Commit the failing-test scaffold** + +```bash +git add tests/test_plotting.py +git commit -m "test: add region contrast plotting coverage" +``` + +### Task 2: Implement shared join and normalization helpers + +**Files:** +- Modify: `dimelo/plotting.py` +- Test: `tests/test_plotting.py` + +- [ ] **Step 1: Add a contrast-result metadata reader and join-key validator** + +Insert small internal helpers in `dimelo/plotting.py` below the existing validation helpers: + +```python +def _region_contrast_grouping_key(result, position_table: pd.DataFrame) -> str: + available = [column for column in ("sample_id", "condition") if column in position_table.columns] + if not available: + raise ValueError( + "region contrast plotting requires position_table to include sample_id or condition." + ) + if "condition" in available: + return "condition" + return "sample_id" + + +def _region_contrast_metadata(result) -> dict[str, object]: + return { + "analysis_unit": result.metadata.get("analysis_unit"), + "representation": result.metadata.get("representation"), + "signal_source": result.metadata.get("signal_source"), + "test": result.metadata.get("test"), + } +``` + +- [ ] **Step 2: Add a helper that validates and filters a positional source table** + +Add this helper in the same file: + +```python +def _prepare_region_contrast_position_table( + *, + result, + position_table: pd.DataFrame, + grouping_key: str, +) -> pd.DataFrame: + _require_columns( + position_table, + ("region_id", grouping_key, "position", "value", "region_strand"), + "position_table", + ) + filtered = position_table[position_table["region_id"].isin(result.summary["region_id"])].copy() + if filtered.empty: + raise ValueError("position_table does not contain any region_id values present in the contrast result.") + return filtered.reset_index(drop=True) +``` + +- [ ] **Step 3: Add a side-merging helper for numerator, denominator, and delta** + +Add this helper in the same file: + +```python +def _prepare_region_contrast_value_modes( + *, + result, + position_table: pd.DataFrame, + grouping_key: str, +) -> pd.DataFrame: + contrast = result.contrast + numerator_mask = position_table[grouping_key].isin(contrast.numerator or []) + denominator_mask = position_table[grouping_key].isin(contrast.denominator or []) + + numerator = position_table.loc[numerator_mask].copy() + denominator = position_table.loc[denominator_mask].copy() + if numerator.empty or denominator.empty: + raise ValueError("position_table does not contain rows for both contrast sides.") + + numerator["value_mode"] = "numerator" + denominator["value_mode"] = "denominator" + + delta = numerator.merge( + denominator, + on=["region_id", "position", "region_strand"], + suffixes=("_numerator", "_denominator"), + how="inner", + ) + if delta.empty: + raise ValueError("Unable to compute delta because numerator and denominator positions do not align.") + + delta = delta.rename( + columns={ + "position": "position", + "region_strand": "region_strand", + } + ) + delta["value"] = delta["value_numerator"] - delta["value_denominator"] + delta[grouping_key] = "delta" + delta["value_mode"] = "delta" + delta = delta.loc[:, ["region_id", grouping_key, "position", "value", "region_strand"]] + + return pd.concat( + [ + numerator.loc[:, ["region_id", grouping_key, "position", "value", "region_strand", "value_mode"]], + denominator.loc[:, ["region_id", grouping_key, "position", "value", "region_strand", "value_mode"]], + delta, + ], + ignore_index=True, + ) +``` + +- [ ] **Step 4: Run the targeted tests to verify the join layer works** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "region_contrast_profile_data_requires_joinable_grouping_key" -q +``` + +Expected: PASS for the validation test, while the helper-existence tests still fail. + +- [ ] **Step 5: Commit the join/validation layer** + +```bash +git add dimelo/plotting.py tests/test_plotting.py +git commit -m "feat: add region contrast plotting join helpers" +``` + +### Task 3: Implement `prepare_region_contrast_profile_data(...)` + +**Files:** +- Modify: `dimelo/plotting.py` +- Test: `tests/test_plotting.py` + +- [ ] **Step 1: Add the public helper using the shared fixed-window prep** + +Add this function in `dimelo/plotting.py` near the other public plotting helpers: + +```python +def prepare_region_contrast_profile_data( + *, + result, + position_table: pd.DataFrame, + axis: AxisSpec, + aggregation: AggregationSpec, + value_mode: str = "all", +) -> dict[str, pd.DataFrame | dict[str, object]]: + validate_axis_spec(axis, plot_family="aggregate_profile") + validate_aggregation_spec(aggregation) + grouping_key = _region_contrast_grouping_key(result, position_table) + filtered = _prepare_region_contrast_position_table( + result=result, + position_table=position_table, + grouping_key=grouping_key, + ) + valued = _prepare_region_contrast_value_modes( + result=result, + position_table=filtered, + grouping_key=grouping_key, + ) + if value_mode != "all": + valued = valued.loc[valued["value_mode"] == value_mode].copy() + + valued["anchor"] = valued["position"] + prepared = prepare_aggregate_plot_data( + valued, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="value", + position_column="position", + anchor_column="anchor", + region_strand_column="region_strand", + ) + prepared["metadata"] = { + **prepared["metadata"], + **_region_contrast_metadata(result), + "plot_family": "region_contrast_profile", + "value_modes": sorted(valued["value_mode"].unique()), + } + prepared["summary_table"] = ( + prepared["plot_table"] + .groupby(["value_mode", "plot_x"], as_index=False)["value"] + .mean() + ) + return prepared +``` + +- [ ] **Step 2: Fix the anchor handling so the profile prep uses the real fixed-window contract** + +Replace the temporary `valued["anchor"] = valued["position"]` line with: + +```python +_require_columns(filtered, ("anchor",), "position_table") +valued = valued.merge( + filtered.loc[:, ["region_id", grouping_key, "position", "anchor"]].drop_duplicates(), + on=["region_id", grouping_key, "position"], + how="left", +) +``` + +This keeps the helper aligned with the actual fixed-window substrate instead of collapsing all `plot_x` values to zero. + +- [ ] **Step 3: Run the targeted profile tests** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "prepare_region_contrast_profile_data" -q +``` + +Expected: PASS for the profile tests. + +- [ ] **Step 4: Commit the profile helper** + +```bash +git add dimelo/plotting.py tests/test_plotting.py +git commit -m "feat: add region contrast profile plotting prep" +``` + +### Task 4: Implement `prepare_region_contrast_heatmap_data(...)` + +**Files:** +- Modify: `dimelo/plotting.py` +- Test: `tests/test_plotting.py` + +- [ ] **Step 1: Add the heatmap helper on top of the same substrate** + +Add this function in `dimelo/plotting.py`: + +```python +def prepare_region_contrast_heatmap_data( + *, + result, + position_table: pd.DataFrame, + axis: AxisSpec, + aggregation: AggregationSpec, + value_mode: str = "all", +) -> dict[str, pd.DataFrame | dict[str, object]]: + profile_payload = prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode=value_mode, + ) + rank_lookup = ( + result.summary.loc[:, ["region_id", "rank"]] + .drop_duplicates() + .sort_values(["rank", "region_id"], kind="stable") + .reset_index(drop=True) + ) + rank_lookup["row_order"] = range(len(rank_lookup)) + plot_table = profile_payload["plot_table"].merge(rank_lookup, on="region_id", how="left") + profile_payload["plot_table"] = plot_table + profile_payload["metadata"] = { + **profile_payload["metadata"], + "plot_family": "region_contrast_heatmap", + } + profile_payload["summary_table"] = rank_lookup + return profile_payload +``` + +- [ ] **Step 2: Add a failure-mode test for missing contrast-side rows** + +Extend `tests/test_plotting.py` with: + +```python +def test_prepare_region_contrast_heatmap_data_requires_both_contrast_sides(): + result = _minimal_region_contrast_result() + position_table = pd.DataFrame( + [ + {"region_id": "chr1:90-110,+", "condition": "NS", "position": 95, "anchor": 100, "value": 0.1, "region_strand": "+"} + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=20, + downstream_bp=20, + ) + aggregation = plotting.AggregationSpec() + + with pytest.raises(ValueError, match="both contrast sides"): + plotting.prepare_region_contrast_heatmap_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) +``` + +- [ ] **Step 3: Run the full plotting suite** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q +``` + +Expected: PASS, including the new heatmap coverage. + +- [ ] **Step 4: Commit the heatmap helper** + +```bash +git add dimelo/plotting.py tests/test_plotting.py +git commit -m "feat: add region contrast heatmap plotting prep" +``` + +### Task 5: Document the new plotting helpers and verify the slice + +**Files:** +- Modify: `docs/region-contrasts.md` +- Modify: `README.md` +- Verify: `tests/test_plotting.py` +- Verify: `tests/test_region_contrasts.py` +- Verify: `tests/test_workflows.py` + +- [ ] **Step 1: Add a region-contrasts plotting example** + +Append a short section to `docs/region-contrasts.md`: + +```python +profile_payload = plotting.prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=2000, + downstream_bp=2000, + ), + aggregation=plotting.AggregationSpec(), + value_mode="all", +) +``` + +Add one sentence clarifying that `position_table` must already be an aggregated positional substrate from the parsing/loading layer. + +- [ ] **Step 2: Add a short README compatibility note** + +Add one short paragraph to `README.md` under the plotting section explaining: + +```text +Newer region-contrast plotting helpers accept a RegionContrastResult plus an explicit position_table from the parsing/loading layer. This keeps region scoring and positional extraction separate while still using the shared plotting-axis system. +``` + +- [ ] **Step 3: Run the final verification set** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py tests/test_region_contrasts.py tests/test_workflows.py -q +``` + +Expected: PASS. If warnings appear, note them in the summary only if they are new or blocking. + +- [ ] **Step 4: Commit the docs and final verification slice** + +```bash +git add docs/region-contrasts.md README.md dimelo/plotting.py tests/test_plotting.py +git commit -m "docs: add region contrast plotting guide" +``` + +## Self-Review + +- Spec coverage: + - public helpers: Task 3 and Task 4 + - explicit `position_table`: Task 1 and Task 2 + - numerator/denominator/delta payloads: Task 2 and Task 3 + - fixed-window aggregate-only scope: Task 3 and Task 4 + - compatibility/docs: Task 5 +- Placeholder scan: + - no `TODO`, `TBD`, or deferred implementation steps are left in the task bodies +- Type consistency: + - `prepare_region_contrast_profile_data(...)` and `prepare_region_contrast_heatmap_data(...)` are used consistently across tests, code steps, and docs + diff --git a/docs/superpowers/plans/2026-04-01-region-discovery-cluster-workflow.md b/docs/superpowers/plans/2026-04-01-region-discovery-cluster-workflow.md new file mode 100644 index 0000000..ac02050 --- /dev/null +++ b/docs/superpowers/plans/2026-04-01-region-discovery-cluster-workflow.md @@ -0,0 +1,270 @@ +# Region Discovery To Cluster Workflow Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add one end-to-end workflow that runs region discovery, selects discovered loci, clusters those loci with the shared clustering engine, and returns both the discovery and clustering results. + +**Architecture:** Build a thin orchestration layer in `dimelo.workflows` that composes the existing `dimelo.region_discovery.scan_genome(...)` and `dimelo.workflows.shared_cluster_distribution(...)` entry points. Keep the result data-first by introducing a small wrapper model that stores both outputs plus the exact discovered-region subset passed into clustering. + +**Tech Stack:** Python dataclasses, pandas, existing dimelo pileup/extract workflows, pytest + +--- + +### Task 1: Add The Combined Result Model + +**Files:** +- Modify: `dimelo/models.py` +- Modify: `dimelo/__init__.py` +- Test: `tests/test_models.py` + +- [ ] **Step 1: Write the failing tests** + +```python +def test_region_discovery_cluster_result_requires_non_none_values(): + with pytest.raises(ValueError, match="discovery"): + RegionDiscoveryClusterResult( + discovery=None, + clustering=_dummy_shared_cluster_result(), + selected_regions=pd.DataFrame(), + metadata={}, + ) + + +def test_region_discovery_cluster_result_accepts_valid_payloads(): + result = RegionDiscoveryClusterResult( + discovery=_dummy_region_discovery_result(), + clustering=_dummy_shared_cluster_result(), + selected_regions=pd.DataFrame([{"chromosome": "chr1", "start": 0, "end": 100}]), + metadata={"selection_mode": "top_n"}, + ) + assert list(result.selected_regions.columns) == ["chromosome", "start", "end"] +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_models.py -q` +Expected: FAIL with `NameError` or import errors for `RegionDiscoveryClusterResult` + +- [ ] **Step 3: Write the minimal model implementation** + +```python +@dataclass +class RegionDiscoveryClusterResult: + discovery: RegionDiscoveryResult + clustering: SharedClusterResult + selected_regions: pd.DataFrame + metadata: dict[str, Any] = field(default_factory=dict) + figures: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "discovery": self.discovery, + "clustering": self.clustering, + "selected_regions": self.selected_regions, + "metadata": self.metadata, + "figures": self.figures, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "RegionDiscoveryClusterResult requires non-None values for: " + f"{', '.join(missing)}" + ) +``` + +- [ ] **Step 4: Export the model** + +```python +from .models import ( + ... + RegionDiscoveryClusterResult, +) +``` + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_models.py -q` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add dimelo/models.py dimelo/__init__.py tests/test_models.py +git commit -m "feat: add discovery to cluster workflow result model" +``` + +### Task 2: Implement The Discovery To Cluster Workflow + +**Files:** +- Modify: `dimelo/workflows.py` +- Test: `tests/test_workflows.py` + +- [ ] **Step 1: Write the failing workflow tests** + +```python +def test_region_discovery_cluster_workflow_returns_both_results(monkeypatch): + monkeypatch.setattr(region_discovery, "scan_genome", _mock_discovery_result) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + + result = workflows.discovery_cluster_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + ) + + assert result.discovery.hits.shape[0] == 2 + assert result.clustering.model.mode == "region_anchored" + assert list(result.selected_regions.columns) == ["chromosome", "start", "end", "name", "score", "strand"] + + +def test_region_discovery_cluster_workflow_selects_top_n_hits(monkeypatch): + ... + result = workflows.discovery_cluster_workflow( + ..., + selection={"mode": "top_n", "top_n": 1}, + ) + assert result.selected_regions["name"].tolist() == ["chr1:0-500"] +``` + +- [ ] **Step 2: Run the targeted tests to verify they fail** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_workflows.py -q` +Expected: FAIL with missing `discovery_cluster_workflow` + +- [ ] **Step 3: Implement selection helpers and the orchestration entry point** + +```python +def _select_discovery_hits( + discovery: RegionDiscoveryResult, + *, + selection_mode: str, + top_n: int | None, + score_threshold: float | None, + adjusted_p_value_max: float | None, +) -> pd.DataFrame: + hits = discovery.hits.copy() + ... + return region_discovery.hits_to_bed(selected_hits) + + +def discovery_cluster_workflow( + *, + samples: Iterable[SampleSpec], + motifs: Iterable[str], + genome_sizes: dict[str, int], + discovery: dict[str, Any], + clustering: dict[str, Any], + selection: dict[str, Any] | None = None, +) -> RegionDiscoveryClusterResult: + discovery_result = region_discovery.scan_genome(...) + selected_regions = _select_discovery_hits(...) + region_spec = _selected_regions_to_region_spec(selected_regions) + clustering_result = shared_cluster_distribution( + samples=samples, + motifs=motifs, + matched_regions=region_spec, + **clustering, + ) + return RegionDiscoveryClusterResult( + discovery=discovery_result, + clustering=clustering_result, + selected_regions=selected_regions, + metadata={...}, + ) +``` + +- [ ] **Step 4: Cover error handling in tests** + +```python +def test_region_discovery_cluster_workflow_errors_when_no_hits_survive_selection(monkeypatch): + ... + with pytest.raises(ValueError, match="No discovery hits remained after selection"): + workflows.discovery_cluster_workflow(...) + + +def test_region_discovery_cluster_workflow_rejects_unknown_selection_mode(monkeypatch): + ... + with pytest.raises(ValueError, match="Unsupported selection mode"): + workflows.discovery_cluster_workflow(...) +``` + +- [ ] **Step 5: Run targeted workflow tests** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_workflows.py -q` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add dimelo/workflows.py tests/test_workflows.py +git commit -m "feat: add discovery to cluster workflow" +``` + +### Task 3: Document And Verify The Integrated User Flow + +**Files:** +- Modify: `docs/region-discovery.md` +- Modify: `docs/shared-clustering.md` +- Modify: `README.md` +- Test: `tests/test_workflows.py` + +- [ ] **Step 1: Add one integration test that uses real discovery hit rows as the clustering region input** + +```python +def test_region_discovery_cluster_workflow_passes_selected_regions_into_clustering(monkeypatch): + captured = {} + ... + def _fake_cluster(**kwargs): + captured["matched_regions"] = kwargs["matched_regions"] + return _mock_cluster_result(**kwargs) + + monkeypatch.setattr(workflows, "shared_cluster_distribution", _fake_cluster) + result = workflows.discovery_cluster_workflow(...) + + assert captured["matched_regions"] == ["chr1:0-500,+", "chr1:500-1000,-"] + assert result.selected_regions["name"].tolist() == ["chr1:0-500", "chr1:500-1000"] +``` + +- [ ] **Step 2: Document the default behavior** + +```markdown +from dimelo import workflows + +result = workflows.discovery_cluster_workflow( + samples=samples, + motifs=["A,0"], + genome_sizes={"chr1": 248956422}, + discovery={"window_size": 2000, "step_size": 500, "score": "beta_binomial", "contrast": contrast}, + clustering={"mode": "region_anchored", "n_clusters": 6}, + selection={"mode": "top_n", "top_n": 250}, +) +``` + +Explain: +- `discovery` config is passed to `scan_genome(...)` +- `selection` defaults to top-ranked hits +- `clustering` config is passed to `shared_cluster_distribution(...)` +- the workflow returns the BED-style `selected_regions` table, but passes a serializable region-spec derived from that table into `matched_regions` +- the result keeps `discovery`, `selected_regions`, and `clustering` together + +- [ ] **Step 3: Run the full verification slice** + +Run: `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_models.py tests/test_region_discovery.py tests/test_workflows.py -q` +Expected: PASS + +- [ ] **Step 4: Commit** + +```bash +git add docs/region-discovery.md docs/shared-clustering.md README.md tests/test_workflows.py +git commit -m "docs: add discovery to cluster workflow guide" +``` + +--- + +## Self-Review + +- Spec coverage: this plan adds the combined return type, the orchestration workflow, selection policy, error handling, and the user-facing docs for the one-call discovery-to-cluster path. +- Placeholder scan: every task names exact files, targeted tests, and concrete implementation points; there are no TODO placeholders. +- Type consistency: `RegionDiscoveryClusterResult`, `discovery_cluster_workflow`, and `selected_regions` are used consistently across tasks. diff --git a/docs/superpowers/plans/2026-04-02-docs-and-integration-cleanup.md b/docs/superpowers/plans/2026-04-02-docs-and-integration-cleanup.md new file mode 100644 index 0000000..6a30715 --- /dev/null +++ b/docs/superpowers/plans/2026-04-02-docs-and-integration-cleanup.md @@ -0,0 +1,252 @@ +# Docs And Integration Cleanup Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Normalize machine-specific paths in tracked historical `docs/superpowers` docs, then perform a narrow discoverability-focused integration cleanup around the already-implemented analysis and plotting work. + +**Architecture:** Execute this cleanup in two phases. Phase A normalizes tracked spec/plan docs so they are portable in the repo without changing their meaning. Phase B applies only the smallest public-surface cleanup needed to make the recently completed work easier to find and understand. The central status source of truth remains [docs/superpowers/README.md](../README.md). + +**Tech Stack:** Markdown docs, existing repo documentation, lightweight Python/package surface inspection where needed + +--- + +## File Structure + +- Modify: `docs/superpowers/specs/` + - Normalize machine-local path references to repo-relative or plain repo paths. +- Modify: `docs/superpowers/plans/` + - Normalize machine-local path references to repo-relative or plain repo paths. +- Modify: `docs/superpowers/README.md` + - Only if link/status corrections are needed after normalization. +- Modify if needed for integration cleanup: + - `README.md` + - `docs/region-contrasts.md` + - `docs/shared-clustering.md` + - `docs/region-discovery.md` + - `docs/global-analysis.md` + - `dimelo/__init__.py` + +### Task 1: Normalize machine-specific paths in tracked `docs/superpowers` docs + +**Files:** +- Modify: `docs/superpowers/specs/*.md` +- Modify: `docs/superpowers/plans/*.md` + +- [ ] **Step 1: Inventory the remaining machine-local path references** + +Run: + +```bash +rg -n "/Users/[^/]+/Documents/GitHub/dimelo-toolkit" docs/superpowers/specs docs/superpowers/plans +``` + +Expected: a list of tracked historical docs that still contain machine-local absolute paths. + +- [ ] **Step 2: Normalize Markdown links to repo-relative links** + +For Markdown links inside tracked superpowers docs, convert forms like: + +```md +[plotting.py](/absolute/path/to/dimelo-toolkit/dimelo/plotting.py) +``` + +to: + +```md +[plotting.py](../../../dimelo/plotting.py) +``` + +or the correct relative form from the document location. + +- [ ] **Step 3: Normalize plain path mentions and command snippets** + +For plain path references or shell commands inside tracked superpowers docs, replace machine-local prefixes with repo-relative or repo-root-style paths. For example: + +```text +/absolute/path/to/dimelo-toolkit/docs/superpowers/plans/2026-04-01-region-contrasts-plotting.md +``` + +becomes: + +```text +docs/superpowers/plans/2026-04-01-region-contrasts-plotting.md +``` + +Do this conservatively: preserve the original meaning and command intent, only remove machine-specific leakage. + +- [ ] **Step 4: Run a second grep to confirm the path leak is gone** + +Run: + +```bash +rg -n "/Users/[^/]+/Documents/GitHub/dimelo-toolkit" docs/superpowers/specs docs/superpowers/plans +``` + +Expected: no output, unless an intentionally preserved example remains and is documented in the final summary. + +- [ ] **Step 5: Commit the normalization pass** + +```bash +git add docs/superpowers/specs docs/superpowers/plans +git commit -m "docs: normalize superpowers doc paths" +``` + +### Task 2: Verify and repair `docs/superpowers` navigation after normalization + +**Files:** +- Modify if needed: `docs/superpowers/README.md` +- Verify: `docs/superpowers/specs/` +- Verify: `docs/superpowers/plans/` + +- [ ] **Step 1: Verify the central index links still resolve** + +Run: + +```bash +for f in $(rg -o "\\((specs|plans)/[^)]+" docs/superpowers/README.md | tr -d "("); do test -f "docs/superpowers/$f" || echo "MISSING $f"; done +``` + +Expected: no output. + +- [ ] **Step 2: Fix any broken links or stale status wording in the index** + +If the verification step reports missing links or if the normalization changed path conventions enough to make the README confusing, update `docs/superpowers/README.md` minimally. + +Keep edits narrow: + +- link correction +- wording clarification +- status correction if a recent implementation slice is now clearly misclassified + +- [ ] **Step 3: Verify the `docs/superpowers` tree is fully tracked** + +Run: + +```bash +git status --short docs/superpowers +``` + +Expected: no `??` entries remain under `docs/superpowers`. + +- [ ] **Step 4: Commit the index repair pass if needed** + +```bash +git add docs/superpowers/README.md +git commit -m "docs: refresh superpowers index links" +``` + +If no README changes were needed, skip this commit. + +### Task 3: Apply narrow discoverability-focused integration cleanup + +**Files:** +- Inspect: `README.md` +- Inspect: `docs/region-contrasts.md` +- Inspect: `docs/shared-clustering.md` +- Inspect: `docs/region-discovery.md` +- Inspect: `docs/global-analysis.md` +- Inspect: `dimelo/__init__.py` +- Modify only if needed: the minimum subset of the above + +- [ ] **Step 1: Inventory obvious discoverability gaps around completed work** + +Check for: + +- recently added helpers/workflows not mentioned where users would look +- stale references to “later” work that is now implemented +- missing package exports for helpers users reasonably expect to import directly + +Run these focused inspections: + +```bash +rg -n "prepare_region_contrast_profile_data|prepare_region_contrast_heatmap_data|global_analysis|region_discovery|shared_cluster_distribution" README.md docs/*.md dimelo/__init__.py +``` + +- [ ] **Step 2: Apply only the minimum integration cleanup needed** + +Examples of acceptable changes: + +- add one missing cross-link in `README.md` +- fix stale wording in an analysis guide +- expose an already-implemented helper in `dimelo/__init__.py` if that is an obvious user-facing expectation + +Examples of disallowed changes: + +- broad module refactors +- new plotting features +- renaming stable APIs for style alone + +- [ ] **Step 3: Verify touched docs or exports** + +If only docs changed, run: + +```bash +rg -n "prepare_region_contrast_profile_data|prepare_region_contrast_heatmap_data|shared_cluster_distribution|global_analysis|region_discovery" README.md docs/*.md +``` + +If `dimelo/__init__.py` changed, also run: + +```bash +python3.11 - <<'PY' +import dimelo +print("dimelo import ok") +PY +``` + +Expected: docs references resolve sensibly, and `dimelo` still imports if exports changed. + +- [ ] **Step 4: Commit the integration cleanup** + +```bash +git add README.md docs dimelo/__init__.py +git commit -m "docs: tighten analysis discoverability" +``` + +Only include files actually changed. + +### Task 4: Final verification and cleanup summary + +**Files:** +- Verify: `docs/superpowers/` +- Verify any files touched in Task 3 + +- [ ] **Step 1: Confirm no machine-local path leaks remain in tracked superpowers docs** + +Run: + +```bash +rg -n "/Users/[^/]+/Documents/GitHub/[^/]+" docs/superpowers/specs docs/superpowers/plans docs/superpowers/README.md +``` + +Expected: no output unless an intentional exception is documented. + +- [ ] **Step 2: Confirm the working tree is clean** + +Run: + +```bash +git status --short +``` + +Expected: no unexpected modified/untracked files remain from this cleanup slice. + +- [ ] **Step 3: Commit any final small verification-driven fix** + +```bash +git add -A +git commit -m "docs: finalize cleanup verification" +``` + +Only do this if verification required one last small correction. Otherwise skip. + +## Self-Review + +- Spec coverage: + - historical path portability: Task 1 + - index/navigation verification: Task 2 + - public-surface discoverability cleanup: Task 3 + - final verification: Task 4 +- Placeholder scan: + - no `TODO`, `TBD`, or “fix later” steps remain +- Type consistency: + - all path references use the current `docs/superpowers` layout and the current repo root diff --git a/docs/superpowers/plans/2026-04-02-docs-coherence-and-discoverability.md b/docs/superpowers/plans/2026-04-02-docs-coherence-and-discoverability.md new file mode 100644 index 0000000..4a8e027 --- /dev/null +++ b/docs/superpowers/plans/2026-04-02-docs-coherence-and-discoverability.md @@ -0,0 +1,267 @@ +# Docs Coherence And Discoverability Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make the internal and user-facing docs coherent with the current branch state, and apply only minimal package-surface cleanup where the docs clearly demand it. + +**Architecture:** Execute this as a docs-first cleanup in three layers: refresh the internal status map, sweep the main user-facing guides for naming and flow consistency, then inspect the top-level package surface for any small discoverability mismatch exposed by the docs pass. Keep the slice additive and non-behavioral. + +**Tech Stack:** Markdown docs, existing Python package surface, lightweight repo inspection + +--- + +## Status Note (2026-04-14) + +This document remains the detailed execution playbook for docs coherence work. +The active cross-plan backlog now lives in +`docs/superpowers/plans/2026-04-14-unified-remaining-work-plan.md`. + +## File Map + +- Modify: `docs/superpowers/README.md` + - refresh status-map entries and add missing spec/plan links +- Modify as needed: + - `README.md` + - `docs/global-analysis.md` + - `docs/region-discovery.md` + - `docs/region-contrasts.md` + - `docs/shared-clustering.md` + - only for wording, cross-link, and role-consistency cleanup +- Modify only if clearly needed: + - `dimelo/__init__.py` + - only for a small discoverability/export mismatch exposed by the docs + +## Task 1: Refresh The Internal Status Map + +**Files:** +- Modify: `docs/superpowers/README.md` +- Verify: `docs/superpowers/specs/` +- Verify: `docs/superpowers/plans/` + +- [ ] **Step 1: Inventory missing or stale entries** + +Inspect the current index and repo contents: + +```bash +sed -n '1,260p' docs/superpowers/README.md +ls docs/superpowers/specs docs/superpowers/plans +``` + +Expected findings: + +- the internal index is missing the newest plotting specs/plans +- status labels may understate work that is already implemented + +- [ ] **Step 2: Update the status map minimally** + +Edit `docs/superpowers/README.md` to: + +- add: + - `specs/2026-04-02-region-discovery-plotting-design.md` + - `specs/2026-04-02-global-analysis-plotting-design.md` + - `specs/2026-04-02-docs-coherence-and-discoverability-design.md` + - `plans/2026-04-02-region-discovery-plotting.md` + - `plans/2026-04-02-global-analysis-plotting.md` +- refresh status labels where current branch reality clearly differs +- update the `Current Themes` section only as needed to reflect the newer plotting helper coverage + +Keep the file concise. Do not turn it into a changelog. + +- [ ] **Step 3: Verify index links resolve** + +Run: + +```bash +for f in $(rg -o "\\((specs|plans)/[^)]+" docs/superpowers/README.md | tr -d "("); do test -f "docs/superpowers/$f" || echo "MISSING $f"; done +``` + +Expected: no output. + +- [ ] **Step 4: Commit the status-map refresh** + +```bash +git add docs/superpowers/README.md +git commit -m "docs: refresh superpowers status map" +``` + +## Task 2: Sweep User-Facing Guides For Consistency + +**Files:** +- Modify as needed: + - `README.md` + - `docs/global-analysis.md` + - `docs/region-discovery.md` + - `docs/region-contrasts.md` + - `docs/shared-clustering.md` + +- [ ] **Step 1: Inventory stale wording and naming drift** + +Run focused searches: + +```bash +rg -n "once implemented|later for|not yet|planned|prepare_region_contrast_|prepare_region_discovery_|prepare_global_analysis_|shared_cluster_distribution|discovery_cluster_workflow|discovery_cluster_contrast_workflow" README.md docs/*.md +``` + +Inspect the matching sections and note only concrete consistency issues such as: + +- stale “future” wording +- inconsistent helper names +- inconsistent module-role descriptions +- missing short cross-links where users would naturally look + +- [ ] **Step 2: Apply a narrow consistency sweep** + +Edit only the minimum subset of the user-facing guides needed so they: + +- tell one consistent story about the analysis stack +- use the same helper/workflow names everywhere +- describe plotting-helper coverage honestly +- avoid implying that already-shipped helpers or workflows are still future work + +Allowed changes: + +- wording cleanup +- short cross-link additions +- short clarifications of current helper coverage + +Disallowed changes: + +- major rewrites +- new feature documentation beyond what is already implemented +- API renames for style + +- [ ] **Step 3: Verify the guide sweep** + +Run: + +```bash +rg -n "prepare_region_contrast_|prepare_region_discovery_|prepare_global_analysis_|shared_cluster_distribution|discovery_cluster_workflow|discovery_cluster_contrast_workflow" README.md docs/*.md +``` + +Expected: matches should reflect the intended current helper/workflow names consistently. + +- [ ] **Step 4: Commit the docs consistency sweep** + +```bash +git add README.md docs/global-analysis.md docs/region-discovery.md docs/region-contrasts.md docs/shared-clustering.md +git commit -m "docs: align analysis guide naming" +``` + +Only include files actually changed. + +## Task 3: Apply Small Package-Surface Cleanup If Needed + +**Files:** +- Inspect: `dimelo/__init__.py` +- Modify only if clearly needed: `dimelo/__init__.py` + +- [ ] **Step 1: Check whether the docs expose an import mismatch** + +Inspect the top-level package surface: + +```bash +sed -n '1,220p' dimelo/__init__.py +``` + +Compare that with the docs updated in Task 2. + +Only proceed if there is a concrete mismatch such as: + +- docs telling users to import a top-level module that is not actually exposed +- docs implying a clearly user-facing module path that the package does not support + +- [ ] **Step 2: Make the minimum export fix if needed** + +If and only if a concrete mismatch exists, update `dimelo/__init__.py` minimally. + +Allowed: + +- add one missing module export already treated as top-level in the docs + +Disallowed: + +- broad re-export churn +- exporting helper functions just for convenience +- aesthetic reshaping of `__all__` + +- [ ] **Step 3: Verify imports if `__init__.py` changed** + +Run: + +```bash +python3.11 - <<'PY' +import dimelo +print("dimelo import ok") +PY +``` + +Expected: `dimelo import ok` + +- [ ] **Step 4: Commit the package-surface cleanup if needed** + +```bash +git add dimelo/__init__.py +git commit -m "docs: align package surface with guides" +``` + +If no package-surface fix was needed, skip this commit. + +## Final Verification + +**Files:** +- Verify: `docs/superpowers/README.md` +- Verify any touched user-facing docs +- Verify `dimelo/__init__.py` if changed + +- [ ] **Step 1: Confirm the internal index resolves** + +Run: + +```bash +for f in $(rg -o "\\((specs|plans)/[^)]+" docs/superpowers/README.md | tr -d "("); do test -f "docs/superpowers/$f" || echo "MISSING $f"; done +``` + +Expected: no output. + +- [ ] **Step 2: Confirm the working tree is clean** + +Run: + +```bash +git status --short +``` + +Expected: no output. + +- [ ] **Step 3: Summarize the coherence outcomes** + +Capture in the handoff: + +- which status-map entries were added or corrected +- which user-facing docs were adjusted +- whether any package-surface change was needed +- the main remaining next-step categories after docs are coherent + +## Self-Review + +Spec coverage check: + +- internal status-map accuracy: covered in Task 1 +- user-facing guide consistency: covered in Task 2 +- small package-surface cleanup only if needed: covered in Task 3 +- validation and clean-state closeout: covered in Final Verification + +Placeholder scan: + +- no `TODO`, `TBD`, or undefined steps remain +- each task includes exact files, commands, and narrow allowed scope + +Type consistency: + +- all referenced helper/workflow names match the current approved naming: + - `shared_cluster_distribution(...)` + - `discovery_cluster_workflow(...)` + - `discovery_cluster_contrast_workflow(...)` + - `prepare_region_contrast_*` + - `prepare_region_discovery_*` + - `prepare_global_analysis_*` diff --git a/docs/superpowers/plans/2026-04-02-global-analysis-plotting.md b/docs/superpowers/plans/2026-04-02-global-analysis-plotting.md new file mode 100644 index 0000000..de7876d --- /dev/null +++ b/docs/superpowers/plans/2026-04-02-global-analysis-plotting.md @@ -0,0 +1,684 @@ +# Global Analysis Plotting Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add renderer-neutral plotting-prep helpers for `dimelo.global_analysis` summary and broad-window views. + +**Architecture:** Extend the shared plotting module with two result-centric helpers that consume `GlobalAnalysisResult` and emit stable payload dictionaries. Keep the implementation additive and data-first: no `run_global_analysis()` changes, no renderer coupling, and per-contig broad-window organization by default. + +**Tech Stack:** Python 3.11, pandas, pytest, existing `dimelo.plotting`, `dimelo.global_analysis`, and `dimelo.models` modules + +--- + +## File Map + +- Modify: `dimelo/plotting.py` + - add internal validation/helpers for `GlobalAnalysisResult` + - add `prepare_global_analysis_summary_data(...)` + - add `prepare_global_analysis_window_data(...)` +- Modify: `tests/test_plotting.py` + - add focused plotting helper coverage using synthetic `GlobalAnalysisResult` fixtures +- Modify: `docs/global-analysis.md` + - document the new plotting-prep helpers and their data-first outputs +- Modify: `README.md` + - add one short note about the new global-analysis plotting helpers + +## Task 1: Add Failing Tests For Global Summary Plot Prep + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/models.py` +- Reference: `dimelo/plotting.py` + +- [ ] **Step 1: Write the failing summary-prep tests** + +Add these tests near the other plotting helper tests in `tests/test_plotting.py`: + +```python +from dimelo.models import GlobalAnalysisResult + + +def _make_global_analysis_result() -> GlobalAnalysisResult: + summary = pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "modified_count": 10, + "valid_count": 20, + "global_fraction": 0.5, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "modified_count": 16, + "valid_count": 20, + "global_fraction": 0.8, + }, + { + "sample_id": "s3", + "condition": "treated", + "replicate": 2, + "motif": "A,0", + "modified_count": 12, + "valid_count": 20, + "global_fraction": 0.6, + }, + ] + ) + normalization_factors = pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "global_fraction": 0.5, + "reference_fraction": 0.6333333333, + "global_offset": -0.1333333333, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "global_fraction": 0.8, + "reference_fraction": 0.6333333333, + "global_offset": 0.1666666667, + }, + { + "sample_id": "s3", + "condition": "treated", + "replicate": 2, + "motif": "A,0", + "global_fraction": 0.6, + "reference_fraction": 0.6333333333, + "global_offset": -0.0333333333, + }, + ] + ) + windows = pd.DataFrame( + columns=[ + "sample_id", + "condition", + "replicate", + "motif", + "window_id", + "chromosome", + "start", + "end", + "strand", + "modified_count", + "valid_count", + "window_fraction", + ] + ) + return GlobalAnalysisResult( + summary=summary, + windows=windows, + normalization_factors=normalization_factors, + plot_data={ + "global_fraction_bar": summary.copy(), + "window_fraction_table": windows.copy(), + }, + metadata={"window_size": 100000, "step_size": 50000}, + ) + + +def test_prepare_global_analysis_summary_data_returns_expected_tables(): + result = _make_global_analysis_result() + + payload = plotting.prepare_global_analysis_summary_data(result=result) + + assert set(payload) == {"sample_summary", "condition_summary", "normalization_table", "metadata"} + assert payload["sample_summary"]["sample_id"].tolist() == ["s1", "s2", "s3"] + assert payload["normalization_table"]["sample_id"].tolist() == ["s1", "s2", "s3"] + assert payload["condition_summary"]["condition"].tolist() == ["NS", "treated"] + assert payload["condition_summary"]["sample_n"].tolist() == [1, 2] + assert payload["metadata"]["motifs"] == ["A,0"] + + +def test_prepare_global_analysis_summary_data_computes_condition_means(): + result = _make_global_analysis_result() + + payload = plotting.prepare_global_analysis_summary_data(result=result) + condition_summary = payload["condition_summary"].set_index("condition") + + assert condition_summary.loc["NS", "global_fraction_mean"] == pytest.approx(0.5) + assert condition_summary.loc["treated", "global_fraction_mean"] == pytest.approx(0.7) + assert condition_summary.loc["treated", "global_fraction_median"] == pytest.approx(0.7) + + +def test_prepare_global_analysis_summary_data_filters_motifs(): + result = _make_global_analysis_result() + + payload = plotting.prepare_global_analysis_summary_data( + result=result, + motifs=["A,0"], + ) + + assert payload["sample_summary"]["motif"].unique().tolist() == ["A,0"] + assert payload["normalization_table"]["motif"].unique().tolist() == ["A,0"] +``` + +- [ ] **Step 2: Run the new summary-prep tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "global_analysis_summary_data" -q +``` + +Expected: FAIL because `prepare_global_analysis_summary_data(...)` does not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_plotting.py +git commit -m "test: add global analysis summary plotting coverage" +``` + +## Task 2: Implement Global Summary Plot Prep + +**Files:** +- Modify: `dimelo/plotting.py` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Add the internal global-analysis helpers** + +Add these internal helpers in `dimelo/plotting.py` near the other result-validation utilities: + +```python +def _validate_global_analysis_result(result) -> None: + if result is None: + raise ValueError("plotting helpers require a GlobalAnalysisResult.") + required_attrs = ("summary", "windows", "normalization_factors", "metadata") + if not all(hasattr(result, attr) for attr in required_attrs): + raise TypeError("plotting helpers require a GlobalAnalysisResult-like object.") + + +def _filter_motif_table(table: pd.DataFrame, motifs: list[str] | None, *, owner: str) -> pd.DataFrame: + if motifs is None: + return table.copy() + filtered = table.loc[table["motif"].isin(motifs)].copy() + if filtered.empty: + raise ValueError(f"Requested motifs are not present in {owner}.") + return filtered +``` + +- [ ] **Step 2: Add `prepare_global_analysis_summary_data(...)`** + +Add this public function in `dimelo/plotting.py` near the other public plotting helpers: + +```python +def prepare_global_analysis_summary_data( + *, + result, + motifs: list[str] | None = None, + aggregate_conditions: bool = True, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_global_analysis_result(result) + + sample_summary = _filter_motif_table(result.summary, motifs, owner="result.summary") + normalization_table = _filter_motif_table( + result.normalization_factors, + motifs, + owner="result.normalization_factors", + ) + + if sample_summary.empty: + condition_summary = pd.DataFrame( + columns=[ + "condition", + "motif", + "global_fraction_mean", + "global_fraction_median", + "sample_n", + ] + ) + motif_values: list[str] = [] if motifs is None else list(motifs) + else: + motif_values = sample_summary["motif"].drop_duplicates().tolist() + if aggregate_conditions: + condition_summary = ( + sample_summary.groupby(["condition", "motif"], as_index=False, sort=False) + .agg( + global_fraction_mean=("global_fraction", "mean"), + global_fraction_median=("global_fraction", "median"), + sample_n=("sample_id", "nunique"), + ) + .copy() + ) + else: + condition_summary = pd.DataFrame( + columns=[ + "condition", + "motif", + "global_fraction_mean", + "global_fraction_median", + "sample_n", + ] + ) + + return { + "sample_summary": sample_summary.reset_index(drop=True), + "condition_summary": condition_summary.reset_index(drop=True), + "normalization_table": normalization_table.reset_index(drop=True), + "metadata": { + "motifs": motif_values, + "aggregate_conditions": aggregate_conditions, + }, + } +``` + +- [ ] **Step 3: Run the summary-prep tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "global_analysis_summary_data" -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit the summary-prep implementation** + +```bash +git add dimelo/plotting.py tests/test_plotting.py +git commit -m "feat: add global analysis summary plotting prep" +``` + +## Task 3: Add Failing Tests For Global Window Plot Prep + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/plotting.py` + +- [ ] **Step 1: Add the failing window-prep tests** + +Append these tests in `tests/test_plotting.py` near the new global summary tests: + +```python +def _make_global_window_result() -> GlobalAnalysisResult: + result = _make_global_analysis_result() + result.windows = pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-100", + "chromosome": "chr1", + "start": 0, + "end": 100, + "strand": ".", + "modified_count": 5, + "valid_count": 10, + "window_fraction": 0.5, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-100", + "chromosome": "chr1", + "start": 0, + "end": 100, + "strand": ".", + "modified_count": 8, + "valid_count": 10, + "window_fraction": 0.8, + }, + { + "sample_id": "s3", + "condition": "treated", + "replicate": 2, + "motif": "A,0", + "window_id": "chr2:0-100", + "chromosome": "chr2", + "start": 0, + "end": 100, + "strand": ".", + "modified_count": 6, + "valid_count": 10, + "window_fraction": 0.6, + }, + ] + ) + result.plot_data["window_fraction_table"] = result.windows.copy() + return result + + +def test_prepare_global_analysis_window_data_returns_expected_tables(): + result = _make_global_window_result() + + payload = plotting.prepare_global_analysis_window_data(result=result) + + assert set(payload) == {"window_table", "condition_window_table", "metadata"} + assert payload["window_table"]["contig"].tolist() == ["chr1", "chr1", "chr2"] + assert payload["window_table"]["window_midpoint"].tolist() == [50.0, 50.0, 50.0] + assert payload["metadata"]["contig_order"] == ["chr1", "chr2"] + + +def test_prepare_global_analysis_window_data_filters_contigs_in_requested_order(): + result = _make_global_window_result() + + payload = plotting.prepare_global_analysis_window_data( + result=result, + contigs=["chr2"], + ) + + assert payload["metadata"]["contig_order"] == ["chr2"] + assert payload["window_table"]["contig"].tolist() == ["chr2"] + + +def test_prepare_global_analysis_window_data_aggregates_conditions(): + result = _make_global_window_result() + + payload = plotting.prepare_global_analysis_window_data( + result=result, + aggregate_conditions=True, + ) + + condition_rows = payload["condition_window_table"] + ns_row = condition_rows.loc[condition_rows["condition"] == "NS"].iloc[0] + treated_chr1 = condition_rows.loc[ + (condition_rows["condition"] == "treated") + & (condition_rows["contig"] == "chr1") + ].iloc[0] + + assert ns_row["window_fraction_mean"] == pytest.approx(0.5) + assert treated_chr1["window_fraction_mean"] == pytest.approx(0.8) +``` + +- [ ] **Step 2: Run the new window-prep tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "global_analysis_window_data" -q +``` + +Expected: FAIL because `prepare_global_analysis_window_data(...)` does not yet exist. + +- [ ] **Step 3: Commit the failing window-prep tests** + +```bash +git add tests/test_plotting.py +git commit -m "test: add global analysis window plotting coverage" +``` + +## Task 4: Implement Global Window Plot Prep + +**Files:** +- Modify: `dimelo/plotting.py` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Add `prepare_global_analysis_window_data(...)`** + +Add this public function in `dimelo/plotting.py` near the new summary helper: + +```python +def prepare_global_analysis_window_data( + *, + result, + contigs: list[str] | None = None, + motifs: list[str] | None = None, + aggregate_conditions: bool = False, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_global_analysis_result(result) + + window_table = _filter_motif_table(result.windows, motifs, owner="result.windows") + + if window_table.empty: + condition_window_table = pd.DataFrame( + columns=[ + "condition", + "motif", + "contig", + "start", + "end", + "window_midpoint", + "window_fraction_mean", + "window_fraction_median", + "sample_n", + ] + ) + return { + "window_table": pd.DataFrame( + columns=[ + "sample_id", + "condition", + "replicate", + "motif", + "window_id", + "contig", + "start", + "end", + "strand", + "window_fraction", + "window_midpoint", + ] + ), + "condition_window_table": condition_window_table, + "metadata": { + "contig_order": [], + "motifs": [] if motifs is None else list(motifs), + "aggregate_conditions": aggregate_conditions, + }, + } + + window_table = window_table.copy() + window_table["contig"] = window_table["chromosome"] + + if contigs is not None: + window_table = window_table.loc[window_table["contig"].isin(contigs)].copy() + if window_table.empty: + raise ValueError("Requested contigs are not present in result.windows.") + contig_order = list(contigs) + else: + contig_order = window_table["contig"].drop_duplicates().tolist() + + window_table["window_midpoint"] = (window_table["start"] + window_table["end"]) / 2.0 + window_table = _sort_discovery_table( + window_table, + contig_order=contig_order, + sort_columns=["start", "end", "sample_id"], + ) + + if aggregate_conditions: + condition_window_table = ( + window_table.groupby( + ["condition", "motif", "contig", "start", "end", "window_midpoint"], + as_index=False, + sort=False, + ) + .agg( + window_fraction_mean=("window_fraction", "mean"), + window_fraction_median=("window_fraction", "median"), + sample_n=("sample_id", "nunique"), + ) + .copy() + ) + condition_window_table = _sort_discovery_table( + condition_window_table, + contig_order=contig_order, + sort_columns=["start", "end", "condition"], + ) + else: + condition_window_table = pd.DataFrame( + columns=[ + "condition", + "motif", + "contig", + "start", + "end", + "window_midpoint", + "window_fraction_mean", + "window_fraction_median", + "sample_n", + ] + ) + + return { + "window_table": window_table.reset_index(drop=True), + "condition_window_table": condition_window_table.reset_index(drop=True), + "metadata": { + "contig_order": contig_order, + "motifs": window_table["motif"].drop_duplicates().tolist(), + "aggregate_conditions": aggregate_conditions, + }, + } +``` + +- [ ] **Step 2: Run the window-prep tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "global_analysis_window_data" -q +``` + +Expected: PASS. + +- [ ] **Step 3: Run the combined global-analysis plotting subset** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "global_analysis_summary_data or global_analysis_window_data" -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit the window-prep implementation** + +```bash +git add dimelo/plotting.py tests/test_plotting.py +git commit -m "feat: add global analysis window plotting prep" +``` + +## Task 5: Document The New Global Analysis Plotting Helpers + +**Files:** +- Modify: `docs/global-analysis.md` +- Modify: `README.md` +- Verify: `tests/test_plotting.py` + +- [ ] **Step 1: Add a short global-analysis plotting section to the guide** + +Append a short section to `docs/global-analysis.md` with an example like: + +```python +from dimelo import plotting + +summary_payload = plotting.prepare_global_analysis_summary_data( + result=result, + aggregate_conditions=True, +) + +window_payload = plotting.prepare_global_analysis_window_data( + result=result, + aggregate_conditions=True, +) +``` + +Explain briefly: + +- summary payloads include sample-level and condition-level views +- normalization values are exposed through `normalization_table` +- window payloads stay per-contig by default +- both helpers are data-prep only and renderer-neutral + +- [ ] **Step 2: Add one short README note** + +Add one short paragraph to `README.md` near the analysis guides or plotting section explaining: + +- `global_analysis` now has renderer-neutral summary and broad-window plotting helpers +- they consume `GlobalAnalysisResult` +- broad-window payloads stay per-contig by default + +- [ ] **Step 3: Run the touched plotting test file** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit the docs pass** + +```bash +git add docs/global-analysis.md README.md tests/test_plotting.py dimelo/plotting.py +git commit -m "docs: add global analysis plotting guide" +``` + +## Final Verification + +**Files:** +- Verify: `dimelo/plotting.py` +- Verify: `tests/test_plotting.py` +- Verify: `docs/global-analysis.md` +- Verify: `README.md` + +- [ ] **Step 1: Run the focused plotting verification** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl XDG_CACHE_HOME=/tmp/xdg-cache PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q +``` + +Expected: PASS. + +- [ ] **Step 2: Confirm the working tree is clean** + +Run: + +```bash +git status --short +``` + +Expected: no output. + +- [ ] **Step 3: Summarize outcomes** + +Capture in the handoff: + +- which public helpers were added +- which payload tables they return +- that summary helpers expose both sample-level and condition-level views +- that broad-window helpers stay per-contig by default + +## Self-Review + +Spec coverage check: + +- summary helper with sample and condition views: covered in Tasks 1-2 +- normalization-table exposure: covered in Task 2 and Task 5 docs +- broad-window helper with per-contig default: covered in Tasks 3-4 and Task 5 docs +- additive/data-first behavior: preserved throughout with no `run_global_analysis()` changes + +Placeholder scan: + +- no `TODO`, `TBD`, or undefined steps remain +- each task includes exact files, commands, and implementation guidance + +Type consistency: + +- helper names match the approved spec +- payload keys remain aligned with the design: + - `sample_summary` + - `condition_summary` + - `normalization_table` + - `window_table` + - `condition_window_table` + - `metadata` diff --git a/docs/superpowers/plans/2026-04-02-region-discovery-plotting.md b/docs/superpowers/plans/2026-04-02-region-discovery-plotting.md new file mode 100644 index 0000000..fd9b7d9 --- /dev/null +++ b/docs/superpowers/plans/2026-04-02-region-discovery-plotting.md @@ -0,0 +1,616 @@ +# Region Discovery Plotting Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add renderer-neutral plotting-prep helpers for `dimelo.region_discovery` scan overviews and local top-hit context views. + +**Architecture:** Extend the shared plotting module with two result-centric helpers that consume `RegionDiscoveryResult` and emit stable payload dictionaries. Keep the implementation additive: no `scan_genome()` signature changes, no renderer coupling, and no normalized region-axis logic for this slice. + +**Tech Stack:** Python 3.11, pandas, pytest, existing `dimelo.plotting` and `dimelo.models` modules + +--- + +## File Map + +- Modify: `dimelo/plotting.py` + - add internal validation and extraction helpers for `RegionDiscoveryResult` + - add `prepare_region_discovery_scan_data(...)` + - add `prepare_region_discovery_hit_context_data(...)` +- Modify: `tests/test_plotting.py` + - add focused discovery plotting helper coverage using synthetic `RegionDiscoveryResult` objects +- Modify: `docs/region-discovery.md` + - document the new plotting-prep helpers and their data-first outputs +- Modify: `README.md` + - add one short note under plotting/analysis guides for discovery plotting helpers + +## Task 1: Add Failing Tests For Discovery Scan Plot Prep + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/models.py` +- Reference: `dimelo/plotting.py` + +- [ ] **Step 1: Write the failing scan-prep tests** + +Add these tests near the other plotting helper tests in `tests/test_plotting.py`: + +```python +from dimelo.models import RegionDiscoveryResult + + +def _make_region_discovery_result() -> RegionDiscoveryResult: + windows = pd.DataFrame( + [ + { + "window_id": "chr1:0-100:+", + "chromosome": "chr1", + "start": 0, + "end": 100, + "strand": "+", + "score_value": 0.2, + "rank": 3, + }, + { + "window_id": "chr1:100-200:+", + "chromosome": "chr1", + "start": 100, + "end": 200, + "strand": "+", + "score_value": 0.9, + "rank": 1, + }, + { + "window_id": "chr2:0-100:+", + "chromosome": "chr2", + "start": 0, + "end": 100, + "strand": "+", + "score_value": 0.5, + "rank": 2, + }, + ] + ) + hits = windows.loc[windows["rank"] <= 2].copy() + return RegionDiscoveryResult( + windows=windows, + hits=hits, + plot_data={ + "window_score_table": windows.copy(), + "top_hits_table": hits.copy(), + }, + metadata={ + "score": "effect_size_only", + "contrast_mode": "pairwise", + "merge_hits": False, + }, + ) + + +def test_prepare_region_discovery_scan_data_returns_expected_tables(): + result = _make_region_discovery_result() + + payload = plotting.prepare_region_discovery_scan_data(result=result) + + assert set(payload) == {"scan_table", "hit_table", "metadata"} + assert list(payload["scan_table"]["contig"]) == ["chr1", "chr1", "chr2"] + assert list(payload["hit_table"]["rank"]) == [1, 2] + assert payload["scan_table"]["is_hit"].tolist() == [False, True, True] + assert payload["metadata"]["contig_order"] == ["chr1", "chr2"] + assert payload["metadata"]["score_column"] == "score_value" + + +def test_prepare_region_discovery_scan_data_filters_contigs_in_requested_order(): + result = _make_region_discovery_result() + + payload = plotting.prepare_region_discovery_scan_data( + result=result, + contigs=["chr2", "chr1"], + ) + + assert payload["metadata"]["contig_order"] == ["chr2", "chr1"] + assert payload["scan_table"]["contig"].tolist() == ["chr2", "chr1", "chr1"] + + +def test_prepare_region_discovery_scan_data_limits_hit_overlay(): + result = _make_region_discovery_result() + + payload = plotting.prepare_region_discovery_scan_data( + result=result, + top_n_hits=1, + ) + + assert payload["hit_table"]["rank"].tolist() == [1] + assert payload["scan_table"]["is_hit"].tolist() == [False, True, False] +``` + +- [ ] **Step 2: Run the new scan-prep tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "region_discovery_scan_data" -q +``` + +Expected: FAIL because `prepare_region_discovery_scan_data(...)` does not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_plotting.py +git commit -m "test: add region discovery scan plotting coverage" +``` + +## Task 2: Implement Discovery Scan Plot Prep + +**Files:** +- Modify: `dimelo/plotting.py` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Add the internal discovery scan helpers** + +Add these internal helpers in `dimelo/plotting.py` near the other plot-data validation utilities: + +```python +def _validate_region_discovery_result(result): + if result is None: + raise ValueError("plotting helpers require a RegionDiscoveryResult.") + if not hasattr(result, "windows") or not hasattr(result, "hits") or not hasattr(result, "metadata"): + raise TypeError("plotting helpers require a RegionDiscoveryResult-like object.") + + +def _select_discovery_score_column(windows: pd.DataFrame, score_column: str | None) -> str: + if score_column is not None: + if score_column not in windows.columns: + raise ValueError(f"Unknown discovery score column: {score_column}") + return score_column + if "score_value" in windows.columns: + return "score_value" + raise ValueError("Could not infer a discovery score column from RegionDiscoveryResult.windows.") + + +def _empty_discovery_scan_table(score_column: str) -> pd.DataFrame: + return pd.DataFrame( + columns=[ + "window_id", + "contig", + "start", + "end", + "strand", + score_column, + "window_midpoint", + "is_hit", + ] + ) +``` + +- [ ] **Step 2: Add `prepare_region_discovery_scan_data(...)`** + +Add this public function in `dimelo/plotting.py` near the other public plotting helpers: + +```python +def prepare_region_discovery_scan_data( + *, + result, + contigs: list[str] | None = None, + top_n_hits: int | None = 100, + score_column: str | None = None, + include_all_windows: bool = True, +): + _validate_region_discovery_result(result) + + windows = result.windows.copy() + hits = result.hits.copy() + active_score_column = _select_discovery_score_column(windows, score_column) + + if windows.empty: + empty_scan = _empty_discovery_scan_table(active_score_column) + empty_hits = pd.DataFrame(columns=["window_id", "contig", "start", "end", "strand", active_score_column]) + return { + "scan_table": empty_scan, + "hit_table": empty_hits, + "metadata": { + "contig_order": [], + "score_column": active_score_column, + "score_mode": result.metadata.get("score"), + "contrast_mode": result.metadata.get("contrast_mode"), + "merge_hits": result.metadata.get("merge_hits"), + "top_n_hits": top_n_hits, + }, + } + + windows["contig"] = windows["chromosome"] + hits["contig"] = hits["chromosome"] + + if contigs is not None: + windows = windows.loc[windows["contig"].isin(contigs)].copy() + hits = hits.loc[hits["contig"].isin(contigs)].copy() + if windows.empty: + raise ValueError("Requested contigs are not present in RegionDiscoveryResult.windows.") + contig_order = list(contigs) + else: + contig_order = windows["contig"].drop_duplicates().tolist() + + if top_n_hits is not None and not hits.empty and "rank" in hits.columns: + hits = hits.sort_values("rank", kind="stable").head(top_n_hits).copy() + + hit_window_ids = set(hits.get("window_id", pd.Series(dtype="object")).tolist()) + windows["window_midpoint"] = (windows["start"] + windows["end"]) / 2.0 + windows["is_hit"] = windows["window_id"].isin(hit_window_ids) + + scan_table = windows if include_all_windows else windows.loc[windows["is_hit"]].copy() + scan_table = scan_table.sort_values(["contig", "start", "end"], kind="stable").reset_index(drop=True) + hit_table = hits.sort_values(["rank", "contig", "start", "end"], kind="stable").reset_index(drop=True) + + return { + "scan_table": scan_table, + "hit_table": hit_table, + "metadata": { + "contig_order": contig_order, + "score_column": active_score_column, + "score_mode": result.metadata.get("score"), + "contrast_mode": result.metadata.get("contrast_mode"), + "merge_hits": result.metadata.get("merge_hits"), + "top_n_hits": top_n_hits, + }, + } +``` + +- [ ] **Step 3: Run the scan-prep tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "region_discovery_scan_data" -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit the scan-prep implementation** + +```bash +git add dimelo/plotting.py tests/test_plotting.py +git commit -m "feat: add region discovery scan plotting prep" +``` + +## Task 3: Add Failing Tests For Hit-Context Plot Prep + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/plotting.py` + +- [ ] **Step 1: Add the failing hit-context tests** + +Append these tests in `tests/test_plotting.py` near the new discovery scan tests: + +```python +def test_prepare_region_discovery_hit_context_data_uses_top_ranked_hits(): + result = _make_region_discovery_result() + + payload = plotting.prepare_region_discovery_hit_context_data( + result=result, + top_n=1, + padding_windows=1, + ) + + assert set(payload) == {"context_table", "selected_hits", "metadata"} + assert payload["selected_hits"]["rank"].tolist() == [1] + assert payload["context_table"]["selected_hit_rank"].nunique() == 1 + assert payload["context_table"]["selected_hit_rank"].iloc[0] == 1 + assert payload["context_table"]["is_selected_hit"].sum() == 1 + + +def test_prepare_region_discovery_hit_context_data_adds_relative_window_offsets(): + result = _make_region_discovery_result() + + payload = plotting.prepare_region_discovery_hit_context_data( + result=result, + top_n=1, + padding_windows=1, + ) + + assert sorted(payload["context_table"]["relative_window_offset"].tolist()) == [-1, 0] + + +def test_prepare_region_discovery_hit_context_data_returns_empty_payload_for_no_hits(): + result = RegionDiscoveryResult( + windows=_make_region_discovery_result().windows, + hits=pd.DataFrame(columns=_make_region_discovery_result().hits.columns), + plot_data={}, + metadata={"score": "effect_size_only", "contrast_mode": "pairwise", "merge_hits": False}, + ) + + payload = plotting.prepare_region_discovery_hit_context_data(result=result) + + assert payload["context_table"].empty + assert payload["selected_hits"].empty + assert payload["metadata"]["selection_mode"] == "top_n" +``` + +- [ ] **Step 2: Run the hit-context tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "region_discovery_hit_context_data" -q +``` + +Expected: FAIL because `prepare_region_discovery_hit_context_data(...)` does not yet exist. + +- [ ] **Step 3: Commit the failing hit-context tests** + +```bash +git add tests/test_plotting.py +git commit -m "test: add region discovery hit context coverage" +``` + +## Task 4: Implement Discovery Hit-Context Plot Prep + +**Files:** +- Modify: `dimelo/plotting.py` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Add the hit selection helper** + +Insert this helper in `dimelo/plotting.py` near the discovery scan helpers: + +```python +def _select_region_discovery_hits( + hits: pd.DataFrame, + *, + top_n: int | None, +): + if hits.empty: + return hits.copy(), "top_n" + if "rank" in hits.columns: + selected = hits.sort_values("rank", kind="stable") + else: + selected = hits.copy() + if top_n is not None: + selected = selected.head(top_n) + return selected.copy(), "top_n" +``` + +- [ ] **Step 2: Add `prepare_region_discovery_hit_context_data(...)`** + +Add this public function in `dimelo/plotting.py` near the scan helper: + +```python +def prepare_region_discovery_hit_context_data( + *, + result, + top_n: int | None = 12, + hit_ids: list[str] | None = None, + padding_windows: int | None = 5, + padding_bp: int | None = None, + score_column: str | None = None, +): + _validate_region_discovery_result(result) + + windows = result.windows.copy() + hits = result.hits.copy() + active_score_column = _select_discovery_score_column(windows, score_column) + + if hits.empty or windows.empty: + return { + "context_table": pd.DataFrame( + columns=[ + "window_id", + "contig", + "start", + "end", + "strand", + active_score_column, + "window_midpoint", + "selected_hit_id", + "selected_hit_rank", + "relative_window_offset", + "is_selected_hit", + ] + ), + "selected_hits": hits.copy(), + "metadata": { + "selection_mode": "top_n" if hit_ids is None else "explicit_ids", + "top_n": top_n, + "padding_windows": padding_windows, + "padding_bp": padding_bp, + "score_column": active_score_column, + }, + } + + windows = windows.sort_values(["chromosome", "start", "end"], kind="stable").reset_index(drop=True) + windows["contig"] = windows["chromosome"] + windows["window_midpoint"] = (windows["start"] + windows["end"]) / 2.0 + + if hit_ids is not None: + selected_hits = hits.loc[hits["window_id"].isin(hit_ids)].copy() + selection_mode = "explicit_ids" + else: + selected_hits, selection_mode = _select_region_discovery_hits(hits, top_n=top_n) + + if selected_hits.empty: + return { + "context_table": pd.DataFrame(columns=list(windows.columns) + ["selected_hit_id", "selected_hit_rank", "relative_window_offset", "is_selected_hit"]), + "selected_hits": selected_hits, + "metadata": { + "selection_mode": selection_mode, + "top_n": top_n, + "padding_windows": padding_windows, + "padding_bp": padding_bp, + "score_column": active_score_column, + }, + } + + context_frames = [] + for _, hit in selected_hits.sort_values("rank", kind="stable").iterrows(): + same_contig = windows.loc[windows["contig"] == hit["chromosome"]].reset_index(drop=True) + hit_positions = same_contig.index[same_contig["window_id"] == hit["window_id"]].tolist() + if not hit_positions: + continue + hit_position = hit_positions[0] + start_index = max(0, hit_position - int(padding_windows or 0)) + end_index = min(len(same_contig), hit_position + int(padding_windows or 0) + 1) + context = same_contig.iloc[start_index:end_index].copy() + context["selected_hit_id"] = hit["window_id"] + context["selected_hit_rank"] = hit.get("rank") + context["relative_window_offset"] = range(start_index - hit_position, end_index - hit_position) + context["is_selected_hit"] = context["window_id"] == hit["window_id"] + context_frames.append(context) + + context_table = ( + pd.concat(context_frames, ignore_index=True) + if context_frames + else pd.DataFrame(columns=list(windows.columns) + ["selected_hit_id", "selected_hit_rank", "relative_window_offset", "is_selected_hit"]) + ) + + return { + "context_table": context_table, + "selected_hits": selected_hits.sort_values("rank", kind="stable").reset_index(drop=True), + "metadata": { + "selection_mode": selection_mode, + "top_n": top_n, + "padding_windows": padding_windows, + "padding_bp": padding_bp, + "score_column": active_score_column, + }, + } +``` + +- [ ] **Step 3: Run the hit-context tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "region_discovery_hit_context_data" -q +``` + +Expected: PASS. + +- [ ] **Step 4: Run the combined discovery plotting subset** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "region_discovery_scan_data or region_discovery_hit_context_data" -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit the hit-context implementation** + +```bash +git add dimelo/plotting.py tests/test_plotting.py +git commit -m "feat: add region discovery hit context plotting prep" +``` + +## Task 5: Document The New Discovery Plotting Helpers + +**Files:** +- Modify: `docs/region-discovery.md` +- Modify: `README.md` +- Verify: `tests/test_plotting.py` + +- [ ] **Step 1: Add a short discovery plotting section to the guide** + +Append a short section to `docs/region-discovery.md` with an example like: + +```python +from dimelo import plotting + +scan_payload = plotting.prepare_region_discovery_scan_data( + result=result, + top_n_hits=50, +) + +context_payload = plotting.prepare_region_discovery_hit_context_data( + result=result, + top_n=12, + padding_windows=5, +) +``` + +Explain briefly: + +- scan payloads stay per-contig by default +- hit-context payloads are intended for small multiple panels +- both helpers are data-prep only and renderer-neutral + +- [ ] **Step 2: Add one short README note** + +Add one short paragraph to `README.md` under the plotting or analysis-guides area explaining: + +- `region_discovery` now has renderer-neutral scan and hit-context plotting helpers +- they consume `RegionDiscoveryResult` +- they prepare per-contig scan tables rather than cumulative genome axes by default + +- [ ] **Step 3: Run the touched plotting test file** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit the docs pass** + +```bash +git add docs/region-discovery.md README.md tests/test_plotting.py dimelo/plotting.py +git commit -m "docs: add region discovery plotting guide" +``` + +## Final Verification + +**Files:** +- Verify: `dimelo/plotting.py` +- Verify: `tests/test_plotting.py` +- Verify: `docs/region-discovery.md` +- Verify: `README.md` + +- [ ] **Step 1: Run the focused plotting verification** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q +``` + +Expected: PASS. + +- [ ] **Step 2: Confirm the working tree is clean** + +Run: + +```bash +git status --short +``` + +Expected: no output. + +- [ ] **Step 3: Summarize outcomes** + +Capture in the handoff: + +- which public helpers were added +- which tests were added +- that discovery plotting remains data-first and per-contig by default + +## Self-Review + +Spec coverage check: + +- scan-overview helper: covered in Tasks 1-2 +- hit-context helper: covered in Tasks 3-4 +- per-contig default and metadata: covered in Tasks 1-2 and Task 5 docs +- additive/no-renderer design: preserved throughout, with no `scan_genome()` changes + +Placeholder scan: + +- no `TODO`, `TBD`, or undefined implementation steps remain +- each task includes exact files, code snippets, commands, and expected outcomes + +Type consistency: + +- both helpers are named consistently with the approved spec +- `RegionDiscoveryResult` remains the public input +- payload keys stay aligned with the spec: `scan_table`, `hit_table`, `context_table`, `selected_hits`, `metadata` diff --git a/docs/superpowers/plans/2026-04-02-shared-clustering-plotting.md b/docs/superpowers/plans/2026-04-02-shared-clustering-plotting.md new file mode 100644 index 0000000..bca97ee --- /dev/null +++ b/docs/superpowers/plans/2026-04-02-shared-clustering-plotting.md @@ -0,0 +1,691 @@ +# Shared Clustering Plotting Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add renderer-neutral plotting-prep helpers for `dimelo.shared_clustering` distribution/change views, cluster-profile views, and region-occupancy views. + +**Architecture:** Extend the shared plotting module with three result-centric helpers that consume `SharedClusterResult` and emit stable payload dictionaries. Keep the implementation additive and data-first: preserve the existing lightweight `result.plot_data`, avoid clustering logic changes, and only aggregate from canonical `SharedClusterResult` tables already produced by `dimelo.workflows`. + +**Tech Stack:** Python 3.11, pandas, pytest, existing `dimelo.plotting`, `dimelo.workflows`, `dimelo.distribution`, and `dimelo.models` modules + +--- + +## File Map + +- Modify: `dimelo/plotting.py` + - add `SharedClusterResult` validation/helpers + - add `prepare_shared_cluster_distribution_data(...)` + - add `prepare_shared_cluster_profile_data(...)` + - add `prepare_shared_cluster_region_data(...)` +- Modify: `tests/test_plotting.py` + - add focused plotting helper coverage using synthetic `SharedClusterResult` fixtures +- Modify: `tests/test_workflows.py` + - verify shared-clustering workflow outputs remain compatible with the new helper layer +- Modify: `docs/shared-clustering.md` + - document the new plotting-prep helpers and how they relate to existing `result.plot_data` +- Modify: `README.md` + - add one short note about shared-clustering plotting-prep helpers + +## Task 1: Add Failing Tests For Distribution And Change Plot Prep + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/models.py` +- Reference: `dimelo/plotting.py` + +- [ ] **Step 1: Write the failing distribution/change tests** + +Add these tests near the other plotting helper tests in `tests/test_plotting.py`: + +```python +from dimelo.models import SharedClusterModel, SharedClusterResult + + +def _make_shared_cluster_result() -> SharedClusterResult: + model = SharedClusterModel( + mode="region_anchored", + motifs=["A,0"], + feature_names=["f0", "f1"], + preprocessing={"signal_normalization": "none"}, + estimator=object(), + cluster_labels=["C0", "C1"], + fit_metadata={"clusterer": "minibatch_kmeans", "n_clusters": 2}, + ) + assignments = pd.DataFrame( + [ + {"sample_id": "s1", "condition": "NS", "cluster": "C0"}, + {"sample_id": "s1", "condition": "NS", "cluster": "C1"}, + {"sample_id": "s2", "condition": "treated", "cluster": "C1"}, + ] + ) + cluster_distribution = pd.DataFrame( + [ + {"sample_id": "s1", "condition": "NS", "cluster": "C0", "count": 2, "fraction": 2 / 3}, + {"sample_id": "s1", "condition": "NS", "cluster": "C1", "count": 1, "fraction": 1 / 3}, + {"sample_id": "s2", "condition": "treated", "cluster": "C0", "count": 1, "fraction": 1 / 4}, + {"sample_id": "s2", "condition": "treated", "cluster": "C1", "count": 3, "fraction": 3 / 4}, + ] + ) + condition_distribution = pd.DataFrame( + [ + {"condition": "NS", "cluster": "C0", "count": 2, "fraction": 2 / 3, "replicate_n": 1}, + {"condition": "NS", "cluster": "C1", "count": 1, "fraction": 1 / 3, "replicate_n": 1}, + {"condition": "treated", "cluster": "C0", "count": 1, "fraction": 1 / 4, "replicate_n": 1}, + {"condition": "treated", "cluster": "C1", "count": 3, "fraction": 3 / 4, "replicate_n": 1}, + ] + ) + distribution_change = pd.DataFrame( + [ + { + "condition": "treated", + "cluster": "C0", + "count": 1, + "fraction": 1 / 4, + "replicate_n": 1, + "reference_fraction": 2 / 3, + "delta_fraction": -5 / 12, + "log2_fc": -1.415037499278844, + }, + { + "condition": "treated", + "cluster": "C1", + "count": 3, + "fraction": 3 / 4, + "replicate_n": 1, + "reference_fraction": 1 / 3, + "delta_fraction": 5 / 12, + "log2_fc": 1.1699250014423124, + }, + ] + ) + cluster_profiles = pd.DataFrame( + [ + {"cluster": "C0", "count": 3, "f0": 0.1, "f1": 0.2}, + {"cluster": "C1", "count": 4, "f0": 0.8, "f1": 0.9}, + ] + ) + region_summaries = pd.DataFrame( + [ + {"region_id": "reg1", "sample_id": "s1", "condition": "NS", "cluster": "C0", "count": 2, "fraction": 2 / 3}, + {"region_id": "reg1", "sample_id": "s1", "condition": "NS", "cluster": "C1", "count": 1, "fraction": 1 / 3}, + {"region_id": "reg1", "sample_id": "s2", "condition": "treated", "cluster": "C0", "count": 1, "fraction": 1 / 4}, + {"region_id": "reg1", "sample_id": "s2", "condition": "treated", "cluster": "C1", "count": 3, "fraction": 3 / 4}, + ] + ) + return SharedClusterResult( + model=model, + assignments=assignments, + cluster_distribution=cluster_distribution, + condition_distribution=condition_distribution, + distribution_change=distribution_change, + cluster_profiles=cluster_profiles, + region_summaries=region_summaries, + plot_data={ + "cluster_distribution_bar": cluster_distribution.copy(), + "cluster_distribution_heatmap": pd.DataFrame( + [{"condition": "NS", "C0": 2 / 3, "C1": 1 / 3}, {"condition": "treated", "C0": 1 / 4, "C1": 3 / 4}] + ), + }, + metadata={"mode": "region_anchored"}, + ) + + +def test_prepare_shared_cluster_distribution_data_returns_distribution_payload(): + result = _make_shared_cluster_result() + + payload = plotting.prepare_shared_cluster_distribution_data(result=result) + + assert set(payload) == { + "sample_distribution", + "condition_distribution", + "distribution_change", + "metadata", + } + assert payload["sample_distribution"]["sample_id"].tolist() == ["s1", "s1", "s2", "s2"] + assert payload["condition_distribution"]["condition"].tolist() == ["NS", "NS", "treated", "treated"] + assert payload["distribution_change"]["condition"].tolist() == ["treated", "treated"] + assert payload["metadata"]["mode"] == "region_anchored" + + +def test_prepare_shared_cluster_distribution_data_handles_missing_change_table(): + result = _make_shared_cluster_result() + result.distribution_change = None + + payload = plotting.prepare_shared_cluster_distribution_data(result=result) + + assert list(payload["distribution_change"].columns) == [ + "condition", + "cluster", + "count", + "fraction", + "replicate_n", + "reference_fraction", + "delta_fraction", + "log2_fc", + ] + assert payload["distribution_change"].empty +``` + +- [ ] **Step 2: Run the new distribution/change tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "shared_cluster_distribution_data" -q +``` + +Expected: FAIL because `prepare_shared_cluster_distribution_data(...)` does not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_plotting.py +git commit -m "test: add shared clustering distribution plotting coverage" +``` + +## Task 2: Implement Distribution And Change Plot Prep + +**Files:** +- Modify: `dimelo/plotting.py` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Add the internal shared-clustering validation helpers** + +Add these helpers in `dimelo/plotting.py` near the other result-validation utilities: + +```python +def _validate_shared_cluster_result(result) -> None: + if result is None: + raise ValueError("plotting helpers require a SharedClusterResult.") + required_attrs = ( + "cluster_distribution", + "condition_distribution", + "cluster_profiles", + "plot_data", + "metadata", + ) + if not all(hasattr(result, attr) for attr in required_attrs): + raise TypeError("plotting helpers require a SharedClusterResult-like object.") + + +def _empty_distribution_change_table() -> pd.DataFrame: + return pd.DataFrame( + columns=[ + "condition", + "cluster", + "count", + "fraction", + "replicate_n", + "reference_fraction", + "delta_fraction", + "log2_fc", + ] + ) +``` + +- [ ] **Step 2: Add `prepare_shared_cluster_distribution_data(...)`** + +Add this public function in `dimelo/plotting.py` near the other public plotting helpers: + +```python +def prepare_shared_cluster_distribution_data( + *, + result, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_shared_cluster_result(result) + + sample_distribution = prepare_cluster_distribution_bar_data(result.cluster_distribution) + condition_distribution = ( + result.condition_distribution.loc[ + :, ["condition", "cluster", "count", "fraction", "replicate_n"] + ] + .sort_values(["condition", "cluster"], kind="stable") + .reset_index(drop=True) + ) + distribution_change = ( + _empty_distribution_change_table() + if result.distribution_change is None + else result.distribution_change.sort_values(["condition", "cluster"], kind="stable").reset_index(drop=True) + ) + metadata = { + "mode": result.model.mode, + "cluster_labels": list(result.model.cluster_labels), + "has_distribution_change": not distribution_change.empty, + } + return { + "sample_distribution": sample_distribution, + "condition_distribution": condition_distribution, + "distribution_change": distribution_change, + "metadata": metadata, + } +``` + +- [ ] **Step 3: Run the distribution/change tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "shared_cluster_distribution_data" -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit the implementation** + +```bash +git add dimelo/plotting.py tests/test_plotting.py +git commit -m "feat: add shared clustering distribution plotting prep" +``` + +## Task 3: Add Failing Tests For Cluster Profile Plot Prep + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/plotting.py` + +- [ ] **Step 1: Write the failing cluster-profile tests** + +Add these tests in `tests/test_plotting.py` below the distribution helper coverage: + +```python +def test_prepare_shared_cluster_profile_data_returns_long_form_profiles(): + result = _make_shared_cluster_result() + + payload = plotting.prepare_shared_cluster_profile_data(result=result) + + assert set(payload) == {"profile_table", "metadata"} + assert set(payload["profile_table"].columns) == {"cluster", "feature", "value", "count"} + assert payload["profile_table"]["cluster"].tolist() == ["C0", "C0", "C1", "C1"] + assert payload["profile_table"]["feature"].tolist() == ["f0", "f1", "f0", "f1"] + assert payload["metadata"]["feature_names"] == ["f0", "f1"] + + +def test_prepare_shared_cluster_profile_data_respects_feature_subset(): + result = _make_shared_cluster_result() + + payload = plotting.prepare_shared_cluster_profile_data(result=result, features=["f1"]) + + assert payload["profile_table"]["feature"].unique().tolist() == ["f1"] + assert payload["profile_table"]["cluster"].tolist() == ["C0", "C1"] +``` + +- [ ] **Step 2: Run the new cluster-profile tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "shared_cluster_profile_data" -q +``` + +Expected: FAIL because `prepare_shared_cluster_profile_data(...)` does not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_plotting.py +git commit -m "test: add shared clustering profile plotting coverage" +``` + +## Task 4: Implement Cluster Profile Plot Prep + +**Files:** +- Modify: `dimelo/plotting.py` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Add the cluster-profile helper** + +Add this public function in `dimelo/plotting.py`: + +```python +def prepare_shared_cluster_profile_data( + *, + result, + features: list[str] | None = None, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_shared_cluster_result(result) + cluster_profiles = result.cluster_profiles.copy() + if cluster_profiles.empty: + return { + "profile_table": pd.DataFrame(columns=["cluster", "feature", "value", "count"]), + "metadata": {"feature_names": [], "cluster_labels": list(result.model.cluster_labels)}, + } + + feature_names = [column for column in cluster_profiles.columns if column not in {"cluster", "count"}] + if features is not None: + missing = [feature for feature in features if feature not in feature_names] + if missing: + raise ValueError(f"Requested features are not present in cluster_profiles: {', '.join(missing)}") + feature_names = [feature for feature in feature_names if feature in features] + + profile_table = ( + cluster_profiles.loc[:, ["cluster", "count", *feature_names]] + .melt(id_vars=["cluster", "count"], var_name="feature", value_name="value") + .sort_values(["cluster", "feature"], kind="stable") + .reset_index(drop=True) + ) + return { + "profile_table": profile_table, + "metadata": { + "feature_names": feature_names, + "cluster_labels": list(result.model.cluster_labels), + }, + } +``` + +- [ ] **Step 2: Run the cluster-profile tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "shared_cluster_profile_data" -q +``` + +Expected: PASS. + +- [ ] **Step 3: Commit the implementation** + +```bash +git add dimelo/plotting.py tests/test_plotting.py +git commit -m "feat: add shared clustering profile plotting prep" +``` + +## Task 5: Add Failing Tests For Region Occupancy Plot Prep + +**Files:** +- Modify: `tests/test_plotting.py` +- Modify: `tests/test_workflows.py` +- Reference: `dimelo/workflows.py` + +- [ ] **Step 1: Write the failing region-occupancy tests** + +Add these tests in `tests/test_plotting.py`: + +```python +def test_prepare_shared_cluster_region_data_returns_sample_and_condition_tables(): + result = _make_shared_cluster_result() + + payload = plotting.prepare_shared_cluster_region_data(result=result) + + assert set(payload) == {"region_table", "condition_region_table", "metadata"} + assert payload["region_table"]["sample_id"].tolist() == ["s1", "s1", "s2", "s2"] + assert payload["condition_region_table"]["condition"].tolist() == ["NS", "NS", "treated", "treated"] + assert payload["metadata"]["mode"] == "region_anchored" + + +def test_prepare_shared_cluster_region_data_can_disable_condition_aggregation(): + result = _make_shared_cluster_result() + + payload = plotting.prepare_shared_cluster_region_data( + result=result, + aggregate_conditions=False, + ) + + assert payload["condition_region_table"].empty + + +def test_prepare_shared_cluster_region_data_rejects_missing_region_summaries(): + result = _make_shared_cluster_result() + result.region_summaries = None + + with pytest.raises(ValueError, match="region_summaries"): + plotting.prepare_shared_cluster_region_data(result=result) +``` + +Add this workflow compatibility test in `tests/test_workflows.py` near the existing shared-clustering workflow tests: + +```python +def test_shared_cluster_distribution_region_anchored_region_summaries_feed_region_plotting(monkeypatch): + fake_samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + regions_bed="r1.bed", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="treated", + extract_h5="s2.h5", + regions_bed="r2.bed", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + + def fake_region_table(*args, **kwargs): + return np.array([[0.1, 0.9], [0.9, 0.1]]), [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + ] + + monkeypatch.setattr(workflows.region_analysis, "build_region_feature_table", fake_region_table) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="region_anchored", + motifs=["A,0"], + matched_regions="matched.bed", + n_clusters=2, + make_plots=False, + ) + + payload = plotting.prepare_shared_cluster_region_data(result=result) + + assert not payload["region_table"].empty + assert list(payload["condition_region_table"]["condition"].unique()) == ["NS", "treated"] +``` + +- [ ] **Step 2: Run the new region-occupancy tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py tests/test_workflows.py -k "shared_cluster_region_data" -q +``` + +Expected: FAIL because `prepare_shared_cluster_region_data(...)` does not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_plotting.py tests/test_workflows.py +git commit -m "test: add shared clustering region plotting coverage" +``` + +## Task 6: Implement Region Occupancy Plot Prep And Workflow Alignment + +**Files:** +- Modify: `dimelo/plotting.py` +- Modify: `tests/test_plotting.py` +- Modify: `tests/test_workflows.py` + +- [ ] **Step 1: Add the region-occupancy helper** + +Add this public function in `dimelo/plotting.py`: + +```python +def prepare_shared_cluster_region_data( + *, + result, + aggregate_conditions: bool = True, +) -> dict[str, pd.DataFrame | dict[str, object]]: + _validate_shared_cluster_result(result) + if result.region_summaries is None: + raise ValueError("SharedClusterResult.region_summaries is required for region plotting.") + + region_table = result.region_summaries.sort_values( + ["region_id", "sample_id", "cluster"], + kind="stable", + ).reset_index(drop=True) + + if aggregate_conditions and not region_table.empty: + condition_region_table = ( + region_table.groupby(["region_id", "condition", "cluster"], as_index=False, sort=False) + .agg( + count=("count", "sum"), + fraction_mean=("fraction", "mean"), + fraction_median=("fraction", "median"), + sample_n=("sample_id", "nunique"), + ) + .sort_values(["region_id", "condition", "cluster"], kind="stable") + .reset_index(drop=True) + ) + else: + condition_region_table = pd.DataFrame( + columns=[ + "region_id", + "condition", + "cluster", + "count", + "fraction_mean", + "fraction_median", + "sample_n", + ] + ) + + return { + "region_table": region_table, + "condition_region_table": condition_region_table, + "metadata": { + "mode": result.model.mode, + "cluster_labels": list(result.model.cluster_labels), + "has_condition_aggregation": aggregate_conditions, + }, + } +``` + +- [ ] **Step 2: Keep workflow compatibility explicit in tests** + +Do not change `dimelo/workflows.py` unless a test reveals a real mismatch. If the new helper works directly on existing `SharedClusterResult.region_summaries`, keep the workflow untouched and preserve the additive boundary. + +- [ ] **Step 3: Run the region-occupancy tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py tests/test_workflows.py -k "shared_cluster_region_data or region_summaries_feed_region_plotting" -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit the implementation** + +```bash +git add dimelo/plotting.py tests/test_plotting.py tests/test_workflows.py +git commit -m "feat: add shared clustering region plotting prep" +``` + +## Task 7: Document The New Shared Clustering Plotting Helpers + +**Files:** +- Modify: `docs/shared-clustering.md` +- Modify: `README.md` + +- [ ] **Step 1: Update the shared clustering guide** + +Add a short “Plotting Prep Helpers” section to `docs/shared-clustering.md` that documents the three new helpers and keeps the continuity note about existing lightweight `result.plot_data`: + +```md +## Plotting Prep Helpers + +`SharedClusterResult` now has three renderer-neutral plotting helpers in `dimelo.plotting`: + +- `prepare_shared_cluster_distribution_data(result=...)` +- `prepare_shared_cluster_profile_data(result=...)` +- `prepare_shared_cluster_region_data(result=...)` + +These helpers sit on top of the canonical result tables: + +- `result.cluster_distribution` +- `result.condition_distribution` +- `result.distribution_change` +- `result.cluster_profiles` +- `result.region_summaries` + +The older lightweight payloads in `result.plot_data["cluster_distribution_bar"]` and +`result.plot_data["cluster_distribution_heatmap"]` remain supported for backward familiarity. +``` + +- [ ] **Step 2: Add a short README note** + +Add one short paragraph in `README.md` near the other workflow/plotting summaries: + +```md +Shared clustering also has renderer-neutral plotting prep in `dimelo.plotting`. Use +`prepare_shared_cluster_distribution_data(...)` for sample/condition cluster fractions, +`prepare_shared_cluster_profile_data(...)` for cluster feature summaries, and +`prepare_shared_cluster_region_data(...)` for region-level occupancy tables. +``` + +- [ ] **Step 3: Commit the docs** + +```bash +git add docs/shared-clustering.md README.md +git commit -m "docs: add shared clustering plotting guide" +``` + +## Task 8: Run Final Verification + +**Files:** +- Reference: `dimelo/plotting.py` +- Reference: `tests/test_plotting.py` +- Reference: `tests/test_workflows.py` +- Reference: `docs/shared-clustering.md` +- Reference: `README.md` + +- [ ] **Step 1: Run the focused plotting and workflow tests** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl XDG_CACHE_HOME=/tmp/xdg-cache PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py tests/test_workflows.py -q +``` + +Expected: PASS with no new failures. Existing non-blocking warnings are acceptable if they are unchanged. + +- [ ] **Step 2: Run a quick git status check** + +Run: + +```bash +git status --short +``` + +Expected: clean working tree. + +- [ ] **Step 3: Self-review against the spec** + +Check the completed work against `docs/superpowers/specs/2026-04-02-shared-clustering-plotting-design.md`: + +- distribution/change helper present and additive +- cluster-profile helper present and data-first +- region-occupancy helper present with optional condition aggregation +- existing `result.plot_data` preserved +- docs updated without claiming renderer support + +- [ ] **Step 4: Commit any final doc/test touch-ups if needed** + +```bash +git add dimelo/plotting.py tests/test_plotting.py tests/test_workflows.py docs/shared-clustering.md README.md +git commit -m "fix: tighten shared clustering plotting prep" +``` + +Only make this commit if the self-review or verification uncovered a small follow-up patch. diff --git a/docs/superpowers/plans/2026-04-02-superpowers-docs-cleanup.md b/docs/superpowers/plans/2026-04-02-superpowers-docs-cleanup.md new file mode 100644 index 0000000..bca3385 --- /dev/null +++ b/docs/superpowers/plans/2026-04-02-superpowers-docs-cleanup.md @@ -0,0 +1,236 @@ +# Superpowers Docs Cleanup Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Track the existing internal plan docs in git and add a central `docs/superpowers` index that maps specs and plans to their current implementation status. + +**Architecture:** Keep the existing `docs/superpowers/specs` and `docs/superpowers/plans` directories unchanged, then add one central [README.md](../README.md) that explains the layout, groups the existing work by theme, and labels status centrally. This is a docs-only cleanup slice; it should not rewrite historical spec/plan content or touch runtime code. + +**Tech Stack:** Markdown docs, git, existing `docs/superpowers/specs` and `docs/superpowers/plans` content + +--- + +## File Structure + +- Create: `docs/superpowers/README.md` + - Central index for specs, plans, themes, and status labels. +- Stage existing untracked files under `docs/superpowers/plans/` + - No content edits required unless a broken filename/path is discovered. +- Verify: `docs/superpowers/specs/` +- Verify: `docs/superpowers/plans/` + +### Task 1: Create the central `docs/superpowers` index + +**Files:** +- Create: `docs/superpowers/README.md` +- Reference: `docs/superpowers/specs/2026-04-02-superpowers-docs-cleanup-design.md` +- Reference: `docs/superpowers/specs/` +- Reference: `docs/superpowers/plans/` + +- [ ] **Step 1: Write the new index file** + +Create `docs/superpowers/README.md` with this exact starter structure: + +```md +# Superpowers Docs Index + +This directory tracks internal design and implementation work for the current branch. + +- `specs/` contains design documents. +- `plans/` contains executable implementation breakdowns. +- Status is tracked here centrally so historical docs can remain stable snapshots. + +## Status Labels + +- `implemented`: the planned slice has landed in the repo. +- `partially implemented`: some related work has landed, but the broader feature family still has follow-on work. +- `planned`: the design exists, but the slice is not yet implemented. +- `not started`: a plan exists, but execution has not started. + +## Specs + +## Plans + +## Current Themes +``` + +- [ ] **Step 2: Fill the `Specs` section with grouped entries** + +Populate `## Specs` with grouped bullets for: + +- shared clustering +- region analysis +- plotting +- cleanup/docs + +Use this style: + +```md +### Shared Clustering + +- [2026-03-31-shared-cluster-distribution-design.md](../specs/2026-03-31-shared-cluster-distribution-design.md) - shared-boundary clustering architecture and artifact model. Status: `partially implemented` +``` + +Include at least these specs: + +- `2026-03-31-shared-cluster-distribution-design.md` +- `2026-03-31-region-analysis-architecture-design.md` +- `2026-03-31-pre-plan-decisions-addendum.md` +- `2026-04-01-paired-region-discovery-design.md` +- `2026-04-01-plotting-axis-architecture-design.md` +- `2026-04-01-region-contrasts-plotting-design.md` +- `2026-04-02-superpowers-docs-cleanup-design.md` + +- [ ] **Step 3: Fill the `Plans` section with grouped entries** + +Populate `## Plans` with grouped bullets for all current plan files in `docs/superpowers/plans/`, using this style: + +```md +### Plotting + +- [2026-04-01-plotting-axis-architecture.md](2026-04-01-plotting-axis-architecture.md) - shared plotting-axis implementation slice. Status: `implemented` +``` + +List all current plan files: + +- `2026-03-31-global-analysis-foundations.md` +- `2026-03-31-region-contrasts-foundations.md` +- `2026-03-31-region-discovery-foundations.md` +- `2026-03-31-shared-clustering-foundations.md` +- `2026-04-01-cluster-occupancy-region-contrasts.md` +- `2026-04-01-discovery-cluster-contrast-workflow.md` +- `2026-04-01-paired-region-discovery.md` +- `2026-04-01-plotting-axis-architecture.md` +- `2026-04-01-region-contrasts-plotting.md` +- `2026-04-01-region-discovery-cluster-workflow.md` +- `2026-04-02-superpowers-docs-cleanup.md` + +- [ ] **Step 4: Fill the `Current Themes` section** + +Add a short theme map that points readers to the most relevant spec/plan chains, for example: + +```md +## Current Themes + +- Shared clustering: start with the shared clustering design spec, then the foundations plan. +- Region contrasts: start with the region analysis architecture spec, then the contrasts foundations plan and the region-contrasts plotting plan. +- Region discovery: use the region analysis architecture spec, the paired discovery spec, and the discovery workflow plans together. +- Plotting: start with the plotting-axis spec, then the plotting-axis implementation plan and the region-contrasts plotting plan. +``` + +- [ ] **Step 5: Commit the new index** + +```bash +git add docs/superpowers/README.md +git commit -m "docs: add superpowers docs index" +``` + +### Task 2: Stage and track the existing untracked plan files + +**Files:** +- Stage: `docs/superpowers/plans/2026-03-31-global-analysis-foundations.md` +- Stage: `docs/superpowers/plans/2026-03-31-region-contrasts-foundations.md` +- Stage: `docs/superpowers/plans/2026-03-31-region-discovery-foundations.md` +- Stage: `docs/superpowers/plans/2026-04-01-cluster-occupancy-region-contrasts.md` +- Stage: `docs/superpowers/plans/2026-04-01-discovery-cluster-contrast-workflow.md` +- Stage: `docs/superpowers/plans/2026-04-01-paired-region-discovery.md` +- Stage: `docs/superpowers/plans/2026-04-01-plotting-axis-architecture.md` +- Stage: `docs/superpowers/plans/2026-04-01-region-contrasts-plotting.md` +- Stage: `docs/superpowers/plans/2026-04-01-region-discovery-cluster-workflow.md` + +- [ ] **Step 1: Confirm the currently untracked plan set** + +Run: + +```bash +git status --short docs/superpowers/plans +``` + +Expected: the plan files above appear as untracked (`??`). + +- [ ] **Step 2: Stage the plan files without editing them** + +Run: + +```bash +git add \ + docs/superpowers/plans/2026-03-31-global-analysis-foundations.md \ + docs/superpowers/plans/2026-03-31-region-contrasts-foundations.md \ + docs/superpowers/plans/2026-03-31-region-discovery-foundations.md \ + docs/superpowers/plans/2026-04-01-cluster-occupancy-region-contrasts.md \ + docs/superpowers/plans/2026-04-01-discovery-cluster-contrast-workflow.md \ + docs/superpowers/plans/2026-04-01-paired-region-discovery.md \ + docs/superpowers/plans/2026-04-01-plotting-axis-architecture.md \ + docs/superpowers/plans/2026-04-01-region-contrasts-plotting.md \ + docs/superpowers/plans/2026-04-01-region-discovery-cluster-workflow.md \ + docs/superpowers/plans/2026-04-02-superpowers-docs-cleanup.md +``` + +- [ ] **Step 3: Commit the tracked plan artifacts** + +```bash +git add docs/superpowers/plans +git commit -m "docs: track superpowers implementation plans" +``` + +### Task 3: Verify the index and status map against the repo + +**Files:** +- Verify: `docs/superpowers/README.md` +- Verify: `docs/superpowers/specs/` +- Verify: `docs/superpowers/plans/` + +- [ ] **Step 1: Check that every linked file in the index exists** + +Run: + +```bash +rg -o 'docs/superpowers/[^)]+' docs/superpowers/README.md +``` + +Then verify those paths exist with: + +```bash +test -f docs/superpowers/README.md +``` + +Expected: every file referenced by the index exists on disk. + +- [ ] **Step 2: Check that the plans are no longer untracked** + +Run: + +```bash +git status --short docs/superpowers +``` + +Expected: no `??` entries remain for the tracked plan docs. + +- [ ] **Step 3: Review status labels for obvious misstatements** + +Manually confirm the index does not mark unfinished families as fully complete. In particular: + +- plotting should reflect that the axis core and region-contrast plotting slices are implemented +- region discovery should remain `partially implemented` if follow-on contrast modes or plotting slices still remain +- global analysis should remain `partially implemented` unless the family is truly complete + +- [ ] **Step 4: Commit any index corrections from verification** + +```bash +git add docs/superpowers/README.md +git commit -m "docs: verify superpowers docs status index" +``` + +If no corrections were needed, skip this commit. + +## Self-Review + +- Spec coverage: + - central index: Task 1 + - tracking untracked plans: Task 2 + - central status map and verification: Task 3 +- Placeholder scan: + - no `TODO`, `TBD`, or deferred implementation text remains in the task steps +- Type consistency: + - all referenced paths use the existing `docs/superpowers/specs` and `docs/superpowers/plans` layout + diff --git a/docs/superpowers/plans/2026-04-03-single-read-region-contrasts.md b/docs/superpowers/plans/2026-04-03-single-read-region-contrasts.md new file mode 100644 index 0000000..53fee66 --- /dev/null +++ b/docs/superpowers/plans/2026-04-03-single-read-region-contrasts.md @@ -0,0 +1,860 @@ +# Single-Read Region Contrasts Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Extend `dimelo.region_contrasts` with a first `analysis_unit="single_read"` family covering `read_mod_fraction` and `read_window_features` for defined-region contrasts. + +**Architecture:** Keep all new behavior inside `region_contrasts.score_regions(...)` by adding explicit request validation, read-level evidence builders, and sample-aware scoring paths. Build `read_mod_fraction` first, then add `read_window_features` using the current built-in feature set by default and a validated user-supplied `feature_table` override, while keeping the inferential boundary at the sample level. + +**Tech Stack:** Python 3.11, pandas, scipy, pytest, existing `dimelo.region_contrasts`, `dimelo.region_analysis`, `dimelo.cluster`, `dimelo.models`, and docs modules + +--- + +## File Map + +- Modify: `dimelo/region_contrasts.py` + - extend request validation for `analysis_unit="single_read"` + - add read-level evidence builders + - add sample-aware scoring paths for `read_mod_fraction` + - add feature-table validation and scoring for `read_window_features` +- Modify: `tests/test_region_contrasts.py` + - add validation coverage + - add synthetic evidence/scoring coverage for both single-read representations + - add matched-pairwise coverage +- Modify: `docs/region-contrasts.md` + - document `single_read` semantics, supported modes, and v1 constraints +- Modify: `README.md` + - add one short note describing the new `single_read` contrast family + +## Task 1: Add Failing Validation Tests For `analysis_unit=\"single_read\"` + +**Files:** +- Modify: `tests/test_region_contrasts.py` +- Reference: `dimelo/region_contrasts.py` + +- [ ] **Step 1: Write the failing validation tests** + +Add these tests near the existing validation coverage in `tests/test_region_contrasts.py`: + +```python +def test_validate_region_contrast_request_accepts_single_read_mod_fraction(): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="extract_reads", + test="sample_distribution_shift", + ) + + +def test_validate_region_contrast_request_accepts_single_read_window_features(): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_window_features", + signal_source="extract_features", + test="feature_summary_shift", + ) + + +def test_validate_region_contrast_request_rejects_single_read_wrong_signal_source(): + with pytest.raises(ValueError, match="extract_reads"): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="pileup_counts", + test="sample_distribution_shift", + ) + + +def test_validate_region_contrast_request_rejects_single_read_unknown_representation(): + with pytest.raises(ValueError, match="read_mod_fraction"): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_shape", + signal_source="extract_reads", + test="sample_distribution_shift", + ) + + +def test_validate_region_contrast_request_rejects_single_read_unknown_test(): + with pytest.raises(ValueError, match="sample_distribution_shift"): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="extract_reads", + test="beta_binomial", + ) +``` + +- [ ] **Step 2: Run the validation tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -k "single_read and validate_region_contrast_request" -q +``` + +Expected: FAIL because `single_read` is not yet accepted by `validate_region_contrast_request(...)`. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_region_contrasts.py +git commit -m "test: add single-read contrast validation coverage" +``` + +## Task 2: Implement `single_read` Request Validation + +**Files:** +- Modify: `dimelo/region_contrasts.py` +- Modify: `tests/test_region_contrasts.py` + +- [ ] **Step 1: Extend `validate_region_contrast_request(...)`** + +Add a `single_read` branch in `dimelo/region_contrasts.py`: + +```python + if analysis_unit == "single_read": + if representation == "read_mod_fraction": + if signal_source != "extract_reads": + raise ValueError( + "Current single_read read_mod_fraction support requires " + "signal_source='extract_reads'." + ) + if test not in {"effect_size_only", "sample_distribution_shift"}: + raise ValueError( + "Current single_read read_mod_fraction scoring support requires " + "test in {'effect_size_only', 'sample_distribution_shift'}." + ) + return + + if representation == "read_window_features": + if signal_source != "extract_features": + raise ValueError( + "Current single_read read_window_features support requires " + "signal_source='extract_features'." + ) + if test not in {"effect_size_only", "feature_summary_shift"}: + raise ValueError( + "Current single_read read_window_features support requires " + "test in {'effect_size_only', 'feature_summary_shift'}." + ) + return + + raise ValueError( + "Current single_read support requires representation='read_mod_fraction' " + "or 'read_window_features'." + ) +``` + +Update the final error message so unsupported units mention `single_read`: + +```python + if analysis_unit not in {"ensemble_region", "cluster_occupancy", "single_read"}: + raise ValueError( + "V1 region_contrasts inference requires analysis_unit='ensemble_region', " + "'cluster_occupancy', or 'single_read'." + ) +``` + +- [ ] **Step 2: Run the validation tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -k "single_read and validate_region_contrast_request" -q +``` + +Expected: PASS. + +- [ ] **Step 3: Commit the validation implementation** + +```bash +git add dimelo/region_contrasts.py tests/test_region_contrasts.py +git commit -m "feat: add single-read contrast request validation" +``` + +## Task 3: Add Failing Tests For `read_mod_fraction` Evidence Building + +**Files:** +- Modify: `tests/test_region_contrasts.py` +- Reference: `dimelo/region_contrasts.py` + +- [ ] **Step 1: Write the failing `read_mod_fraction` evidence tests** + +Add a small synthetic helper and tests in `tests/test_region_contrasts.py`: + +```python +def _single_read_samples(): + return [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + replicate=1, + metadata={"extract_path": "s1.tsv"}, + ), + SampleSpec( + sample_id="s2", + condition="treated", + extract_h5="s2.h5", + replicate=1, + metadata={"extract_path": "s2.tsv"}, + ), + ] + + +def test_build_single_read_mod_fraction_evidence_table(): + samples = _single_read_samples() + extract_table = pd.DataFrame( + [ + {"region_id": "reg1", "sample_id": "s1", "condition": "NS", "read_id": "r1", "modified_count": 2, "valid_count": 4}, + {"region_id": "reg1", "sample_id": "s1", "condition": "NS", "read_id": "r2", "modified_count": 1, "valid_count": 4}, + {"region_id": "reg1", "sample_id": "s2", "condition": "treated", "read_id": "r3", "modified_count": 4, "valid_count": 4}, + ] + ) + + result = region_contrasts.build_single_read_mod_fraction_evidence_table( + extract_table=extract_table + ) + + assert result.to_dict("records") == [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r1", + "modified_count": 2, + "valid_count": 4, + "read_mod_fraction": pytest.approx(0.5), + }, + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r2", + "modified_count": 1, + "valid_count": 4, + "read_mod_fraction": pytest.approx(0.25), + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "read_id": "r3", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": pytest.approx(1.0), + }, + ] + + +def test_build_single_read_mod_fraction_evidence_table_rejects_missing_columns(): + with pytest.raises(ValueError, match="modified_count"): + region_contrasts.build_single_read_mod_fraction_evidence_table( + extract_table=pd.DataFrame([{"region_id": "reg1", "sample_id": "s1"}]) + ) +``` + +- [ ] **Step 2: Run the evidence tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -k "single_read_mod_fraction_evidence" -q +``` + +Expected: FAIL because the evidence builder does not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_region_contrasts.py +git commit -m "test: add single-read mod-fraction evidence coverage" +``` + +## Task 4: Implement `read_mod_fraction` Evidence And Sample-Aware Scoring + +**Files:** +- Modify: `dimelo/region_contrasts.py` +- Modify: `tests/test_region_contrasts.py` + +- [ ] **Step 1: Add the evidence builder** + +Add this helper in `dimelo/region_contrasts.py` near the other evidence builders: + +```python +def build_single_read_mod_fraction_evidence_table( + *, + extract_table: pd.DataFrame, +) -> pd.DataFrame: + required_columns = { + "region_id", + "sample_id", + "condition", + "read_id", + "modified_count", + "valid_count", + } + missing_columns = required_columns - set(extract_table.columns) + if missing_columns: + missing_display = ", ".join(sorted(missing_columns)) + raise ValueError( + "build_single_read_mod_fraction_evidence_table requires columns: " + f"{missing_display}." + ) + + evidence = extract_table.loc[ + :, + [ + "region_id", + "sample_id", + "condition", + "read_id", + "modified_count", + "valid_count", + ], + ].copy() + evidence["read_mod_fraction"] = evidence["modified_count"].div( + evidence["valid_count"].where(evidence["valid_count"] != 0), + fill_value=0, + ).fillna(0.0) + return evidence +``` + +- [ ] **Step 2: Add failing scoring tests for `read_mod_fraction`** + +Add these tests in `tests/test_region_contrasts.py`: + +```python +def test_score_regions_single_read_mod_fraction_effect_size_only(): + evidence = pd.DataFrame( + [ + {"region_id": "reg1", "sample_id": "s1", "condition": "NS", "read_id": "r1", "modified_count": 1, "valid_count": 4, "read_mod_fraction": 0.25}, + {"region_id": "reg1", "sample_id": "s1", "condition": "NS", "read_id": "r2", "modified_count": 2, "valid_count": 4, "read_mod_fraction": 0.50}, + {"region_id": "reg1", "sample_id": "s2", "condition": "treated", "read_id": "r3", "modified_count": 4, "valid_count": 4, "read_mod_fraction": 1.00}, + {"region_id": "reg1", "sample_id": "s3", "condition": "treated", "read_id": "r4", "modified_count": 3, "valid_count": 4, "read_mod_fraction": 0.75}, + ] + ) + + result = region_contrasts.score_regions( + samples=[], + regions=None, + contrast=ContrastSpec(mode="group_vs_group", numerator=["treated"], denominator=["NS"]), + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="extract_reads", + test="effect_size_only", + read_table=evidence, + ) + + row = result.summary.iloc[0] + assert row["region_id"] == "reg1" + assert row["sample_summary_numerator_mean"] == pytest.approx(0.875) + assert row["sample_summary_denominator_mean"] == pytest.approx(0.375) + assert row["delta_summary_mean"] == pytest.approx(0.5) +``` + +- [ ] **Step 3: Implement the sample-aware scoring path** + +Add internal helpers in `dimelo/region_contrasts.py`: + +```python +def _summarize_single_read_mod_fraction_by_sample(evidence: pd.DataFrame) -> pd.DataFrame: + return ( + evidence.groupby(["region_id", "sample_id", "condition"], as_index=False, sort=False) + .agg( + read_n=("read_id", "nunique"), + sample_summary_mean=("read_mod_fraction", "mean"), + sample_summary_median=("read_mod_fraction", "median"), + sample_summary_var=("read_mod_fraction", "var"), + ) + .fillna({"sample_summary_var": 0.0}) + ) + + +def _score_single_read_mod_fraction( + *, + evidence: pd.DataFrame, + contrast: ContrastSpec, + test: str, +) -> tuple[pd.DataFrame, pd.DataFrame]: + sample_summary = _summarize_single_read_mod_fraction_by_sample(evidence) + pooled = _pool_region_groups( + sample_summary.rename(columns={"sample_summary_mean": "mod_fraction"})[ + ["region_id", "sample_id", "condition", "mod_fraction"] + ], + contrast=contrast, + ) + pooled = pooled.rename( + columns={ + "fraction": "sample_summary_mean", + "reference_fraction": "sample_summary_reference_mean", + "delta_fraction": "delta_summary_mean", + } + ) + summary = pooled.sort_values("region_id", kind="stable").reset_index(drop=True) + return evidence, summary +``` + +Wire it into `score_regions(...)` behind: + +```python +if analysis_unit == "single_read" and representation == "read_mod_fraction": + if read_table is None: + raise ValueError("single_read read_mod_fraction scoring currently requires read_table.") + evidence = build_single_read_mod_fraction_evidence_table(extract_table=read_table) + regions_table, summary = _score_single_read_mod_fraction( + evidence=evidence, + contrast=contrast, + test=test, + ) +``` + +Keep `test="sample_distribution_shift"` mapped to the same first sample-aware summary path in v1. + +- [ ] **Step 4: Run the `read_mod_fraction` tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -k "single_read_mod_fraction" -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit the implementation** + +```bash +git add dimelo/region_contrasts.py tests/test_region_contrasts.py +git commit -m "feat: add single-read mod-fraction contrasts" +``` + +## Task 5: Add Failing Tests For `read_window_features` + +**Files:** +- Modify: `tests/test_region_contrasts.py` +- Reference: `dimelo/region_contrasts.py` + +- [ ] **Step 1: Write the failing feature-evidence and scoring tests** + +Add these tests: + +```python +def test_build_single_read_feature_evidence_table_accepts_user_features(): + feature_table = pd.DataFrame( + [ + {"region_id": "reg1", "sample_id": "s1", "condition": "NS", "read_id": "r1", "f0": 0.1, "f1": 0.2}, + {"region_id": "reg1", "sample_id": "s2", "condition": "treated", "read_id": "r2", "f0": 0.8, "f1": 0.9}, + ] + ) + + result = region_contrasts.build_single_read_feature_evidence_table(feature_table=feature_table) + + assert result.to_dict("records") == feature_table.to_dict("records") + + +def test_build_single_read_feature_evidence_table_rejects_missing_read_id(): + with pytest.raises(ValueError, match="read_id"): + region_contrasts.build_single_read_feature_evidence_table( + feature_table=pd.DataFrame([{"region_id": "reg1", "sample_id": "s1", "condition": "NS", "f0": 0.1}]) + ) + + +def test_score_regions_single_read_window_features_effect_size_only(): + feature_table = pd.DataFrame( + [ + {"region_id": "reg1", "sample_id": "s1", "condition": "NS", "read_id": "r1", "f0": 0.1, "f1": 0.2}, + {"region_id": "reg1", "sample_id": "s1", "condition": "NS", "read_id": "r2", "f0": 0.2, "f1": 0.3}, + {"region_id": "reg1", "sample_id": "s2", "condition": "treated", "read_id": "r3", "f0": 0.8, "f1": 0.9}, + {"region_id": "reg1", "sample_id": "s3", "condition": "treated", "read_id": "r4", "f0": 0.7, "f1": 0.8}, + ] + ) + + result = region_contrasts.score_regions( + samples=[], + regions=None, + contrast=ContrastSpec(mode="group_vs_group", numerator=["treated"], denominator=["NS"]), + analysis_unit="single_read", + representation="read_window_features", + signal_source="extract_features", + test="effect_size_only", + feature_table=feature_table, + ) + + row = result.summary.iloc[0] + assert row["region_id"] == "reg1" + assert row["f0_delta_mean"] == pytest.approx(0.6) + assert row["f1_delta_mean"] == pytest.approx(0.6) +``` + +- [ ] **Step 2: Run the feature tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -k "single_read_feature or single_read_window_features" -q +``` + +Expected: FAIL because the feature evidence builder and scoring path do not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_region_contrasts.py +git commit -m "test: add single-read feature contrast coverage" +``` + +## Task 6: Implement `read_window_features` With Built-In Default And User Override + +**Files:** +- Modify: `dimelo/region_contrasts.py` +- Modify: `tests/test_region_contrasts.py` + +- [ ] **Step 1: Add the user-feature evidence builder** + +Add this helper: + +```python +def build_single_read_feature_evidence_table( + *, + feature_table: pd.DataFrame, +) -> pd.DataFrame: + required_columns = {"region_id", "sample_id", "condition", "read_id"} + missing_columns = required_columns - set(feature_table.columns) + if missing_columns: + missing_display = ", ".join(sorted(missing_columns)) + raise ValueError( + "build_single_read_feature_evidence_table requires columns: " + f"{missing_display}." + ) + + feature_columns = [ + column for column in feature_table.columns if column not in required_columns + ] + if not feature_columns: + raise ValueError( + "build_single_read_feature_evidence_table requires at least one feature column." + ) + return feature_table.loc[:, ["region_id", "sample_id", "condition", "read_id", *feature_columns]].copy() +``` + +- [ ] **Step 2: Add a built-in feature-source shim** + +Add a narrow internal function that is easy to replace later with the real built-in feature extraction path: + +```python +def _load_builtin_single_read_feature_table(*, samples, regions, motifs): + raise NotImplementedError( + "Built-in single_read feature loading is not implemented yet." + ) +``` + +Then wire `score_regions(...)` so: + +- if `representation="read_window_features"` and `feature_table is not None`, use the user table +- if `feature_table is None`, call `_load_builtin_single_read_feature_table(...)` + +This keeps the built-in default contract explicit in the code and easy to patch in tests. + +- [ ] **Step 3: Implement feature-summary scoring** + +Add helpers: + +```python +def _summarize_single_read_features_by_sample(evidence: pd.DataFrame) -> pd.DataFrame: + group_columns = ["region_id", "sample_id", "condition"] + feature_columns = [ + column for column in evidence.columns if column not in {"region_id", "sample_id", "condition", "read_id"} + ] + return ( + evidence.groupby(group_columns, as_index=False, sort=False)[feature_columns] + .mean() + ) + + +def _score_single_read_features( + *, + evidence: pd.DataFrame, + contrast: ContrastSpec, +) -> tuple[pd.DataFrame, pd.DataFrame]: + sample_summary = _summarize_single_read_features_by_sample(evidence) + feature_columns = [ + column for column in sample_summary.columns if column not in {"region_id", "sample_id", "condition"} + ] + summary_rows = [] + for region_id, region_table in sample_summary.groupby("region_id", sort=False): + numerator_table = region_table[region_table["condition"].isin(contrast.numerator)] + denominator_table = region_table[region_table["condition"].isin(contrast.denominator)] + row = {"region_id": region_id} + for feature_name in feature_columns: + numerator_mean = numerator_table[feature_name].mean() + denominator_mean = denominator_table[feature_name].mean() + row[f"{feature_name}_numerator_mean"] = numerator_mean + row[f"{feature_name}_denominator_mean"] = denominator_mean + row[f"{feature_name}_delta_mean"] = numerator_mean - denominator_mean + summary_rows.append(row) + return evidence, pd.DataFrame(summary_rows) +``` + +- [ ] **Step 4: Add a failing built-in-default test, then implement it with a patched loader** + +Add this test: + +```python +def test_score_regions_single_read_window_features_uses_builtin_loader(monkeypatch): + feature_table = pd.DataFrame( + [ + {"region_id": "reg1", "sample_id": "s1", "condition": "NS", "read_id": "r1", "f0": 0.1}, + {"region_id": "reg1", "sample_id": "s2", "condition": "treated", "read_id": "r2", "f0": 0.9}, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "_load_builtin_single_read_feature_table", + lambda **kwargs: feature_table, + ) + + result = region_contrasts.score_regions( + samples=[], + regions="regions.bed", + contrast=ContrastSpec(mode="pairwise", numerator=["treated"], denominator=["NS"]), + analysis_unit="single_read", + representation="read_window_features", + signal_source="extract_features", + test="feature_summary_shift", + motifs=["A,0"], + ) + + assert result.summary.iloc[0]["f0_delta_mean"] == pytest.approx(0.8) +``` + +- [ ] **Step 5: Run the feature tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -k "single_read_feature or single_read_window_features" -q +``` + +Expected: PASS. + +- [ ] **Step 6: Commit the feature implementation** + +```bash +git add dimelo/region_contrasts.py tests/test_region_contrasts.py +git commit -m "feat: add single-read feature contrasts" +``` + +## Task 7: Add `matched_pairwise` Coverage For Both Tracks + +**Files:** +- Modify: `tests/test_region_contrasts.py` +- Modify: `dimelo/region_contrasts.py` + +- [ ] **Step 1: Write the failing matched-pairwise tests** + +Add these tests: + +```python +def test_score_regions_single_read_mod_fraction_supports_matched_pairwise(): + evidence = pd.DataFrame( + [ + {"region_id": "reg1", "sample_id": "s1_before", "condition": "before", "read_id": "r1", "modified_count": 1, "valid_count": 4, "read_mod_fraction": 0.25, "pair_id": "p1"}, + {"region_id": "reg1", "sample_id": "s1_after", "condition": "after", "read_id": "r2", "modified_count": 4, "valid_count": 4, "read_mod_fraction": 1.0, "pair_id": "p1"}, + {"region_id": "reg1", "sample_id": "s2_before", "condition": "before", "read_id": "r3", "modified_count": 2, "valid_count": 4, "read_mod_fraction": 0.5, "pair_id": "p2"}, + {"region_id": "reg1", "sample_id": "s2_after", "condition": "after", "read_id": "r4", "modified_count": 3, "valid_count": 4, "read_mod_fraction": 0.75, "pair_id": "p2"}, + ] + ) + + result = region_contrasts.score_regions( + samples=[], + regions=None, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["after"], + denominator=["before"], + pairing_key="pair_id", + ), + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="extract_reads", + test="sample_distribution_shift", + read_table=evidence, + ) + + assert result.summary.iloc[0]["delta_summary_mean"] > 0 + + +def test_score_regions_single_read_window_features_supports_matched_pairwise(): + feature_table = pd.DataFrame( + [ + {"region_id": "reg1", "sample_id": "s1_before", "condition": "before", "read_id": "r1", "pair_id": "p1", "f0": 0.1}, + {"region_id": "reg1", "sample_id": "s1_after", "condition": "after", "read_id": "r2", "pair_id": "p1", "f0": 0.9}, + {"region_id": "reg1", "sample_id": "s2_before", "condition": "before", "read_id": "r3", "pair_id": "p2", "f0": 0.2}, + {"region_id": "reg1", "sample_id": "s2_after", "condition": "after", "read_id": "r4", "pair_id": "p2", "f0": 0.8}, + ] + ) + + result = region_contrasts.score_regions( + samples=[], + regions=None, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["after"], + denominator=["before"], + pairing_key="pair_id", + ), + analysis_unit="single_read", + representation="read_window_features", + signal_source="extract_features", + test="feature_summary_shift", + feature_table=feature_table, + ) + + assert result.summary.iloc[0]["f0_delta_mean"] > 0 +``` + +- [ ] **Step 2: Implement matched-pairwise support conservatively** + +Use sample-summary level tables only. Do not add pooled-read logic. The minimal first pass should: + +- preserve `pairing_key` columns from the incoming read or feature table +- carry them through sample summaries +- compute deltas using only complete pairs + +Add a narrow helper if needed: + +```python +def _require_pairing_column(frame: pd.DataFrame, pairing_key: str) -> None: + if pairing_key not in frame.columns: + raise ValueError( + f"matched_pairwise single_read scoring requires column {pairing_key!r}." + ) +``` + +- [ ] **Step 3: Run the matched-pairwise tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -k "matched_pairwise and single_read" -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit the matched-pairwise support** + +```bash +git add dimelo/region_contrasts.py tests/test_region_contrasts.py +git commit -m "feat: add matched single-read contrasts" +``` + +## Task 8: Document The New `single_read` Contrast Family + +**Files:** +- Modify: `docs/region-contrasts.md` +- Modify: `README.md` + +- [ ] **Step 1: Update the region contrasts guide** + +Add a short section to `docs/region-contrasts.md`: + +```md +## Single-Read Contrasts + +`region_contrasts` now supports `analysis_unit="single_read"` for defined-region +comparison on extract-backed read data. + +First-version representations: + +- `representation="read_mod_fraction"` with `signal_source="extract_reads"` +- `representation="read_window_features"` with `signal_source="extract_features"` + +Supported contrast modes in v1: + +- `pairwise` +- `group_vs_group` +- `matched_pairwise` + +Important: reads remain observational units, but samples remain the inferential units. +The v1 single-read paths summarize reads within each `region x sample` before comparing +conditions. +``` + +- [ ] **Step 2: Add a short README note** + +Add one short paragraph near the `region_contrasts` overview in `README.md`: + +```md +`region_contrasts` also now supports `analysis_unit="single_read"` for extract-backed +defined-region comparison. Use `representation="read_mod_fraction"` for per-read motif +fractions or `representation="read_window_features"` for feature-vector summaries, with +sample-aware summaries rather than pooled-read inference. +``` + +- [ ] **Step 3: Commit the docs** + +```bash +git add docs/region-contrasts.md README.md +git commit -m "docs: add single-read contrast guide" +``` + +## Task 9: Run Final Verification + +**Files:** +- Reference: `dimelo/region_contrasts.py` +- Reference: `tests/test_region_contrasts.py` +- Reference: `docs/region-contrasts.md` +- Reference: `README.md` + +- [ ] **Step 1: Run the full region contrast test file** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_region_contrasts.py -q +``` + +Expected: PASS. + +- [ ] **Step 2: Run a targeted workflow/plotting regression subset** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl XDG_CACHE_HOME=/tmp/xdg-cache PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py tests/test_workflows.py -q +``` + +Expected: PASS with no new failures. + +- [ ] **Step 3: Run a quick git status check** + +Run: + +```bash +git status --short +``` + +Expected: clean working tree. + +- [ ] **Step 4: Self-review against the spec** + +Check the completed work against `docs/superpowers/specs/2026-04-03-single-read-region-contrasts-design.md`: + +- `analysis_unit="single_read"` added inside `region_contrasts` +- `read_mod_fraction` implemented +- `read_window_features` implemented +- built-in feature default and user override supported +- sample-aware summaries preserved +- v1 support remains bounded to `pairwise`, `group_vs_group`, and `matched_pairwise` +- no time-course or background-adjusted work accidentally added + +- [ ] **Step 5: Commit any final touch-ups if needed** + +```bash +git add dimelo/region_contrasts.py tests/test_region_contrasts.py docs/region-contrasts.md README.md +git commit -m "fix: tighten single-read contrast support" +``` + +Only make this commit if verification or self-review uncovered a small follow-up patch. diff --git a/docs/superpowers/plans/2026-04-08-matplotlib-renderers.md b/docs/superpowers/plans/2026-04-08-matplotlib-renderers.md new file mode 100644 index 0000000..2570f4f --- /dev/null +++ b/docs/superpowers/plans/2026-04-08-matplotlib-renderers.md @@ -0,0 +1,649 @@ +# Matplotlib Renderers Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a first built-in Matplotlib renderer layer and figure export helper on top of the existing renderer-neutral plotting payloads for `region_contrasts`, `region_discovery`, and `global_analysis`. + +**Architecture:** Keep `dimelo.plotting` as the renderer-neutral prep layer and add a new `dimelo.plotting_matplotlib` module that only consumes prepared payloads and returns Matplotlib figure objects. The renderer layer stays thin: no new biological aggregation, no result-to-payload shortcuts, and only small optional export helpers for writing PNG/PDF outputs. + +**Tech Stack:** Python 3.11, matplotlib, pandas, pathlib, pytest, existing `dimelo.plotting`, user docs in `docs/` + +--- + +## File Map + +- Create: `dimelo/plotting_matplotlib.py` + - thin Matplotlib renderers over existing payload dictionaries + - `save_figure(...)` convenience helper +- Modify: `dimelo/__init__.py` + - export `plotting_matplotlib` +- Modify: `tests/test_plotting.py` + - add focused renderer and export coverage +- Modify: `README.md` + - document the optional Matplotlib renderer layer +- Modify: `docs/region-contrasts.md` + - add short renderer example for contrast payloads +- Modify: `docs/region-discovery.md` + - add short renderer example for discovery payloads +- Modify: `docs/global-analysis.md` + - add short renderer example for global-analysis payloads + +## Task 1: Add Failing Tests For The New Matplotlib Module Skeleton + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/plotting_matplotlib.py` + +- [ ] **Step 1: Write failing tests for module import and `save_figure(...)`** + +Add these tests near the existing plotting coverage in `tests/test_plotting.py`: + +```python +def test_plotting_matplotlib_module_exports_save_figure(): + from dimelo import plotting_matplotlib + + assert hasattr(plotting_matplotlib, "save_figure") + + +def test_save_figure_writes_png(tmp_path): + from matplotlib import pyplot as plt + from dimelo import plotting_matplotlib + + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + output_path = tmp_path / "figure.png" + plotting_matplotlib.save_figure(fig, output_path) + + assert output_path.exists() + assert output_path.stat().st_size > 0 +``` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plotting_matplotlib_module_exports_save_figure or save_figure_writes_png" -q +``` + +Expected: FAIL because `dimelo.plotting_matplotlib` does not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_plotting.py +git commit -m "test: add matplotlib renderer export coverage" +``` + +## Task 2: Create `dimelo.plotting_matplotlib` And `save_figure(...)` + +**Files:** +- Create: `dimelo/plotting_matplotlib.py` +- Modify: `dimelo/__init__.py` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Create the new module with a thin export helper** + +Create `dimelo/plotting_matplotlib.py` with: + +```python +from __future__ import annotations + +from pathlib import Path + +import matplotlib.pyplot as plt + + +def save_figure( + fig, + path, + *, + dpi: int = 300, + bbox_inches: str = "tight", + transparent: bool = False, +) -> Path: + output_path = Path(path) + output_path.parent.mkdir(parents=True, exist_ok=True) + fig.savefig( + output_path, + dpi=dpi, + bbox_inches=bbox_inches, + transparent=transparent, + ) + return output_path +``` + +- [ ] **Step 2: Export the new module from the package** + +Update `dimelo/__init__.py` to import and export `plotting_matplotlib`: + +```python +from . import ( + ... + plotting, + plotting_matplotlib, + ... +) + +__all__ = [ + ... + "plotting", + "plotting_matplotlib", + ... +] +``` + +- [ ] **Step 3: Run the focused tests to verify they pass** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plotting_matplotlib_module_exports_save_figure or save_figure_writes_png" -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit the module skeleton** + +```bash +git add dimelo/plotting_matplotlib.py dimelo/__init__.py tests/test_plotting.py +git commit -m "feat: add matplotlib renderer module" +``` + +## Task 3: Add Failing Tests For Region Contrast Matplotlib Renderers + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/plotting.py` +- Reference: `dimelo/plotting_matplotlib.py` + +- [ ] **Step 1: Add failing tests for profile and heatmap renderers** + +Add tests using the existing `prepare_region_contrast_profile_data(...)` and +`prepare_region_contrast_heatmap_data(...)` fixtures/patterns: + +```python +def test_plot_region_contrast_profile_matplotlib_returns_figure_and_axis(): + from dimelo import plotting, plotting_matplotlib + + result = _mock_region_contrast_result_for_plotting() + position_table = _mock_region_contrast_position_table() + payload = plotting.prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=100, + downstream_bp=100, + ), + aggregation=plotting.AggregationSpec(), + ) + + fig, ax = plotting_matplotlib.plot_region_contrast_profile_matplotlib(payload) + + assert fig is not None + assert ax is not None + + +def test_plot_region_contrast_heatmap_matplotlib_returns_figure_and_axis(): + from dimelo import plotting, plotting_matplotlib + + result = _mock_region_contrast_result_for_plotting() + position_table = _mock_region_contrast_position_table() + payload = plotting.prepare_region_contrast_heatmap_data( + result=result, + position_table=position_table, + axis=plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=100, + downstream_bp=100, + ), + aggregation=plotting.AggregationSpec(), + ) + + fig, ax = plotting_matplotlib.plot_region_contrast_heatmap_matplotlib(payload) + + assert fig is not None + assert ax is not None +``` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_region_contrast_profile_matplotlib or plot_region_contrast_heatmap_matplotlib" -q +``` + +Expected: FAIL because the renderers do not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_plotting.py +git commit -m "test: add region contrast matplotlib coverage" +``` + +## Task 4: Implement Region Contrast Matplotlib Renderers + +**Files:** +- Modify: `dimelo/plotting_matplotlib.py` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Add minimal helper utilities for Matplotlib payload validation** + +Add small internal helpers in `dimelo/plotting_matplotlib.py`: + +```python +def _require_payload_keys(payload: dict, keys: tuple[str, ...], *, owner: str) -> None: + missing = [key for key in keys if key not in payload] + if missing: + missing_display = ", ".join(missing) + raise ValueError(f"{owner} requires payload keys: {missing_display}.") + + +def _make_axis(ax=None, *, figsize=(6, 4)): + if ax is not None: + return ax.figure, ax + fig, created_ax = plt.subplots(figsize=figsize) + return fig, created_ax +``` + +- [ ] **Step 2: Implement `plot_region_contrast_profile_matplotlib(...)`** + +Implement a thin line renderer over `payload["plot_table"]`: + +```python +def plot_region_contrast_profile_matplotlib( + payload, + *, + value_mode: str = "delta", + ax=None, + title: str | None = None, +): + _require_payload_keys(payload, ("plot_table", "metadata"), owner="plot_region_contrast_profile_matplotlib") + plot_table = payload["plot_table"] + if value_mode not in {"numerator", "denominator", "delta"}: + raise ValueError("value_mode must be 'numerator', 'denominator', or 'delta'.") + value_column = { + "numerator": "numerator_value", + "denominator": "denominator_value", + "delta": "delta_value", + }[value_mode] + fig, ax = _make_axis(ax=ax) + if not plot_table.empty: + grouped = plot_table.groupby("plot_position", sort=True)[value_column].mean().reset_index() + ax.plot(grouped["plot_position"], grouped[value_column]) + ax.set_xlabel("Position") + ax.set_ylabel(value_mode.replace("_", " ").title()) + if title: + ax.set_title(title) + return fig, ax +``` + +- [ ] **Step 3: Implement `plot_region_contrast_heatmap_matplotlib(...)`** + +Implement a thin heatmap renderer using `imshow`: + +```python +def plot_region_contrast_heatmap_matplotlib( + payload, + *, + value_mode: str = "delta", + ax=None, + title: str | None = None, +): + _require_payload_keys(payload, ("plot_table", "metadata"), owner="plot_region_contrast_heatmap_matplotlib") + plot_table = payload["plot_table"] + if value_mode not in {"numerator", "denominator", "delta"}: + raise ValueError("value_mode must be 'numerator', 'denominator', or 'delta'.") + value_column = { + "numerator": "numerator_value", + "denominator": "denominator_value", + "delta": "delta_value", + }[value_mode] + fig, ax = _make_axis(ax=ax, figsize=(7, 5)) + if not plot_table.empty: + matrix = plot_table.pivot(index="region_id", columns="plot_position", values=value_column) + image = ax.imshow(matrix.to_numpy(), aspect="auto", interpolation="nearest") + fig.colorbar(image, ax=ax) + ax.set_xlabel("Position") + ax.set_ylabel("Region") + if title: + ax.set_title(title) + return fig, ax +``` + +- [ ] **Step 4: Run the focused tests to verify they pass** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_region_contrast_profile_matplotlib or plot_region_contrast_heatmap_matplotlib" -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit the region-contrast renderers** + +```bash +git add dimelo/plotting_matplotlib.py tests/test_plotting.py +git commit -m "feat: add region contrast matplotlib renderers" +``` + +## Task 5: Add Discovery Renderer Tests And Implementations + +**Files:** +- Modify: `tests/test_plotting.py` +- Modify: `dimelo/plotting_matplotlib.py` + +- [ ] **Step 1: Add failing tests for discovery scan and hit-context renderers** + +Add: + +```python +def test_plot_region_discovery_scan_matplotlib_returns_figure_and_axes(): + from dimelo import plotting, plotting_matplotlib + + payload = plotting.prepare_region_discovery_scan_data(_mock_region_discovery_result()) + fig, axes = plotting_matplotlib.plot_region_discovery_scan_matplotlib(payload) + + assert fig is not None + assert axes is not None + + +def test_plot_region_discovery_hit_context_matplotlib_returns_figure_and_axes(): + from dimelo import plotting, plotting_matplotlib + + payload = plotting.prepare_region_discovery_hit_context_data( + _mock_region_discovery_result(), + top_n=2, + ) + fig, axes = plotting_matplotlib.plot_region_discovery_hit_context_matplotlib(payload) + + assert fig is not None + assert axes is not None +``` + +- [ ] **Step 2: Run the focused discovery renderer tests to verify they fail** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_region_discovery_scan_matplotlib or plot_region_discovery_hit_context_matplotlib" -q +``` + +Expected: FAIL. + +- [ ] **Step 3: Implement the two discovery renderers** + +Add thin per-contig and small-multiple renderers: + +```python +def plot_region_discovery_scan_matplotlib(payload, *, axes=None, title: str | None = None): + _require_payload_keys(payload, ("scan_table", "hit_table", "metadata"), owner="plot_region_discovery_scan_matplotlib") + scan_table = payload["scan_table"] + contigs = payload["metadata"].get("contig_order") or list(scan_table["contig"].dropna().unique()) + if axes is None: + fig, axes = plt.subplots(len(contigs) or 1, 1, figsize=(8, max(3, 2 * max(len(contigs), 1))), squeeze=False) + axes = axes.ravel() + else: + fig = axes[0].figure if isinstance(axes, (list, tuple)) else axes.figure + axes = axes if isinstance(axes, (list, tuple)) else [axes] + for ax, contig in zip(axes, contigs): + contig_table = scan_table.loc[scan_table["contig"] == contig] + ax.plot(contig_table["window_midpoint"], contig_table[payload["metadata"]["score_column"]]) + ax.set_title(str(contig)) + if title: + fig.suptitle(title) + return fig, axes + + +def plot_region_discovery_hit_context_matplotlib(payload, *, axes=None, title: str | None = None): + _require_payload_keys(payload, ("context_table", "selected_hits", "metadata"), owner="plot_region_discovery_hit_context_matplotlib") + context_table = payload["context_table"] + hit_ids = list(context_table["selected_region_id"].dropna().unique()) if not context_table.empty else [] + if axes is None: + fig, axes = plt.subplots(len(hit_ids) or 1, 1, figsize=(8, max(3, 2 * max(len(hit_ids), 1))), squeeze=False) + axes = axes.ravel() + else: + fig = axes[0].figure if isinstance(axes, (list, tuple)) else axes.figure + axes = axes if isinstance(axes, (list, tuple)) else [axes] + score_column = payload["metadata"]["score_column"] + for ax, hit_id in zip(axes, hit_ids): + hit_table = context_table.loc[context_table["selected_region_id"] == hit_id] + ax.plot(hit_table["window_midpoint"], hit_table[score_column]) + ax.set_title(str(hit_id)) + if title: + fig.suptitle(title) + return fig, axes +``` + +- [ ] **Step 4: Run the focused discovery tests to verify they pass** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_region_discovery_scan_matplotlib or plot_region_discovery_hit_context_matplotlib" -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit the discovery renderers** + +```bash +git add dimelo/plotting_matplotlib.py tests/test_plotting.py +git commit -m "feat: add discovery matplotlib renderers" +``` + +## Task 6: Add Global Analysis Renderer Tests And Implementations + +**Files:** +- Modify: `tests/test_plotting.py` +- Modify: `dimelo/plotting_matplotlib.py` + +- [ ] **Step 1: Add failing tests for summary and window renderers** + +Add: + +```python +def test_plot_global_analysis_summary_matplotlib_returns_figure_and_axis(): + from dimelo import plotting, plotting_matplotlib + + payload = plotting.prepare_global_analysis_summary_data(_mock_global_analysis_result()) + fig, ax = plotting_matplotlib.plot_global_analysis_summary_matplotlib(payload) + + assert fig is not None + assert ax is not None + + +def test_plot_global_analysis_window_matplotlib_returns_figure_and_axes(): + from dimelo import plotting, plotting_matplotlib + + payload = plotting.prepare_global_analysis_window_data(_mock_global_analysis_result()) + fig, axes = plotting_matplotlib.plot_global_analysis_window_matplotlib(payload) + + assert fig is not None + assert axes is not None +``` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_global_analysis_summary_matplotlib or plot_global_analysis_window_matplotlib" -q +``` + +Expected: FAIL. + +- [ ] **Step 3: Implement the two global-analysis renderers** + +Add: + +```python +def plot_global_analysis_summary_matplotlib( + payload, + *, + level: str = "condition", + ax=None, + title: str | None = None, +): + _require_payload_keys(payload, ("sample_summary", "condition_summary", "normalization_table", "metadata"), owner="plot_global_analysis_summary_matplotlib") + if level not in {"sample", "condition"}: + raise ValueError("level must be 'sample' or 'condition'.") + table = payload["sample_summary"] if level == "sample" else payload["condition_summary"] + fig, ax = _make_axis(ax=ax) + if not table.empty: + x_column = "sample_id" if level == "sample" else "condition" + ax.bar(table[x_column].astype(str), table["global_fraction"]) + ax.tick_params(axis="x", rotation=45) + ax.set_ylabel("Global Fraction") + if title: + ax.set_title(title) + return fig, ax + + +def plot_global_analysis_window_matplotlib(payload, *, axes=None, title: str | None = None): + _require_payload_keys(payload, ("window_table", "metadata"), owner="plot_global_analysis_window_matplotlib") + window_table = payload["window_table"] + contigs = payload["metadata"].get("contig_order") or list(window_table["contig"].dropna().unique()) + if axes is None: + fig, axes = plt.subplots(len(contigs) or 1, 1, figsize=(8, max(3, 2 * max(len(contigs), 1))), squeeze=False) + axes = axes.ravel() + else: + fig = axes[0].figure if isinstance(axes, (list, tuple)) else axes.figure + axes = axes if isinstance(axes, (list, tuple)) else [axes] + for ax, contig in zip(axes, contigs): + contig_table = window_table.loc[window_table["contig"] == contig] + ax.plot(contig_table["window_midpoint"], contig_table["window_fraction"]) + ax.set_title(str(contig)) + if title: + fig.suptitle(title) + return fig, axes +``` + +- [ ] **Step 4: Run the focused tests to verify they pass** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_global_analysis_summary_matplotlib or plot_global_analysis_window_matplotlib" -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit the global-analysis renderers** + +```bash +git add dimelo/plotting_matplotlib.py tests/test_plotting.py +git commit -m "feat: add global analysis matplotlib renderers" +``` + +## Task 7: Add User-Facing Docs For The Renderer Layer + +**Files:** +- Modify: `README.md` +- Modify: `docs/region-contrasts.md` +- Modify: `docs/region-discovery.md` +- Modify: `docs/global-analysis.md` + +- [ ] **Step 1: Add a short README paragraph** + +Add a short note near the plotting/helper overview: + +```md +For users who want built-in figures instead of only payload tables, `dimelo.plotting_matplotlib` +provides thin Matplotlib renderers on top of the prepared payloads plus `save_figure(...)` +for writing PNG/PDF outputs. The payload-prep helpers remain the canonical interface. +``` + +- [ ] **Step 2: Add one short example to each analysis guide** + +Add renderer examples like: + +```python +from dimelo import plotting, plotting_matplotlib + +payload = plotting.prepare_region_discovery_scan_data(result) +fig, axes = plotting_matplotlib.plot_region_discovery_scan_matplotlib(payload) +plotting_matplotlib.save_figure(fig, "discovery-scan.png") +``` + +Repeat the same pattern for: +- `region-contrasts.md` +- `region-discovery.md` +- `global-analysis.md` + +- [ ] **Step 3: Commit the docs** + +```bash +git add README.md docs/region-contrasts.md docs/region-discovery.md docs/global-analysis.md +git commit -m "docs: add matplotlib renderer examples" +``` + +## Task 8: Final Verification + +**Files:** +- Reference: `dimelo/plotting_matplotlib.py` +- Reference: `tests/test_plotting.py` +- Reference: `README.md` +- Reference: `docs/region-contrasts.md` +- Reference: `docs/region-discovery.md` +- Reference: `docs/global-analysis.md` + +- [ ] **Step 1: Run plotting coverage** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl XDG_CACHE_HOME=/tmp/xdg-cache PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q +``` + +Expected: PASS. + +- [ ] **Step 2: Run workflow regression subset** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl XDG_CACHE_HOME=/tmp/xdg-cache PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_workflows.py -q +``` + +Expected: PASS with no new failures. + +- [ ] **Step 3: Run a clean status check** + +Run: + +```bash +git status --short +``` + +Expected: clean working tree. + +- [ ] **Step 4: Self-review against the spec** + +Check the completed work against `docs/superpowers/specs/2026-04-08-matplotlib-renderers-design.md`: + +- separate `plotting_matplotlib` module added +- prep layer still lives in `dimelo.plotting` +- renderers cover `region_contrasts`, `region_discovery`, and `global_analysis` +- export helper exists +- no new biological prep logic was moved into renderer functions +- docs describe the renderer layer as optional and Matplotlib-specific + +- [ ] **Step 5: Commit any final touch-up if needed** + +```bash +git add dimelo/plotting_matplotlib.py dimelo/__init__.py tests/test_plotting.py README.md docs/region-contrasts.md docs/region-discovery.md docs/global-analysis.md +git commit -m "fix: tighten matplotlib renderer support" +``` + +Only make this commit if verification or self-review uncovers a small follow-up patch. + diff --git a/docs/superpowers/plans/2026-04-10-shared-clustering-matplotlib-renderers.md b/docs/superpowers/plans/2026-04-10-shared-clustering-matplotlib-renderers.md new file mode 100644 index 0000000..0859b0b --- /dev/null +++ b/docs/superpowers/plans/2026-04-10-shared-clustering-matplotlib-renderers.md @@ -0,0 +1,637 @@ +# Shared Clustering Matplotlib Renderers Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add built-in Matplotlib renderers for `shared_clustering` distribution/change, cluster profiles, and region occupancy on top of the existing renderer-neutral payloads. + +**Architecture:** Keep `dimelo.plotting` as the source of truth for clustering plot payloads and extend `dimelo.plotting_matplotlib` with thin renderers that only consume prepared tables. Allow only narrowly scoped prep-layer adjustments if current payloads are awkward to render cleanly; do not add new clustering outputs or new analysis behavior. + +**Tech Stack:** Python 3.11, matplotlib, pandas, pytest, existing `dimelo.plotting`, existing `dimelo.plotting_matplotlib`, user docs in `docs/` + +--- + +## File Map + +- Modify: `dimelo/plotting_matplotlib.py` + - add the five `shared_clustering` Matplotlib renderers + - reuse existing axis/payload helpers where possible +- Modify: `dimelo/plotting.py` + - only if a small payload-shape tweak is needed for renderability +- Modify: `tests/test_plotting.py` + - add focused renderer coverage for the new functions +- Modify: `README.md` + - document the optional shared-clustering Matplotlib renderer layer +- Modify: `docs/shared-clustering.md` + - add short renderer examples and clarify default condition-level occupancy view + +## Task 1: Add Failing Tests For Shared-Cluster Distribution And Change Renderers + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/plotting.py` +- Reference: `dimelo/plotting_matplotlib.py` + +- [ ] **Step 1: Write failing tests for distribution and change renderers** + +Add tests near the existing `prepare_shared_cluster_distribution_data(...)` coverage: + +```python +def test_plot_shared_cluster_distribution_matplotlib_returns_figure_and_axis(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_distribution_data( + result=_make_shared_cluster_result() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_distribution_matplotlib(payload) + + assert fig is not None + assert ax is not None + + +def test_plot_shared_cluster_change_matplotlib_returns_figure_and_axis(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_distribution_data( + result=_make_shared_cluster_result() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_change_matplotlib(payload) + + assert fig is not None + assert ax is not None +``` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_shared_cluster_distribution_matplotlib or plot_shared_cluster_change_matplotlib" -q +``` + +Expected: FAIL because the two renderer functions do not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_plotting.py +git commit -m "test: add shared clustering distribution renderer coverage" +``` + +## Task 2: Implement Shared-Cluster Distribution And Change Renderers + +**Files:** +- Modify: `dimelo/plotting_matplotlib.py` +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Add thin distribution and change renderers** + +Add the new public functions in `dimelo/plotting_matplotlib.py`: + +```python +def plot_shared_cluster_distribution_matplotlib( + payload: Mapping[str, object], + *, + level: str = "condition", + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("sample_distribution", "condition_distribution", "metadata"), + owner="plot_shared_cluster_distribution_matplotlib", + ) + if level not in {"sample", "condition"}: + raise ValueError("level must be 'sample' or 'condition'.") + + table = _require_payload_table( + payload, + "sample_distribution" if level == "sample" else "condition_distribution", + ) + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + if not table.empty: + x_column = "sample_id" if level == "sample" else "condition" + pivot = ( + table.pivot_table( + index=x_column, + columns="cluster", + values="fraction", + aggfunc="mean", + fill_value=0.0, + ) + .sort_index(axis=0) + .sort_index(axis=1) + ) + pivot.plot(kind="bar", stacked=True, ax=ax, legend=True) + ax.tick_params(axis="x", rotation=45) + + ax.set_xlabel("Sample" if level == "sample" else "Condition") + ax.set_ylabel("Fraction") + ax.set_title(title or "Shared cluster distribution") + return fig, ax + + +def plot_shared_cluster_change_matplotlib( + payload: Mapping[str, object], + *, + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("distribution_change", "metadata"), + owner="plot_shared_cluster_change_matplotlib", + ) + change_table = _require_payload_table(payload, "distribution_change") + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + if not change_table.empty: + matrix = ( + change_table.pivot_table( + index="condition", + columns="cluster", + values="delta_fraction", + aggfunc="mean", + fill_value=0.0, + ) + .sort_index(axis=0) + .sort_index(axis=1) + ) + image = ax.imshow( + matrix.to_numpy(), + aspect="auto", + origin="lower", + interpolation="nearest", + ) + ax.figure.colorbar(image, ax=ax, label="Delta Fraction") + ax.set_xticks(range(len(matrix.columns))) + ax.set_xticklabels([str(value) for value in matrix.columns.tolist()], rotation=45, ha="right") + ax.set_yticks(range(len(matrix.index))) + ax.set_yticklabels([str(value) for value in matrix.index.tolist()]) + + ax.set_xlabel("Cluster") + ax.set_ylabel("Condition") + ax.set_title(title or "Shared cluster change") + return fig, ax +``` + +- [ ] **Step 2: Run the focused tests to verify they pass** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_shared_cluster_distribution_matplotlib or plot_shared_cluster_change_matplotlib" -q +``` + +Expected: PASS. + +- [ ] **Step 3: Commit the distribution and change renderers** + +```bash +git add dimelo/plotting_matplotlib.py tests/test_plotting.py +git commit -m "feat: add shared clustering distribution renderers" +``` + +## Task 3: Add Failing Tests For Shared-Cluster Profile Renderers + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/plotting.py` +- Reference: `dimelo/plotting_matplotlib.py` + +- [ ] **Step 1: Write failing tests for profile heatmap and profile-series renderers** + +Add tests near the existing `prepare_shared_cluster_profile_data(...)` coverage: + +```python +def test_plot_shared_cluster_profile_heatmap_matplotlib_returns_figure_and_axis(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_profile_data( + result=_make_shared_cluster_profile_result() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_profile_heatmap_matplotlib(payload) + + assert fig is not None + assert ax is not None + + +def test_plot_shared_cluster_profile_series_matplotlib_returns_figure_and_axis(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_profile_data( + result=_make_shared_cluster_profile_result() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_profile_series_matplotlib(payload) + + assert fig is not None + assert ax is not None +``` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_shared_cluster_profile_heatmap_matplotlib or plot_shared_cluster_profile_series_matplotlib" -q +``` + +Expected: FAIL because the profile renderer functions do not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_plotting.py +git commit -m "test: add shared clustering profile renderer coverage" +``` + +## Task 4: Implement Shared-Cluster Profile Renderers + +**Files:** +- Modify: `dimelo/plotting_matplotlib.py` +- Modify: `dimelo/plotting.py` only if a small metadata tweak is clearly needed +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Add the profile heatmap and series renderers** + +Implement the two profile renderers in `dimelo/plotting_matplotlib.py`: + +```python +def plot_shared_cluster_profile_heatmap_matplotlib( + payload: Mapping[str, object], + *, + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("profile_table", "metadata"), + owner="plot_shared_cluster_profile_heatmap_matplotlib", + ) + profile_table = _require_payload_table(payload, "profile_table") + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + if not profile_table.empty: + matrix = ( + profile_table.pivot_table( + index="cluster", + columns="feature", + values="value", + aggfunc="mean", + ) + .sort_index(axis=0) + .sort_index(axis=1) + ) + image = ax.imshow( + matrix.to_numpy(), + aspect="auto", + origin="lower", + interpolation="nearest", + ) + ax.figure.colorbar(image, ax=ax, label="Value") + ax.set_xticks(range(len(matrix.columns))) + ax.set_xticklabels([str(value) for value in matrix.columns.tolist()], rotation=45, ha="right") + ax.set_yticks(range(len(matrix.index))) + ax.set_yticklabels([str(value) for value in matrix.index.tolist()]) + + ax.set_xlabel("Feature") + ax.set_ylabel("Cluster") + ax.set_title(title or "Shared cluster profiles") + return fig, ax + + +def plot_shared_cluster_profile_series_matplotlib( + payload: Mapping[str, object], + *, + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("profile_table", "metadata"), + owner="plot_shared_cluster_profile_series_matplotlib", + ) + profile_table = _require_payload_table(payload, "profile_table") + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + if not profile_table.empty: + feature_order = list(payload.get("metadata", {}).get("feature_names", [])) + if not feature_order: + feature_order = sorted(profile_table["feature"].dropna().astype(str).unique()) + feature_lookup = {feature: index for index, feature in enumerate(feature_order)} + + for cluster, cluster_table in profile_table.groupby("cluster", sort=False): + cluster_table = cluster_table.copy() + cluster_table["feature_order"] = cluster_table["feature"].map(feature_lookup) + cluster_table = cluster_table.sort_values("feature_order", kind="stable") + ax.plot( + cluster_table["feature_order"], + cluster_table["value"], + marker="o", + linewidth=1.5, + label=str(cluster), + ) + + ax.set_xticks(range(len(feature_order))) + ax.set_xticklabels(feature_order, rotation=45, ha="right") + ax.legend(title="Cluster") + + ax.set_xlabel("Feature") + ax.set_ylabel("Value") + ax.set_title(title or "Shared cluster profile series") + return fig, ax +``` + +- [ ] **Step 2: Run the focused tests to verify they pass** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_shared_cluster_profile_heatmap_matplotlib or plot_shared_cluster_profile_series_matplotlib" -q +``` + +Expected: PASS. + +- [ ] **Step 3: Commit the profile renderers** + +```bash +git add dimelo/plotting_matplotlib.py dimelo/plotting.py tests/test_plotting.py +git commit -m "feat: add shared clustering profile renderers" +``` + +## Task 5: Add Failing Tests For Shared-Cluster Region Renderers + +**Files:** +- Modify: `tests/test_plotting.py` +- Reference: `dimelo/plotting.py` +- Reference: `dimelo/plotting_matplotlib.py` + +- [ ] **Step 1: Write failing tests for the region occupancy renderer** + +Add tests near the existing `prepare_shared_cluster_region_data(...)` coverage: + +```python +def test_plot_shared_cluster_region_matplotlib_defaults_to_condition_level(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_region_data( + result=_make_shared_cluster_region_result() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_region_matplotlib(payload) + + assert fig is not None + assert ax is not None + + +def test_plot_shared_cluster_region_matplotlib_supports_sample_level(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_region_data( + result=_make_shared_cluster_region_result() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_region_matplotlib( + payload, + level="sample", + ) + + assert fig is not None + assert ax is not None +``` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_shared_cluster_region_matplotlib" -q +``` + +Expected: FAIL because the region renderer does not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_plotting.py +git commit -m "test: add shared clustering region renderer coverage" +``` + +## Task 6: Implement Shared-Cluster Region Renderer And Any Minimal Prep Fix + +**Files:** +- Modify: `dimelo/plotting_matplotlib.py` +- Modify: `dimelo/plotting.py` only if a minimal payload tweak is needed +- Modify: `tests/test_plotting.py` + +- [ ] **Step 1: Implement the condition-default region occupancy renderer** + +Implement the renderer in `dimelo/plotting_matplotlib.py`: + +```python +def plot_shared_cluster_region_matplotlib( + payload: Mapping[str, object], + *, + level: str = "condition", + ax=None, + title: str | None = None, +): + _require_payload_keys( + payload, + ("region_table", "condition_region_table", "metadata"), + owner="plot_shared_cluster_region_matplotlib", + ) + if level not in {"sample", "condition"}: + raise ValueError("level must be 'sample' or 'condition'.") + + table = _require_payload_table( + payload, + "condition_region_table" if level == "condition" else "region_table", + ) + fig, ax = _make_axis(ax=ax, figsize=(8, 4)) + + if not table.empty: + value_column = "fraction_mean" if level == "condition" else "fraction" + row_columns = ["region_id", "condition"] if level == "condition" else ["region_id", "sample_id"] + heatmap = table.copy() + heatmap["row_id"] = heatmap[row_columns].astype(str).agg(" | ".join, axis=1) + matrix = ( + heatmap.pivot_table( + index="row_id", + columns="cluster", + values=value_column, + aggfunc="mean", + fill_value=0.0, + ) + .sort_index(axis=0) + .sort_index(axis=1) + ) + image = ax.imshow( + matrix.to_numpy(), + aspect="auto", + origin="lower", + interpolation="nearest", + ) + ax.figure.colorbar(image, ax=ax, label=value_column.replace("_", " ").title()) + ax.set_xticks(range(len(matrix.columns))) + ax.set_xticklabels([str(value) for value in matrix.columns.tolist()], rotation=45, ha="right") + ax.set_yticks(range(len(matrix.index))) + ax.set_yticklabels([str(value) for value in matrix.index.tolist()]) + + ax.set_xlabel("Cluster") + ax.set_ylabel("Region / Condition" if level == "condition" else "Region / Sample") + ax.set_title(title or "Shared cluster region occupancy") + return fig, ax +``` + +- [ ] **Step 2: Add only a minimal prep-layer tweak if the renderer work proves one is necessary** + +If the renderer implementation exposes a real payload friction, keep any prep-layer change small and explicit. An acceptable example is adding a stable metadata ordering field in `prepare_shared_cluster_region_data(...)`: + +```python +return { + "region_table": region_table, + "condition_region_table": condition_region_table, + "metadata": { + "mode": result.model.mode, + "cluster_labels": list(result.model.cluster_labels), + "has_condition_aggregation": aggregate_conditions, + "region_order": region_table["region_id"].drop_duplicates().tolist(), + }, +} +``` + +Do not add new region statistics or new occupancy summaries here. + +- [ ] **Step 3: Run the focused tests to verify they pass** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_shared_cluster_region_matplotlib" -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit the region renderer** + +```bash +git add dimelo/plotting_matplotlib.py dimelo/plotting.py tests/test_plotting.py +git commit -m "feat: add shared clustering region renderer" +``` + +## Task 7: Add User-Facing Docs For Shared-Clustering Matplotlib Renderers + +**Files:** +- Modify: `README.md` +- Modify: `docs/shared-clustering.md` + +- [ ] **Step 1: Add a short README note for shared-clustering renderers** + +Add a short note near the existing Matplotlib renderer overview: + +```md +`shared_clustering` also supports built-in Matplotlib rendering through +`dimelo.plotting_matplotlib`, including cluster-distribution, profile, and +region-occupancy views on top of the prepared clustering payloads. +``` + +- [ ] **Step 2: Add short examples to `docs/shared-clustering.md`** + +Add examples like: + +```python +from dimelo import plotting, plotting_matplotlib + +distribution_payload = plotting.prepare_shared_cluster_distribution_data(result=result) +fig, ax = plotting_matplotlib.plot_shared_cluster_distribution_matplotlib(distribution_payload) +plotting_matplotlib.save_figure(fig, "shared-cluster-distribution.png") +``` + +```python +profile_payload = plotting.prepare_shared_cluster_profile_data(result=result) +fig, ax = plotting_matplotlib.plot_shared_cluster_profile_heatmap_matplotlib(profile_payload) +``` + +```python +region_payload = plotting.prepare_shared_cluster_region_data(result=result) +fig, ax = plotting_matplotlib.plot_shared_cluster_region_matplotlib( + region_payload, + level="condition", +) +``` + +Keep the docs explicit that: + +- condition-level region occupancy is the default rendering view +- sample-level occupancy is still available with `level="sample"` +- the older `result.plot_data` tables remain supported + +- [ ] **Step 3: Commit the docs** + +```bash +git add README.md docs/shared-clustering.md +git commit -m "docs: add shared clustering renderer examples" +``` + +## Task 8: Final Verification And Self-Review + +**Files:** +- Reference: `dimelo/plotting_matplotlib.py` +- Reference: `dimelo/plotting.py` +- Reference: `tests/test_plotting.py` +- Reference: `README.md` +- Reference: `docs/shared-clustering.md` + +- [ ] **Step 1: Run the full plotting coverage** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl XDG_CACHE_HOME=/tmp/xdg-cache PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q +``` + +Expected: PASS. + +- [ ] **Step 2: Run the workflow regression subset** + +Run: + +```bash +MPLCONFIGDIR=/tmp/mpl XDG_CACHE_HOME=/tmp/xdg-cache PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_workflows.py -q +``` + +Expected: PASS with no new failures. + +- [ ] **Step 3: Run a clean status check** + +Run: + +```bash +git status --short +``` + +Expected: clean working tree. + +- [ ] **Step 4: Self-review against the spec** + +Check the completed work against `docs/superpowers/specs/2026-04-10-shared-clustering-matplotlib-renderers-design.md`: + +- separate `shared_clustering` Matplotlib renderers exist in `dimelo.plotting_matplotlib` +- renderers cover distribution/change, profiles, and region occupancy +- default region occupancy view is condition-level +- only minimal prep-layer changes, if any, were introduced +- docs describe the renderer layer as optional and payload-driven + +- [ ] **Step 5: Commit a small follow-up only if verification finds a real issue** + +```bash +git add dimelo/plotting_matplotlib.py dimelo/plotting.py tests/test_plotting.py README.md docs/shared-clustering.md +git commit -m "fix: tighten shared clustering renderer support" +``` + +Only make this commit if verification or self-review uncovers a small real follow-up patch. diff --git a/docs/superpowers/plans/2026-04-12-shared-cluster-tests.md b/docs/superpowers/plans/2026-04-12-shared-cluster-tests.md new file mode 100644 index 0000000..d80a560 --- /dev/null +++ b/docs/superpowers/plans/2026-04-12-shared-cluster-tests.md @@ -0,0 +1,783 @@ +# Shared Cluster Tests Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a new result-centric inference layer for global shared-cluster composition that consumes `SharedClusterResult`, reuses `ContrastSpec`, supports replicate-aware permutation testing plus optional pooled chi-squared / G-test screening, and returns contrast-style result tables plus plot-ready payloads. + +**Architecture:** Keep `workflows.shared_cluster_distribution(...)` descriptive and add a separate `dimelo.shared_cluster_tests` module. The new module should consume existing shared-clustering tables, derive sample-level evidence, and return a `SharedClusterContrastResult` with `summary`, `details`, optional `pairwise`, `plot_data`, and `metadata`. Region-level occupancy inference remains in `dimelo.region_contrasts`, while a small workflow tweak propagates `SampleSpec.metadata` into shared-cluster assignments so `ContrastSpec.pairing_key` has a reliable metadata source. + +**Tech Stack:** Python 3.11, pandas, numpy, scipy.stats, pytest, existing `dimelo.models`, `dimelo.workflows`, `dimelo.region_contrasts`, `dimelo.plotting`-style result conventions + +--- + +## File Map + +- Create: `dimelo/shared_cluster_tests.py` + - public `shared_cluster_tests(...)` + - contrast-mode validation + - sample-level evidence builders + - permutation, chi-squared, and G-test scoring helpers + - result assembly and `plot_data` +- Modify: `dimelo/models.py` + - add `SharedClusterContrastResult` +- Modify: `dimelo/__init__.py` + - export `shared_cluster_tests` +- Modify: `dimelo/workflows.py` + - propagate `SampleSpec.metadata` keys into shared-cluster assignment rows +- Create: `tests/test_shared_cluster_tests.py` + - focused inference-layer coverage +- Modify: `tests/test_models.py` + - result-model validation coverage +- Modify: `tests/test_workflows.py` + - workflow metadata propagation coverage for paired designs +- Modify: `README.md` + - mention `shared_cluster_tests(...)` and the boundary vs `region_contrasts` +- Modify: `docs/shared-clustering.md` + - add user-facing examples and inference-boundary guidance + +## Task 1: Add Failing Tests For The Result Model And Module Skeleton + +**Files:** +- Create: `tests/test_shared_cluster_tests.py` +- Modify: `tests/test_models.py` +- Reference: `dimelo/shared_cluster_tests.py` +- Reference: `dimelo/models.py` + +- [ ] **Step 1: Write the failing module-import and result-model tests** + +Add this near the existing result-model coverage in `tests/test_models.py`: + +```python +from dimelo.models import SharedClusterContrastResult + + +def test_shared_cluster_contrast_result_requires_summary_details_and_plot_data(): + with pytest.raises(ValueError, match="SharedClusterContrastResult requires non-None values"): + SharedClusterContrastResult( + summary=None, + details=pd.DataFrame(), + plot_data={}, + ) +``` + +Create `tests/test_shared_cluster_tests.py` with: + +```python +import pandas as pd + + +def test_shared_cluster_tests_module_exports_entry_point(): + from dimelo import shared_cluster_tests + + assert hasattr(shared_cluster_tests, "shared_cluster_tests") + + +def test_shared_cluster_tests_rejects_unsupported_contrast_mode(): + from dimelo import shared_cluster_tests + from dimelo.models import ContrastSpec + + result = _make_shared_cluster_test_result() + + with pytest.raises(NotImplementedError, match="background_adjusted"): + shared_cluster_tests.shared_cluster_tests( + result=result, + contrast=ContrastSpec( + mode="background_adjusted", + numerator=["treated"], + denominator=["NS"], + background=["bg"], + ), + ) +``` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_models.py tests/test_shared_cluster_tests.py -k "shared_cluster_contrast_result or shared_cluster_tests_module_exports_entry_point or shared_cluster_tests_rejects_unsupported_contrast_mode" -q +``` + +Expected: FAIL because `SharedClusterContrastResult` and `dimelo.shared_cluster_tests` do not yet exist. + +- [ ] **Step 3: Commit the failing tests** + +```bash +git add tests/test_models.py tests/test_shared_cluster_tests.py +git commit -m "test: add shared cluster tests skeleton coverage" +``` + +## Task 2: Add `SharedClusterContrastResult` And The Module Skeleton + +**Files:** +- Create: `dimelo/shared_cluster_tests.py` +- Modify: `dimelo/models.py` +- Modify: `dimelo/__init__.py` +- Modify: `tests/test_models.py` +- Modify: `tests/test_shared_cluster_tests.py` + +- [ ] **Step 1: Add the new result model** + +Add this dataclass in `dimelo/models.py` near the other result models: + +```python +@dataclass +class SharedClusterContrastResult: + summary: pd.DataFrame + details: pd.DataFrame + pairwise: pd.DataFrame | None = None + plot_data: dict[str, pd.DataFrame | dict[str, Any]] = field(default_factory=dict) + metadata: dict[str, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + required_fields = { + "summary": self.summary, + "details": self.details, + "plot_data": self.plot_data, + "metadata": self.metadata, + } + missing = [name for name, value in required_fields.items() if value is None] + if missing: + raise ValueError( + "SharedClusterContrastResult requires non-None values for: " + f"{', '.join(missing)}" + ) +``` + +- [ ] **Step 2: Create the module skeleton and export it** + +Create `dimelo/shared_cluster_tests.py` with: + +```python +from __future__ import annotations + +from dimelo.models import ContrastSpec, SharedClusterContrastResult + + +def _require_supported_shared_cluster_mode(contrast: ContrastSpec) -> None: + supported = {"pairwise", "matched_pairwise", "group_vs_group", "time_course"} + if contrast.mode not in supported: + raise NotImplementedError( + f"Shared cluster tests are not implemented for contrast mode '{contrast.mode}'." + ) + + +def shared_cluster_tests( + *, + result, + contrast: ContrastSpec, + test: str = "permutation", + multiple_testing: str = "fdr_bh", + n_permutations: int = 1000, + random_state: int | None = 42, + include_pairwise: bool = False, +) -> SharedClusterContrastResult: + _require_supported_shared_cluster_mode(contrast) + raise NotImplementedError("shared_cluster_tests is not implemented yet.") +``` + +Update `dimelo/__init__.py`: + +```python +from . import ( + ... + shared_cluster_tests, + ... +) + +__all__ = [ + ... + "shared_cluster_tests", + ... +] +``` + +- [ ] **Step 3: Run the focused tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_models.py tests/test_shared_cluster_tests.py -k "shared_cluster_contrast_result or shared_cluster_tests_module_exports_entry_point or shared_cluster_tests_rejects_unsupported_contrast_mode" -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit the skeleton** + +```bash +git add dimelo/models.py dimelo/shared_cluster_tests.py dimelo/__init__.py tests/test_models.py tests/test_shared_cluster_tests.py +git commit -m "feat: add shared cluster tests module skeleton" +``` + +## Task 3: Add Failing Tests For Pairing-Metadata Propagation From Workflows + +**Files:** +- Modify: `tests/test_workflows.py` +- Modify: `dimelo/workflows.py` +- Reference: `dimelo/shared_cluster_tests.py` + +- [ ] **Step 1: Write the failing workflow metadata propagation test** + +Add this near the existing shared-clustering workflow tests in `tests/test_workflows.py`: + +```python +def test_shared_cluster_distribution_propagates_sample_metadata_into_assignments(monkeypatch): + monkeypatch.setattr(workflows.cluster, "extract_read_windows", _fake_extract_result) + monkeypatch.setattr(workflows.cluster, "read_window_feature_matrix", _fake_feature_matrix) + monkeypatch.setattr(workflows.cluster, "cluster_read_windows", _fake_cluster_fit_result) + + result = workflows.shared_cluster_distribution( + samples=[ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"subject_id": "mouse-1"}, + ), + SampleSpec( + sample_id="s2", + condition="treated", + extract_h5="s2.h5", + metadata={"subject_id": "mouse-2"}, + ), + ], + mode="read_global", + motifs=["A,0"], + n_clusters=2, + ) + + assert "subject_id" in result.assignments.columns + assert set(result.assignments["subject_id"]) == {"mouse-1", "mouse-2"} +``` + +- [ ] **Step 2: Run the focused test to verify it fails** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_workflows.py -k "propagates_sample_metadata_into_assignments" -q +``` + +Expected: FAIL because `SampleSpec.metadata` is not yet propagated into shared-cluster assignments. + +- [ ] **Step 3: Implement the minimal workflow tweak** + +Update the metadata-row construction in `dimelo/workflows.py`: + +```python + for metadata in extracted.metadata: + row = { + "sample_id": sample.sample_id, + "condition": sample.condition, + "replicate": sample.replicate, + } + if sample.metadata: + row.update(sample.metadata) + row.update(metadata) + metadata_rows.append(row) +``` + +For the region-anchored path, add the same metadata merge before +`_build_shared_cluster_result(...)` by enriching `metadata_rows` by `sample_id`. + +- [ ] **Step 4: Run the focused test to verify it passes** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_workflows.py -k "propagates_sample_metadata_into_assignments" -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit the workflow metadata support** + +```bash +git add dimelo/workflows.py tests/test_workflows.py +git commit -m "feat: propagate shared cluster sample metadata" +``` + +## Task 4: Add Failing Tests For Pairwise And Group Permutation Inference + +**Files:** +- Modify: `tests/test_shared_cluster_tests.py` +- Reference: `dimelo/shared_cluster_tests.py` + +- [ ] **Step 1: Add a compact shared-cluster inference fixture** + +Add this helper in `tests/test_shared_cluster_tests.py`: + +```python +def _make_shared_cluster_test_result() -> SharedClusterResult: + return SharedClusterResult( + model=SharedClusterModel( + mode="read_global", + motifs=["A,0"], + feature_names=["f0", "f1"], + preprocessing={"signal_normalization": "none"}, + estimator=object(), + cluster_labels=["C0", "C1"], + fit_metadata={"clusterer": "minibatch_kmeans", "n_clusters": 2}, + ), + assignments=pd.DataFrame( + { + "sample_id": ["s1", "s2", "s3", "s4"], + "condition": ["NS", "NS", "treated", "treated"], + "subject_id": ["p1", "p2", "p1", "p2"], + "cluster": ["C0", "C0", "C1", "C1"], + } + ), + cluster_distribution=pd.DataFrame( + [ + {"sample_id": "s1", "condition": "NS", "cluster": "C0", "count": 80, "fraction": 0.80}, + {"sample_id": "s1", "condition": "NS", "cluster": "C1", "count": 20, "fraction": 0.20}, + {"sample_id": "s2", "condition": "NS", "cluster": "C0", "count": 75, "fraction": 0.75}, + {"sample_id": "s2", "condition": "NS", "cluster": "C1", "count": 25, "fraction": 0.25}, + {"sample_id": "s3", "condition": "treated", "cluster": "C0", "count": 30, "fraction": 0.30}, + {"sample_id": "s3", "condition": "treated", "cluster": "C1", "count": 70, "fraction": 0.70}, + {"sample_id": "s4", "condition": "treated", "cluster": "C0", "count": 25, "fraction": 0.25}, + {"sample_id": "s4", "condition": "treated", "cluster": "C1", "count": 75, "fraction": 0.75}, + ] + ), + condition_distribution=pd.DataFrame( + [ + {"condition": "NS", "cluster": "C0", "count": 155, "fraction": 0.775, "replicate_n": 2}, + {"condition": "NS", "cluster": "C1", "count": 45, "fraction": 0.225, "replicate_n": 2}, + {"condition": "treated", "cluster": "C0", "count": 55, "fraction": 0.275, "replicate_n": 2}, + {"condition": "treated", "cluster": "C1", "count": 145, "fraction": 0.725, "replicate_n": 2}, + ] + ), + distribution_change=None, + cluster_profiles=pd.DataFrame(columns=["cluster", "count", "f0", "f1"]), + region_summaries=None, + plot_data={}, + metadata={}, + ) +``` + +- [ ] **Step 2: Write failing pairwise and matched-pairwise tests** + +Add these tests: + +```python +def test_shared_cluster_tests_pairwise_returns_summary_details_and_plot_data(): + from dimelo import shared_cluster_tests + from dimelo.models import ContrastSpec + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_test_result(), + contrast=ContrastSpec(mode="pairwise", numerator=["treated"], denominator=["NS"]), + test="permutation", + n_permutations=50, + random_state=7, + ) + + assert set(result.summary.columns) >= { + "contrast_id", "composition_effect_size", "omnibus_p_value", "top_cluster" + } + assert set(result.details.columns) >= { + "cluster", "fraction", "reference_fraction", "delta_fraction", "p_value", "adjusted_p_value" + } + assert set(result.plot_data) >= {"summary_table", "cluster_effect_table"} + + +def test_shared_cluster_tests_matched_pairwise_uses_contrast_pairing_key(): + from dimelo import shared_cluster_tests + from dimelo.models import ContrastSpec + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_test_result(), + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["treated"], + denominator=["NS"], + pairing_key="subject_id", + ), + test="permutation", + n_permutations=50, + random_state=7, + ) + + assert result.metadata["paired"] is True + assert result.metadata["pairing_key"] == "subject_id" +``` + +- [ ] **Step 3: Run the focused tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_shared_cluster_tests.py -k "pairwise_returns_summary_details_and_plot_data or matched_pairwise_uses_contrast_pairing_key" -q +``` + +Expected: FAIL because `shared_cluster_tests(...)` is still a stub. + +- [ ] **Step 4: Implement pairwise/group permutation helpers** + +In `dimelo/shared_cluster_tests.py`, add the following minimal helper set: + +```python +def _sample_fraction_table(result) -> pd.DataFrame: + required = {"sample_id", "condition", "cluster", "fraction"} + missing = required - set(result.cluster_distribution.columns) + if missing: + raise ValueError(f"Shared cluster tests require cluster_distribution columns: {', '.join(sorted(missing))}.") + return result.cluster_distribution.loc[:, ["sample_id", "condition", "cluster", "fraction"]].copy() + + +def _cluster_order(result) -> list[str]: + return list(result.model.cluster_labels) + + +def _composition_effect_size(summary_table: pd.DataFrame) -> float: + return float(summary_table["delta_fraction"].abs().sum() / 2.0) +``` + +Then implement the pairwise/group path to: + +- build sample-level cluster fraction evidence +- compute condition means +- compute per-cluster `fraction`, `reference_fraction`, `delta_fraction`, `log2_fc` +- compute omnibus statistic from the L1 / total-variation shift +- compute permutation p-values +- add BH-adjusted per-cluster p-values +- assemble `summary`, `details`, `plot_data`, and `metadata` + +- [ ] **Step 5: Run the focused tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_shared_cluster_tests.py -k "pairwise_returns_summary_details_and_plot_data or matched_pairwise_uses_contrast_pairing_key" -q +``` + +Expected: PASS. + +- [ ] **Step 6: Commit the pairwise/group inference path** + +```bash +git add dimelo/shared_cluster_tests.py tests/test_shared_cluster_tests.py +git commit -m "feat: add shared cluster pairwise inference" +``` + +## Task 5: Add Failing Tests For Pooled Screening Tests + +**Files:** +- Modify: `tests/test_shared_cluster_tests.py` +- Reference: `dimelo/shared_cluster_tests.py` + +- [ ] **Step 1: Write failing chi-squared and G-test coverage** + +Add: + +```python +def test_shared_cluster_tests_supports_chi_squared_screen(): + from dimelo import shared_cluster_tests + from dimelo.models import ContrastSpec + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_test_result(), + contrast=ContrastSpec(mode="pairwise", numerator=["treated"], denominator=["NS"]), + test="chi_squared", + ) + + assert result.metadata["inference_level"] == "pooled_screen" + assert result.summary.loc[0, "test"] == "chi_squared" + + +def test_shared_cluster_tests_supports_g_test_screen(): + from dimelo import shared_cluster_tests + from dimelo.models import ContrastSpec + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_test_result(), + contrast=ContrastSpec(mode="pairwise", numerator=["treated"], denominator=["NS"]), + test="g_test", + ) + + assert result.summary.loc[0, "test"] == "g_test" + assert 0.0 <= result.summary.loc[0, "omnibus_p_value"] <= 1.0 +``` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_shared_cluster_tests.py -k "chi_squared_screen or g_test_screen" -q +``` + +Expected: FAIL because pooled screening tests are not yet implemented. + +- [ ] **Step 3: Implement pooled count screening** + +Add helpers in `dimelo/shared_cluster_tests.py`: + +```python +def _pooled_count_table(result, contrast: ContrastSpec) -> pd.DataFrame: + table = result.cluster_distribution.loc[:, ["sample_id", "condition", "cluster", "count"]].copy() + return table +``` + +Then implement: + +- pooled numerator and denominator cluster-count vectors +- `scipy.stats.chi2_contingency(...)` for `chi_squared` +- `scipy.stats.power_divergence(..., lambda_="log-likelihood")` or equivalent for `g_test` +- summary row with pooled-screen metadata +- per-cluster descriptive `details` rows without replicate-aware p-values + +- [ ] **Step 4: Run the focused tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_shared_cluster_tests.py -k "chi_squared_screen or g_test_screen" -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit the pooled screening path** + +```bash +git add dimelo/shared_cluster_tests.py tests/test_shared_cluster_tests.py +git commit -m "feat: add shared cluster pooled screening tests" +``` + +## Task 6: Add Failing Tests For Time-Course Inference + +**Files:** +- Modify: `tests/test_shared_cluster_tests.py` +- Reference: `dimelo/shared_cluster_tests.py` + +- [ ] **Step 1: Add a compact time-course fixture and failing tests** + +Add: + +```python +def _make_shared_cluster_time_course_result() -> SharedClusterResult: + result = _make_shared_cluster_test_result() + result.cluster_distribution = pd.DataFrame( + [ + {"sample_id": "t0_a", "condition": "t0", "cluster": "C0", "count": 80, "fraction": 0.80}, + {"sample_id": "t0_a", "condition": "t0", "cluster": "C1", "count": 20, "fraction": 0.20}, + {"sample_id": "t1_a", "condition": "t1", "cluster": "C0", "count": 55, "fraction": 0.55}, + {"sample_id": "t1_a", "condition": "t1", "cluster": "C1", "count": 45, "fraction": 0.45}, + {"sample_id": "t2_a", "condition": "t2", "cluster": "C0", "count": 25, "fraction": 0.25}, + {"sample_id": "t2_a", "condition": "t2", "cluster": "C1", "count": 75, "fraction": 0.75}, + ] + ) + return result + + +def test_shared_cluster_tests_time_course_returns_omnibus_and_trend_outputs(): + from dimelo import shared_cluster_tests + from dimelo.models import ContrastSpec + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_time_course_result(), + contrast=ContrastSpec(mode="time_course", time_order=["t0", "t1", "t2"]), + test="permutation", + n_permutations=50, + random_state=7, + ) + + assert {"omnibus_p_value", "trend_p_value", "composition_effect_size"} <= set(result.summary.columns) + assert "time_course_table" in result.plot_data + + +def test_shared_cluster_tests_time_course_optional_pairwise_follow_up(): + from dimelo import shared_cluster_tests + from dimelo.models import ContrastSpec + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_time_course_result(), + contrast=ContrastSpec(mode="time_course", time_order=["t0", "t1", "t2"]), + test="permutation", + include_pairwise=True, + n_permutations=20, + random_state=7, + ) + + assert result.pairwise is not None +``` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_shared_cluster_tests.py -k "time_course_returns_omnibus_and_trend_outputs or time_course_optional_pairwise_follow_up" -q +``` + +Expected: FAIL because time-course inference is not yet implemented. + +- [ ] **Step 3: Implement time-course support** + +In `dimelo/shared_cluster_tests.py`, add: + +- a time-order validator +- an omnibus permutation statistic across timepoints +- a trend statistic using ordered time indices +- optional pairwise follow-up expansion when `include_pairwise=True` + +Use helper signatures like: + +```python +def _require_time_order_present(table: pd.DataFrame, time_order: list[str]) -> None: + ... + + +def _score_time_course( + *, + sample_table: pd.DataFrame, + contrast: ContrastSpec, + n_permutations: int, + random_state: int | None, + include_pairwise: bool, +) -> SharedClusterContrastResult: + ... +``` + +- [ ] **Step 4: Run the focused tests to verify they pass** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_shared_cluster_tests.py -k "time_course_returns_omnibus_and_trend_outputs or time_course_optional_pairwise_follow_up" -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit the time-course path** + +```bash +git add dimelo/shared_cluster_tests.py tests/test_shared_cluster_tests.py +git commit -m "feat: add shared cluster time course inference" +``` + +## Task 7: Add User-Facing Docs + +**Files:** +- Modify: `README.md` +- Modify: `docs/shared-clustering.md` + +- [ ] **Step 1: Add a short README note** + +Add near the existing shared-clustering analysis guide text: + +```md +Global shared-cluster composition inference is available through +`dimelo.shared_cluster_tests.shared_cluster_tests(...)`, which consumes a +`SharedClusterResult` plus a `ContrastSpec`. Per-region occupancy inference +continues to live in `region_contrasts.score_regions(..., analysis_unit="cluster_occupancy")`. +``` + +- [ ] **Step 2: Add shared-clustering guide examples** + +Add examples in `docs/shared-clustering.md`: + +```python +from dimelo import shared_cluster_tests +from dimelo.models import ContrastSpec + +contrast_result = shared_cluster_tests.shared_cluster_tests( + result=result, + contrast=ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + ), +) +``` + +And a paired example: + +```python +contrast_result = shared_cluster_tests.shared_cluster_tests( + result=result, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["treated"], + denominator=["NS"], + pairing_key="subject_id", + ), +) +``` + +Also add boundary text that: + +- `shared_cluster_tests(...)` is for global composition +- `region_contrasts.score_regions(...)` remains the route for region occupancy +- pooled chi-squared / G-test is screening-oriented + +- [ ] **Step 3: Commit the docs** + +```bash +git add README.md docs/shared-clustering.md +git commit -m "docs: add shared cluster tests guide" +``` + +## Task 8: Final Verification And Self-Review + +**Files:** +- Reference: `dimelo/shared_cluster_tests.py` +- Reference: `dimelo/models.py` +- Reference: `dimelo/workflows.py` +- Reference: `tests/test_models.py` +- Reference: `tests/test_shared_cluster_tests.py` +- Reference: `tests/test_workflows.py` +- Reference: `README.md` +- Reference: `docs/shared-clustering.md` + +- [ ] **Step 1: Run the focused shared-cluster inference suite** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_shared_cluster_tests.py -q +``` + +Expected: PASS. + +- [ ] **Step 2: Run the model and workflow regression subset** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_models.py tests/test_workflows.py -k "shared_cluster_contrast_result or shared_cluster_distribution" -q +``` + +Expected: PASS. + +- [ ] **Step 3: Run a clean status check** + +Run: + +```bash +git status --short +``` + +Expected: clean working tree. + +- [ ] **Step 4: Self-review against the spec** + +Confirm explicitly that: + +- `shared_cluster_distribution(...)` remains descriptive +- `shared_cluster_tests(...)` consumes `SharedClusterResult` +- `ContrastSpec` is reused rather than replaced +- replicate-aware permutation is the default path +- pooled chi-squared / G-test is explicit and labeled as screening +- pairwise, matched pairwise, group-vs-group, and time-course are covered +- `region_contrasts` remains the occupancy-inference route +- `plot_data` exists for both summary and per-cluster follow-up + +- [ ] **Step 5: Commit a small follow-up only if verification finds a real issue** + +```bash +git add dimelo/shared_cluster_tests.py dimelo/models.py dimelo/workflows.py tests/test_models.py tests/test_shared_cluster_tests.py tests/test_workflows.py README.md docs/shared-clustering.md +git commit -m "fix: tighten shared cluster tests support" +``` + +Only make this commit if final verification or self-review uncovers a concrete follow-up issue. diff --git a/docs/superpowers/plans/2026-04-13-legacy-cleanup-and-shared-cluster-tests-execution.md b/docs/superpowers/plans/2026-04-13-legacy-cleanup-and-shared-cluster-tests-execution.md new file mode 100644 index 0000000..d1e8996 --- /dev/null +++ b/docs/superpowers/plans/2026-04-13-legacy-cleanup-and-shared-cluster-tests-execution.md @@ -0,0 +1,344 @@ +# Shared Cluster Tests Completion And Legacy Cleanup Execution Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Finish the active shared-cluster inference slice, then execute a prioritized legacy TODO cleanup program without derailing current delivery. + +**Architecture:** Treat shared-cluster inference completion as Sprint 0 (feature lane), then run legacy cleanup in Sprints 1-5 (debt lane). Keep commits narrow and test-gated. Prefer no cross-sprint mixed commits. + +**Tech Stack:** Python 3.11, pytest, pandas, numpy, scipy, dimelo workflows/models/plotting stack + +--- + +## Status Note (2026-04-14) + +This document is retained as the historical sprint breakdown. The canonical +"what remains" backlog is now tracked in +`docs/superpowers/plans/2026-04-14-unified-remaining-work-plan.md`. + +## How Current Track Fits + +- Sprint 0 is the current active branch work: + - `dimelo/shared_cluster_tests.py` + - `tests/test_shared_cluster_tests.py` + - `README.md` + - `docs/shared-clustering.md` + - `docs/superpowers/README.md` +- Sprints 1-5 are legacy debt cleanup outside the new analysis track. +- Recommended flow: complete Sprint 0 first, then run Sprints 1-5 in order. + +## File Map + +- Modify: `dimelo/shared_cluster_tests.py` +- Modify: `tests/test_shared_cluster_tests.py` +- Modify: `docs/shared-clustering.md` +- Modify: `README.md` +- Modify: `docs/superpowers/README.md` +- Modify: `dimelo/parse_bam.py` +- Modify: `dimelo/run_modkit.py` +- Modify: `dimelo/load_processed.py` +- Modify: `dimelo/plot_read_browser.py` +- Modify: `dimelo/plot_reads.py` +- Modify: `dimelo/plot_enrichment.py` +- Modify: `dimelo/plot_enrichment_profile.py` +- Modify: `dimelo/plot_depth_profile.py` +- Modify: `dimelo/plot_depth_histogram.py` +- Modify: `dimelo/utils.py` +- Modify: `dimelo/test_data.py` +- Modify: `dimelo/test/__init__.py` +- Modify: `dimelo/test/dimelo_test.py` + +## Sprint 0: Complete Shared Cluster Tests (Current Track) + +**Files:** +- Modify: `tests/test_shared_cluster_tests.py` +- Modify: `dimelo/shared_cluster_tests.py` +- Modify: `docs/shared-clustering.md` +- Modify: `README.md` +- Modify: `docs/superpowers/README.md` + +- [ ] **Step 1: Add failing tests for missing scope (time-course + pooled tests + include_pairwise)** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_shared_cluster_tests.py -k "time_course or chi_squared or g_test or include_pairwise" -q +``` + +Expected: FAIL on currently missing behavior. + +- [ ] **Step 2: Implement pooled screening paths (`chi_squared`, `g_test`)** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_shared_cluster_tests.py -k "chi_squared or g_test" -q +``` + +Expected: PASS for pooled tests. + +- [ ] **Step 3: Implement `time_course` and `include_pairwise` follow-up** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_shared_cluster_tests.py -k "time_course or include_pairwise" -q +``` + +Expected: PASS for time-course behavior. + +- [ ] **Step 4: Update docs and status map** + +Update: +- `docs/shared-clustering.md` (global shared-cluster tests usage and boundaries) +- `README.md` (analysis guide note for `shared_cluster_tests`) +- `docs/superpowers/README.md` (status line updates) + +- [ ] **Step 5: Run shared-cluster regression subset** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_shared_cluster_tests.py tests/test_models.py tests/test_workflows.py -k "shared_cluster" -q +``` + +Expected: PASS. + +- [ ] **Step 6: Commit Sprint 0** + +```bash +git add dimelo/shared_cluster_tests.py tests/test_shared_cluster_tests.py docs/shared-clustering.md README.md docs/superpowers/README.md +git commit -m "feat: complete shared cluster inference v1 scope" +``` + +## Sprint 1: Parser Correctness And Duplication + +**Files:** +- Modify: `dimelo/parse_bam.py` +- Modify: `dimelo/run_modkit.py` +- Test: `tests/test_distribution.py` +- Test: `tests/test_cluster.py` + +- [ ] **Step 1: Identify parser TODO targets and lock scope** + +Run: + +```bash +rg -n "TODO|FIXME|TBD" dimelo/parse_bam.py dimelo/run_modkit.py +``` + +Expected: Enumerated TODO set for this sprint only. + +- [ ] **Step 2: Refactor duplicate pileup/extract setup paths** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_distribution.py -q +``` + +Expected: PASS with no behavior regression. + +- [ ] **Step 3: Harden coordinate/chunk/compression paths** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_cluster.py tests/test_distribution.py -q +``` + +Expected: PASS. + +- [ ] **Step 4: Clean `run_modkit.py` typing TODO and run focused regression** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_distribution.py tests/test_cluster.py -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit Sprint 1** + +```bash +git add dimelo/parse_bam.py dimelo/run_modkit.py tests/test_distribution.py tests/test_cluster.py +git commit -m "refactor: harden parse_bam core paths and reduce duplication" +``` + +## Sprint 2: Processed Loader Reliability + +**Files:** +- Modify: `dimelo/load_processed.py` +- Test: `tests/test_distribution.py` +- Test: `tests/test_workflows.py` + +- [ ] **Step 1: Triage loader TODOs and group by behavior** + +Run: + +```bash +rg -n "TODO|FIXME|TBD" dimelo/load_processed.py +``` + +Expected: TODO set grouped into naming, progress/parallelization, subsetting semantics. + +- [ ] **Step 2: Clean naming/API clarity and keep compatibility** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_distribution.py -q +``` + +Expected: PASS. + +- [ ] **Step 3: Add progress/parallel behavior parity and subsetting fixes** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_distribution.py tests/test_workflows.py -k "load or processed" -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit Sprint 2** + +```bash +git add dimelo/load_processed.py tests/test_distribution.py tests/test_workflows.py +git commit -m "fix: improve load_processed reliability and API clarity" +``` + +## Sprint 3: Read Browser Behavior And Performance + +**Files:** +- Modify: `dimelo/plot_read_browser.py` +- Test: `tests/test_plotting.py` + +- [ ] **Step 1: Resolve collapse/sort semantics TODOs** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "read_browser" -q +``` + +Expected: PASS. + +- [ ] **Step 2: Stabilize duplicate-read handling and hover contract** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "read_browser or plot_reads" -q +``` + +Expected: PASS. + +- [ ] **Step 3: Reduce hot-path iteration overhead where safe** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q +``` + +Expected: PASS. + +- [ ] **Step 4: Commit Sprint 3** + +```bash +git add dimelo/plot_read_browser.py tests/test_plotting.py +git commit -m "fix: harden read browser behavior and clean plotting debt" +``` + +## Sprint 4: Legacy Plotting API Normalization + +**Files:** +- Modify: `dimelo/plot_reads.py` +- Modify: `dimelo/plot_enrichment.py` +- Modify: `dimelo/plot_enrichment_profile.py` +- Modify: `dimelo/plot_depth_profile.py` +- Modify: `dimelo/plot_depth_histogram.py` +- Test: `tests/test_plotting.py` + +- [ ] **Step 1: Normalize dispatch/API patterns across legacy plotting modules** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -k "plot_reads or enrichment or depth" -q +``` + +Expected: PASS. + +- [ ] **Step 2: Remove redefinition/mypy TODO clusters without changing output behavior** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q +``` + +Expected: PASS. + +- [ ] **Step 3: Commit Sprint 4** + +```bash +git add dimelo/plot_reads.py dimelo/plot_enrichment.py dimelo/plot_enrichment_profile.py dimelo/plot_depth_profile.py dimelo/plot_depth_histogram.py tests/test_plotting.py +git commit -m "refactor: normalize legacy plotting APIs and remove type-debt hotspots" +``` + +## Sprint 5: Utils And Fixture Hygiene + +**Files:** +- Modify: `dimelo/utils.py` +- Modify: `dimelo/test_data.py` +- Modify: `dimelo/test/__init__.py` +- Modify: `dimelo/test/dimelo_test.py` + +- [ ] **Step 1: Resolve utility reproducibility/type TODOs** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_distribution.py tests/test_cluster.py -q +``` + +Expected: PASS. + +- [ ] **Step 2: Improve fixture realism and remove internal test TODOs** + +Run: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest -q +``` + +Expected: PASS. + +- [ ] **Step 3: Commit Sprint 5** + +```bash +git add dimelo/utils.py dimelo/test_data.py dimelo/test/__init__.py dimelo/test/dimelo_test.py +git commit -m "chore: improve utility typing/reproducibility and test fixtures" +``` + +## Cross-Sprint Gates + +- [ ] `git status --short` is clean before each sprint commit. +- [ ] No mixed-sprint commits (one sprint theme per commit). +- [ ] No new `TODO/FIXME/TBD` markers introduced. +- [ ] Full regression at sprint boundaries: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest -q +``` + +## Success Metrics + +- Baseline markers: `96` TODO/FIXME/TBD in `dimelo/*.py`. +- Post Sprint 0 target: current feature lane complete and documented. +- Post Sprint 3 target: TODO count < `35`. +- Post Sprint 5 target: TODO count < `10`, with any remainder explicitly deferred in docs. diff --git a/docs/superpowers/plans/2026-04-13-legacy-cleanup-integration.md b/docs/superpowers/plans/2026-04-13-legacy-cleanup-integration.md new file mode 100644 index 0000000..30d39b1 --- /dev/null +++ b/docs/superpowers/plans/2026-04-13-legacy-cleanup-integration.md @@ -0,0 +1,92 @@ +# Legacy Cleanup Integration + +**Date:** 2026-04-13 +**Branch:** `codex/legacy-cleanup` +**Range for this cleanup track:** `8baefd2..HEAD` + +## Goal + +Package the parser/loader legacy-cleanup sprint into reviewable PR slices with clear risk boundaries and a repeatable verification matrix. + +## Scope Summary + +Track-level diff (`8baefd2..HEAD`): + +- `13 files changed` +- `1755 insertions(+), 697 deletions(-)` +- Core files touched: + - `dimelo/parse_bam.py` + - `dimelo/load_processed.py` + - `dimelo/run_modkit.py` + - `dimelo/utils.py` + - `dimelo/plot_*.py` (debt-only cleanup) + - parser/loader-focused tests + +## What Landed + +### Parser lane + +- Clarified and stabilized threshold semantics in extract conversion. +- Fixed extract coordinate reconstruction and added explicit bounds validation. +- Consolidated chunk/dataset write flow and CSV row parsing in `read_by_base_txt_to_hdf5`. +- Simplified parser helper contracts (`prep_output_directory`, path coercion, thread-command handling). +- Removed stale TODO debt and repeated cleanup code paths. + +### Loader lane + +- Hardened subset contract validation (`dict` only, must include `n`/`frac`, reject `array`). +- Fixed empty-region behavior and empty-subset safety. +- Disambiguated duplicate read names across overlapping regions. +- Added optional parallel paths for region selection and readwise processing. +- Added loader progress wiring and public alias helpers: + - `counts_from_pileup` + - `vectors_from_pileup` + +### Plotting/util lane + +- Cleanup-only removal of stale TODO/type-reassignment debt in plotting helpers. +- No intentional behavior changes in plotting outputs. + +### Hardening tests lane + +- Added parser guardrail tests: + - invalid strand row raises + - non-positive read length raises + - out-of-bounds `pos_in_read` raises +- Added loader contract tests: + - `subset_parameters` non-dict rejection + - tuple-form `sort_by` path normalization/ordering + +## Verification Matrix + +All commands below passed on `codex/legacy-cleanup` after lane completion: + +1. `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 /Users/ngamarra/.pyenv/versions/3.11.3/bin/python3.11 -m pytest tests/test_parse_bam.py tests/test_parse_bam_vectors.py tests/test_utils_regions.py -q` + `25 passed` +2. `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 /Users/ngamarra/.pyenv/versions/3.11.3/bin/python3.11 -m pytest tests/test_distribution.py tests/test_cluster.py -q` + `20 passed, 2 warnings` (existing `tight_layout` and joblib core-detection warnings) +3. `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 /Users/ngamarra/.pyenv/versions/3.11.3/bin/python3.11 -m pytest tests/test_region_contrasts.py -k "read_mod_fraction" -q` + `19 passed, 65 deselected` +4. `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 /Users/ngamarra/.pyenv/versions/3.11.3/bin/python3.11 -m pytest tests/test_workflows.py -k "read_mod_fraction or shared_cluster" -q` + `14 passed, 18 deselected, 1 warning` (existing joblib core-detection warning) + +## PR Split Recommendation + +Split this cleanup track into **3 thematic PRs** (recommended) to keep review focused: + +1. **PR A: Parser correctness + parser refactor** + - `parse_bam.py`, `run_modkit.py`, parser tests, `utils.py` parser-adjacent fixes. + - Include coordinate and threshold semantics changes with their tests. +2. **PR B: Loader contract + parallelization + aliases** + - `load_processed.py`, loader tests, `test_load_processed_aliases.py`. + - Keep API alias additions with loader behavior fixes and parallel parity tests. +3. **PR C: Plotting debt cleanup** + - `plot_depth_histogram.py`, `plot_depth_profile.py`, `plot_enrichment.py`, `plot_enrichment_profile.py`, `plot_reads.py`. + - Pure cleanup/debt removal; no functional change intent. + +Hardening tests can remain with PR A/PR B where they logically belong (preferred), rather than a separate PR. + +## Integration Decision + +Proceed with the 3-PR split above. +If reviewer throughput is constrained, fallback to a 2-PR split by combining PR C into PR B. diff --git a/docs/superpowers/plans/2026-04-13-sprint1-parser-kickoff.md b/docs/superpowers/plans/2026-04-13-sprint1-parser-kickoff.md new file mode 100644 index 0000000..1454eae --- /dev/null +++ b/docs/superpowers/plans/2026-04-13-sprint1-parser-kickoff.md @@ -0,0 +1,77 @@ +# Sprint 1 Parser Kickoff + +**Goal:** Start the legacy cleanup sprint by inventorying parser debt in `dimelo/parse_bam.py` and `dimelo/run_modkit.py`, then move the highest-risk parser work into small, testable fixes. + +**Scope:** This kickoff only covers the legacy parser lane in the `codex/legacy-cleanup` worktree. It does not touch the main workspace checkout. + +--- + +## TODO Inventory + +I found `31` `TODO` markers in the two files in scope. + +### Correctness-Risk + +- `dimelo/parse_bam.py:802` - thresholding behavior is still ambiguous; the comment asks whether the method should threshold without binarization. +- `dimelo/parse_bam.py:979` - vector length is derived from `max(valid_coordinates_list) + 1`; the comment notes this should switch to `read_end - read_start`, which would change output padding semantics and requires reference regeneration. +- `dimelo/parse_bam.py:1049` - the comment explicitly asks to verify that read position is in the correct reference coordinate system. +- `dimelo/parse_bam.py:1055` - read start/end logic is still a placeholder for future true modkit-provided coordinates. +- `dimelo/parse_bam.py:1063` - same placeholder logic for read end calculation. + +### API/Design Cleanup + +- `dimelo/parse_bam.py:83` - `regions` is still a TODO in the public docstring. +- `dimelo/parse_bam.py:111` - typed-path handling has a cluster of mypy issues that need a coherent fix. +- `dimelo/parse_bam.py:157` - cleanup naming and flow are confusing; the comment calls out different usage between cleanup and extract. +- `dimelo/parse_bam.py:191` - region-specifier construction should be a method or merged into prep logic. +- `dimelo/parse_bam.py:210` - extract and pileup diverge mainly on printing vs. raising, and the comment suggests unifying that behavior. +- `dimelo/parse_bam.py:245` - question about whether this method should store and use its output. +- `dimelo/parse_bam.py:332` - `regions` is still a TODO in the extract docstring. +- `dimelo/parse_bam.py:360` - second cluster of path/type issues in the extract path. +- `dimelo/parse_bam.py:386` - intermediate mod-specific `.txt` files are still an open API/design question. +- `dimelo/parse_bam.py:480` - same question about whether the progress-run output needs to be retained. +- `dimelo/parse_bam.py:703` - the region-command builder may need to be split into conversion plus command construction. +- `dimelo/parse_bam.py:745` - the HDF5 file layout still needs a clearer documented key/value map. +- `dimelo/parse_bam.py:769` - second cluster of type/path issues in the HDF5 conversion path. +- `dimelo/parse_bam.py:1146` - `input_file` may be the wrong abstraction for this helper’s defaulting behavior. +- `dimelo/run_modkit.py:128` - the type annotation on `finding_progress_dict` is still called out as uncertain. + +### Code-Structure Cleanup + +- `dimelo/parse_bam.py:273` - cleanup could be centralized instead of duplicated. +- `dimelo/parse_bam.py:782` - the function calls around the HDF5 write path can likely be consolidated. +- `dimelo/parse_bam.py:783` - both files may be opened together instead of separately. +- `dimelo/parse_bam.py:817` - repeated dataset handling could loop over a dict. +- `dimelo/parse_bam.py:903` - repeated dataset handling could loop over a dict here too. +- `dimelo/parse_bam.py:935` - read-name initialization could move out of the hot loop. +- `dimelo/parse_bam.py:952` - chunk accounting should probably use `read_counter % chunk_size`. +- `dimelo/parse_bam.py:969` - CSV parsing should use the `csv` module. +- `dimelo/parse_bam.py:999` - gzip compression for vectors should be factored into a shared helper. +- `dimelo/parse_bam.py:1085` - there is another consolidation opportunity in the final write path. +- `dimelo/parse_bam.py:1106` - gzip compression should be shared across the write code path. + +## Sprint 1 Execution Order + +1. Lock down the correctness-risk items first with targeted tests around coordinate handling, vector length, and threshold semantics in `dimelo/parse_bam.py`. +2. Triage the public API/design debt next by clarifying the `regions` docstrings, path typing, and helper responsibilities in the parser and `run_modkit.py`. +3. Consolidate the repeated HDF5 write-path structure only after the behavior is pinned down by tests. +4. Re-run the parser-focused regression subset after each slice so the cleanup stays incremental instead of turning into a rewrite. + +## Baseline Test + +- Command: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 /Users/ngamarra/.pyenv/versions/3.11.3/bin/python3.11 -m pytest tests/test_distribution.py tests/test_cluster.py -q +``` + +- Result: + +```text +20 passed, 2 warnings in 10.79s +``` + +The warnings were non-fatal: + +- `tests/test_cluster.py::test_plot_region_cluster_profiles_motif_index` emitted a `tight_layout` warning from `dimelo/cluster.py:1865`. +- `tests/test_cluster.py::test_cluster_read_windows_kmeans` emitted the usual `joblib` physical-core detection warning. diff --git a/docs/superpowers/plans/2026-04-14-unified-remaining-work-plan.md b/docs/superpowers/plans/2026-04-14-unified-remaining-work-plan.md new file mode 100644 index 0000000..4da2a24 --- /dev/null +++ b/docs/superpowers/plans/2026-04-14-unified-remaining-work-plan.md @@ -0,0 +1,118 @@ +# Unified Remaining Work Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans. This plan consolidates outstanding work across historical `docs/superpowers/plans/*.md`. + +**Goal:** Maintain one canonical backlog of what is still open after auditing all historical plan docs, and execute remaining work in a clean, commit-friendly sequence. + +**Audit Date:** 2026-04-14 + +## Audited Plan Inventory + +The table below captures what remains from every tracked plan document. + +| Plan | Status | Remaining Work | +|---|---|---| +| `2026-03-31-global-analysis-foundations.md` | implemented | none | +| `2026-03-31-region-contrasts-foundations.md` | implemented | none | +| `2026-03-31-region-discovery-foundations.md` | implemented | none | +| `2026-03-31-shared-clustering-foundations.md` | implemented | none | +| `2026-04-01-cluster-occupancy-region-contrasts.md` | implemented | none | +| `2026-04-01-discovery-cluster-contrast-workflow.md` | implemented | none | +| `2026-04-01-paired-region-discovery.md` | implemented | none | +| `2026-04-01-plotting-axis-architecture.md` | implemented | none | +| `2026-04-01-region-contrasts-plotting.md` | implemented | none | +| `2026-04-01-region-discovery-cluster-workflow.md` | implemented | none | +| `2026-04-02-docs-and-integration-cleanup.md` | implemented | none | +| `2026-04-02-docs-coherence-and-discoverability.md` | partially implemented | keep status index synchronized with newly added plans and run periodic coherence checks | +| `2026-04-02-global-analysis-plotting.md` | implemented | none | +| `2026-04-02-region-discovery-plotting.md` | implemented | none | +| `2026-04-02-shared-clustering-plotting.md` | implemented | none | +| `2026-04-02-superpowers-docs-cleanup.md` | implemented | none | +| `2026-04-03-single-read-region-contrasts.md` | implemented | none | +| `2026-04-08-matplotlib-renderers.md` | implemented | none | +| `2026-04-10-shared-clustering-matplotlib-renderers.md` | implemented | none | +| `2026-04-12-shared-cluster-tests.md` | implemented | none | +| `2026-04-13-legacy-cleanup-and-shared-cluster-tests-execution.md` | partially implemented | finish legacy cleanup sprints and close commit-hygiene gates | + +## Canonical Remaining Backlog + +Only two workstreams remain active after the full audit: + +1. Legacy cleanup execution (from `2026-04-13-*`) +2. Ongoing docs coherence/index maintenance (from `2026-04-02-docs-coherence-*`) + +## Workstream A: Legacy Cleanup Completion + +**Scope Source:** `2026-04-13-legacy-cleanup-and-shared-cluster-tests-execution.md` + +### A1. Finish Sprint 1 leftovers (parser/runtime debt) + +- [ ] `run_modkit.py`: resolve remaining typing and API clarity debt. +- [ ] `parse_bam.py`: close remaining high-value TODO clusters that are still behavior-adjacent, not cosmetic. +- [ ] Verification: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_distribution.py tests/test_cluster.py -q +``` + +### A2. Finish Sprint 4 (legacy plotting normalization) + +- [ ] Normalize dispatch/API patterns in: + - `dimelo/plot_reads.py` + - `dimelo/plot_enrichment.py` + - `dimelo/plot_enrichment_profile.py` + - `dimelo/plot_depth_profile.py` + - `dimelo/plot_depth_histogram.py` +- [ ] Remove redefinition/mypy-TODO hotspots without output behavior drift. +- [ ] Verification: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_plotting.py -q +``` + +### A3. Finish Sprint 5 (utils + legacy fixture hygiene) + +- [ ] `utils.py`: close reproducibility/type TODOs that affect public behavior or determinism. +- [ ] `dimelo/test_data.py`, `dimelo/test/__init__.py`, `dimelo/test/dimelo_test.py`: + - reduce external fragility (network/download assumptions), + - improve restricted-environment compatibility (process/semaphore constraints), + - keep legacy integration coverage meaningful. +- [ ] Verification: + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3.11 -m pytest tests/test_distribution.py tests/test_cluster.py -q +``` + +## Workstream B: Docs Coherence Maintenance + +**Scope Source:** `2026-04-02-docs-coherence-and-discoverability.md` + +- [ ] Keep `docs/superpowers/README.md` in sync with all files in `docs/superpowers/plans/`. +- [ ] Keep status labels aligned with branch reality for the active cycle. +- [ ] Run link-existence verification after index edits: + +```bash +for f in $(rg -o "\\((specs|plans)/[^)]+" docs/superpowers/README.md | tr -d "("); do + test -f "docs/superpowers/$f" || echo "MISSING $f" +done +``` + +## Execution Order Going Forward + +1. Finish Workstream A1, commit. +2. Finish Workstream A2, commit. +3. Finish Workstream A3, commit. +4. Apply Workstream B coherence pass, commit. +5. Run final verification suite for the branch: + +```bash +pytest tests +``` + +## Commit Hygiene Rules (Canonical) + +- [ ] Keep one sprint/theme per commit. +- [ ] Keep `git status --short` clean between sprint commits. +- [ ] Do not introduce new `TODO/FIXME/TBD` markers in touched files. +- [ ] Record verification command output in PR notes before merge. + diff --git a/docs/superpowers/specs/2026-03-31-pre-plan-decisions-addendum.md b/docs/superpowers/specs/2026-03-31-pre-plan-decisions-addendum.md new file mode 100644 index 0000000..2c2a6e2 --- /dev/null +++ b/docs/superpowers/specs/2026-03-31-pre-plan-decisions-addendum.md @@ -0,0 +1,186 @@ +# Pre-Plan Decisions Addendum + +## Purpose + +This addendum resolves the main remaining policy gaps in the shared clustering and region-analysis specs before writing the implementation plan. + +It should be read alongside: + +- `2026-03-31-shared-cluster-distribution-design.md` +- `2026-03-31-region-analysis-architecture-design.md` + +## Decisions + +### 1. Replicate Handling For V1 + +Formal inferential `region_contrasts` in v1 should be limited to: + +- `analysis_unit="ensemble_region"` +- `signal_source="pileup_counts"` +- `test="beta_binomial"` + +Replicate policy in v1: + +- `single_dataset` + no replicate modeling needed +- `pairwise` + pool counts within each side after recording replicate-level summaries +- `group_vs_group` + pool counts within each side after recording replicate-level summaries +- `matched_pairwise` + allowed in schema, but v1 should expose effect-size summaries only unless replicate pairing can be handled with a clearly defined paired-count procedure +- `background_adjusted` + allowed in schema, but inferential testing should initially operate on pooled background-adjusted summaries only if the correction step is explicitly defined and audited +- `time_course` + effect-size-only in v1, not formal significance testing + +Rationale: + +- this preserves a clean, defensible inferential path for v1 +- it avoids pretending that pooled beta-binomial inference is valid for all replicate structures +- it leaves room for later GLM or hierarchical modeling without blocking the initial release + +### 2. Authoritative V1 Support Matrix + +The implementation plan should treat the following combinations as officially supported in v1: + +#### Fully Supported + +- `region_contrasts` + - `analysis_unit="ensemble_region"` + - `representation in {"modified_fraction", "modified_count"}` + - `signal_source="pileup_counts"` + - `test in {"beta_binomial", "effect_size_only"}` + +- `shared_cluster_distribution` + - `mode in {"read_global", "region_anchored"}` + - `clusterer="minibatch_kmeans"` + - `signal_normalization in {"none", "per_sample_global", "control_regions"}` + - `feature_scaling in {"none", "robust_zscore"}` + - `cluster_basis in {"shape_only", "shape_plus_level", "level_only"}` + +#### Schema-Supported But Exploratory-Only + +- `region_contrasts` + - `analysis_unit="single_read"` + - `analysis_unit="cluster_occupancy"` + - `signal_source in {"region_features", "cluster_occupancy"}` + - `time_course` with inferential testing + +These combinations may return tables or effect sizes in v1, but should not promise formal significance testing unless explicitly implemented. + +### 3. Artifact Compatibility Rules + +Every cached artifact must carry a compatibility fingerprint. + +Required metadata: + +- artifact schema version +- package version +- source file path(s) +- source file digest(s) or timestamp-plus-size fingerprint +- relevant parameter hash +- upstream artifact lineage if the artifact was derived from another artifact + +Workflow-level compatibility checks must include: + +- normalization mode +- feature scaling mode +- cluster basis +- motifs +- window size +- region source or region digest when applicable + +Rule: + +- if any required compatibility field mismatches, `prefer_cached` must rebuild rather than reuse + +### 4. `region_discovery` Search-Space Policy For V1 + +V1 discovery should use a simple, explicit tiled-window model. + +Defaults: + +- canonical contigs only by default +- user-configurable include/exclude contig filters +- fixed `window_size` +- fixed `step_size` +- minimum coverage threshold before scoring +- FDR computed on raw windows before merging +- adjacent significant windows merged after scoring using a fixed merge-distance rule + +Rationale: + +- easy to document +- easy to test +- avoids hiding discovery behavior in heuristic candidate generation + +This can be extended later with adaptive candidate windows, but the first implementation should stay deterministic. + +### 5. Normalization Ownership + +Normalization factors should have one canonical home: + +- `global_analysis` owns computation of reusable normalization diagnostics and factors +- downstream workflows may compute a factor on demand only if no compatible normalization artifact exists + +Workflow rule: + +- prefer canonical normalization artifacts when present +- if absent, compute on demand and record that the factor was rebuilt locally + +This keeps normalization behavior consistent across workflows while preserving the fallback model. + +### 6. User Guidance For `pileup` Versus `extract` + +The docs and examples should include one explicit recommendation table: + +- run `pileup` when you care about: + - motif abundance + - locus-level defined-region contrasts + - de novo region discovery + - broad global summaries + +- run `extract` when you care about: + - single-read pattern analysis + - shared read clustering + - read-state occupancy summaries + +- run both when you want: + - formal locus-level abundance testing plus downstream read-level structural follow-up + - discovery or contrast first, then clustering on selected loci + +V1 should not introduce a new required preprocessing abstraction; it should instead document the decision clearly and allow later wrappers to remain additive. + +### 7. Plotting Flexibility Policy + +Plotting should be data-first across workflows. + +Required rule: + +- workflows return canonical result tables first +- workflows may also return plot-ready tables or lightweight plot specs +- built-in figure generation should default to Matplotlib for backward familiarity +- users must be able to ignore built-in plotting and use returned tables with their own plotting stack + +V1 should not attempt a full backend plugin system. Instead it should: + +- preserve simple Matplotlib-based renderers +- expose stable `plot_data` payloads where useful +- keep renderer-specific logic out of the analysis core + +## Recommended Implementation Consequences + +The implementation plan should: + +1. treat pooled beta-binomial testing as the only fully inferential defined-region engine in v1 +2. include an explicit support-matrix section rather than leaving legal combinations implicit +3. build artifact compatibility checks before broad cache reuse +4. keep `region_discovery` deterministic and tiled in v1 +5. make normalization artifacts reusable across workflows +6. include user-facing guidance for when to run `pileup`, `extract`, or both +7. make plotting data-first with Matplotlib as the default compatibility renderer + +## Summary + +These decisions keep v1 narrow where statistical claims matter, while still letting the architecture expose broader future-facing schema for single-read and occupancy-based contrasts. They also reduce the risk of cache misuse, inconsistent normalization, and user confusion about how preprocessing connects to downstream analysis. diff --git a/docs/superpowers/specs/2026-03-31-region-analysis-architecture-design.md b/docs/superpowers/specs/2026-03-31-region-analysis-architecture-design.md new file mode 100644 index 0000000..17d2734 --- /dev/null +++ b/docs/superpowers/specs/2026-03-31-region-analysis-architecture-design.md @@ -0,0 +1,751 @@ +# Region Analysis Architecture Design + +## Summary + +Add a coherent downstream analysis architecture that separates: + +- preprocessing and artifact materialization in `parse_bam` +- genome-scale summarization in `global_analysis` +- de novo locus finding in `region_discovery` +- defined-region comparison in `region_contrasts` +- deeper structure analysis in `cluster` + +The goal is to preserve the current region-targeted analysis logic while making it easier to support both broad global analysis and explicit defined-region comparison workflows. + +## Goals + +- Keep `parse_bam` as the preprocessing entry point rather than replacing it. +- Preserve the current logic that supports region-targeted downstream analysis. +- Add a global-analysis layer for broad dataset-wide summaries and normalization support. +- Reserve `region_discovery` for de novo locus finding when the user does not yet know which regions matter. +- Add `region_contrasts` for significance testing and effect-size analysis over predefined regions. +- Make it explicit what each comparison is run on: + which datasets are compared, + what the analysis unit is, + and what read or region representation is being contrasted. +- Integrate cleanly with shared clustering workflows and pileup-backed summaries. +- Keep plotting flexible and modern by making returned tables canonical, while preserving simple Matplotlib-based renderers for continuity. +- Preserve continuity with prior versions by leaving the current under-the-hood parsing architecture substantially intact and layering new region-analysis modules on top. + +## Non-Goals + +- Collapse all downstream analysis into one giant generic module. +- Hide biological interpretation behind vague names like "region test" or "single-read comparison". +- Replace existing pileup/extract logic with one monolithic artifact type. +- Implement every possible statistical test in the first version. +- Rewrite the current low-level parsing layer unless the change is behavior-preserving and continuity-safe. + +## Architectural Split + +### `dimelo.parse_bam` + +Remains the preprocessing and artifact-materialization layer. + +Responsibilities: + +- run `modkit`-backed pileup generation +- run read-level extraction +- validate inputs +- emit reusable outputs that downstream workflows can consume + +User model: + +- preprocessing/materialization lives here +- downstream interpretation does not + +## Compatibility And Continuity Requirements + +The updated region-analysis architecture must preserve the current parsing model. + +Required constraints: + +- keep `parse_bam.pileup()` and `parse_bam.extract()` as the low-level preprocessing surface +- do not substantially change the under-the-hood modkit-backed parsing logic unless the behavior is preserved +- build `global_analysis`, `region_discovery`, and `region_contrasts` on top of current parsed outputs +- prefer additive orchestration and metadata layers rather than replacing current parse output semantics +- ensure old region-targeted analysis habits still map naturally onto the new modules + +User-facing consequence: + +- users who know the previous package should still recognize the preprocessing model +- new modules explain what to do after parsing rather than redefining what parsing means + +### `dimelo.global_analysis` + +New module for broad dataset-wide summarization. + +Responsibilities: + +- global accessibility or modification summaries +- chromosome- or window-level aggregated tracks +- QC summaries +- global shift metrics +- control-region normalization support +- generation of broad candidate tracks for de novo discovery + +This module is for asking: + +- what is happening globally in this dataset? +- do we see broad compaction or decompaction? +- are there obvious global technical shifts? + +### `dimelo.region_discovery` + +Reserved for de novo locus finding only. + +Responsibilities: + +- scan broad genomic inputs without requiring predefined regions +- tile the genome or candidate intervals +- compute discovery scores on windows +- merge or refine nearby hits +- export discovered loci as BED-like outputs + +This module is for asking: + +- where in the genome are the most interesting regions? +- which loci stand out within one dataset? +- which loci change strongly between matched datasets when regions were not known in advance? + +If the user already has a BED or defined region set, they should not use `region_discovery`. + +### `dimelo.region_contrasts` + +New module for defined-region comparison and testing. + +Responsibilities: + +- summarize signal over known regions +- compute effect sizes across matched datasets +- run region-level statistical tests such as beta-binomial +- support pairwise, grouped, background-adjusted, and time-course contrasts +- export ranked results for downstream region-targeted workflows + +This module is for asking: + +- how do these known loci differ across conditions? +- which predefined regions change significantly? +- are observed differences at known loci likely to reflect technical scaling or real biology? + +### `dimelo.cluster` + +Retains responsibility for structure analysis once reads or regions have been selected. + +Responsibilities: + +- shared-boundary clustering across datasets +- read-global and region-anchored clustering workflows +- cluster occupancy summaries +- integration with region-level summaries and contrasts + +This module is for asking: + +- what structure exists among reads or regions? +- do read states or region states reorganize across conditions? + +## User-Facing Flow + +The intended flow should be: + +1. `parse_bam` + materialize reusable preprocessing outputs +2. `global_analysis` + summarize genome-wide behavior and broad technical or biological shifts +3. `region_discovery` + discover candidate loci when regions are not known +4. `region_contrasts` + score and test known loci +5. `cluster` + perform deep structural analysis on reads or regions of interest + +This flow should be composable rather than mandatory. A user may enter at `region_contrasts` with an existing BED, or enter at `cluster` using a previously selected set of regions. + +## User Progression Through Processing And Analysis + +The package should make it obvious how a user proceeds from preprocessing to downstream analysis. + +### Common Starting Point + +All downstream workflows should begin from preprocessing outputs created by `parse_bam`. + +Conceptually: + +```python +from dimelo import parse_bam + +pileup_path, processed_regions = parse_bam.pileup(...) +extract_path, processed_regions = parse_bam.extract(...) +``` + +The user should not have to guess which downstream workflow consumes which preprocessing product: + +- use pileup-backed outputs for: + - global summaries + - defined-region abundance contrasts + - de novo region discovery scans +- use extract-backed outputs for: + - single-read pattern analysis + - read clustering + - cluster occupancy summaries + +### Typical Processing Paths + +#### Path A: Known Regions, Locus-Level Abundance Testing + +1. preprocess with pileup or load an existing pileup-backed artifact +2. run `region_contrasts.score_regions(...)` +3. review ranked or significant loci +4. optionally pass top regions into `cluster` + +#### Path B: Known Regions, Single-Read Or State-Level Analysis + +1. preprocess with extract or load an existing extract-backed artifact +2. run clustering or other read-level feature workflows +3. summarize cluster occupancy or read-state metrics by region +4. optionally score those outputs in `region_contrasts` + +#### Path C: Unknown Regions + +1. preprocess with pileup-backed outputs +2. run `region_discovery.scan_genome(...)` +3. export discovered intervals +4. score those intervals with `region_contrasts` +5. optionally perform deep clustering on selected regions + +This progression should be reflected in documentation and examples so the user sees the intended branching early. + +## Why `region_discovery` And `region_contrasts` Must Be Separate + +These are different scientific tasks: + +- discovery asks where signal or change exists +- contrasts ask what happens at loci already defined + +If they are collapsed together: + +- de novo scanning logic gets mixed with BED-based testing +- statistical assumptions become unclear +- it becomes hard to tell whether a result was discovered or tested on predefined loci + +The package should therefore reserve: + +- `region_discovery` for unknown loci +- `region_contrasts` for known loci + +## `region_contrasts` Core API + +### Primary Entry Point + +```python +result = region_contrasts.score_regions( + samples=samples, + regions=regions, + contrast=contrast_spec, + analysis_unit="ensemble_region", + representation="modified_fraction", + signal_source="pileup_counts", + test="beta_binomial", + multiple_testing="fdr_bh", +) +``` + +The API must make these dimensions explicit: + +1. `contrast` + which datasets or conditions are compared +2. `analysis_unit` + what the rows represent +3. `representation` + what property of those rows is being contrasted + +## Contrast Specification + +### `ContrastSpec` + +```python +@dataclass +class ContrastSpec: + mode: str + numerator: list[str] | None = None + denominator: list[str] | None = None + background: list[str] | None = None + time_order: list[str] | None = None + pairing_key: str | None = None + reference_condition: str | None = None + metadata: dict[str, Any] | None = None +``` + +### Supported Contrast Modes + +- `single_dataset` + rank regions within one dataset +- `pairwise` + compare two datasets directly +- `matched_pairwise` + compare matched samples using an explicit pairing key +- `group_vs_group` + compare multiple samples or conditions against another group +- `background_adjusted` + compare signal after accounting for matched background measurements +- `time_course` + compare ordered condition trajectories + +The same `ContrastSpec` structure should support all of these rather than fragmenting into unrelated function families. + +## Analysis Unit Model + +The package must be explicit about what observational level is being compared. + +### `analysis_unit="ensemble_region"` + +Rows represent aggregated region-level signal. + +Examples: + +- region-level modified counts +- region-level valid counts +- region-level modified fraction +- region-level enrichment summaries + +This is the default path for significance testing with beta-binomial models. + +### `analysis_unit="single_read"` + +Rows represent individual reads within predefined regions. + +Examples: + +- per-read modification fraction +- read-level density profile summaries +- read window features +- read span or length metrics + +This is for within-region distributional changes, not simple count-based testing. + +### `analysis_unit="cluster_occupancy"` + +Rows represent region-level summaries derived from clustered reads or clustered regions. + +Examples: + +- fraction of reads assigned to a cluster within a region +- dominant cluster fraction +- cluster entropy +- state-mixture proportions + +This is the bridge between clustering and region-level contrasts. + +## Representation Model + +Even within a given analysis unit, users need to know what property is being compared. + +### Ensemble-Region Representations + +- `modified_fraction` +- `modified_count` +- `coverage` +- `enrichment` +- `depth_profile_summary` + +### Single-Read Representations + +- `read_mod_fraction` +- `read_density_profile` +- `read_window_features` +- `read_shape` +- `read_length_or_span` +- `motif_specific_fraction` + +### Cluster-Occupancy Representations + +- `cluster_fraction` +- `dominant_cluster` +- `cluster_entropy` +- `state_mixture` + +## Why `analysis_unit` And `representation` Must Both Be Explicit + +Without these fields, a user cannot tell whether a result means: + +- the average region became more or less accessible +- the distribution of reads within the region changed +- the mixture of read states changed +- the local pattern shape changed while the mean remained constant + +These are biologically different conclusions and should never be hidden behind a generic label like "region contrast". + +## Biological Interpretation Of Contrasts + +The package must make it clear what biological question a contrast answers. + +### Reference-Matched Motif Abundance + +This asks: + +- at a defined locus, did the abundance of modification across reference-matched motif opportunities change? + +Interpretation: + +- aggregate motif-level modified and valid counts within a locus +- compare abundance across conditions or matched datasets + +Recommended configuration: + +- `analysis_unit="ensemble_region"` +- `representation="modified_fraction"` or `representation="modified_count"` +- `signal_source="pileup_counts"` +- `test="beta_binomial"` when formal significance testing is desired + +This is the primary count-based contrast path and should be the first-class default. + +### Single-Read Pattern Change + +This asks: + +- within a known locus, did the pattern of modification across individual reads change? + +Interpretation: + +- align and orient reads relative to a common reference frame +- compare per-read fractions, local densities, shapes, or feature vectors + +Recommended configuration: + +- `analysis_unit="single_read"` +- `representation="read_mod_fraction"`, `read_density_profile`, `read_window_features`, or `read_shape` +- extract-backed signal source + +This is not the same question as abundance testing, and beta-binomial should not be presented as the default test here. + +### Locus-Aggregated Structural Change + +This asks: + +- at a locus, did the mixture of read states or the aggregated structure change even if the mean abundance did not? + +Interpretation: + +- summarize read states or read clusters into region-level occupancy features +- compare mixture structure across conditions + +Recommended configuration: + +- `analysis_unit="cluster_occupancy"` +- `representation="cluster_fraction"`, `dominant_cluster`, or `cluster_entropy` +- clustering-derived signal source + +This is the main bridge from clustering into defined-region contrast analysis. + +### Required User Clarity + +Every result and exported table should make all of the following explicit: + +- which datasets or conditions were contrasted +- whether the contrast is abundance, single-read pattern, or state-mixture oriented +- what observational unit was used +- what representation was scored + +Users should never need to infer this from filenames or notebook context. + +## Signal Sources + +The first implementation should support multiple signal substrates with a clear default. + +### `signal_source="pileup_counts"` + +Primary default. + +Uses per-region modified and valid counts. + +This is the natural substrate for: + +- beta-binomial testing +- region-level effect sizes +- broad defined-region comparison + +### `signal_source="region_features"` + +Optional later mode. + +Uses region-level summaries derived from read-level artifacts or feature engineering. + +This is useful when pileup counts are not the right abstraction. + +### `signal_source="cluster_occupancy"` + +Integration mode with clustering workflows. + +Uses region-level cluster proportions or occupancy summaries as input to defined-region contrasts. + +## Statistical Engines + +### First-Pass Default + +Support `test="beta_binomial"` for: + +- `analysis_unit="ensemble_region"` +- `signal_source="pileup_counts"` + +This should be the first formal significance-testing path because it aligns with the current count-oriented architecture and the requested statistical direction. + +### Additional First-Version Mode + +Support `test="effect_size_only"` for fast exploratory use without p-values. + +### Planned But Not Required For V1 + +- distribution-shift tests for `single_read` +- occupancy/fraction tests for `cluster_occupancy` +- richer time-course inferential models + +These should be designed into the schema now, but not block the first implementation. + +## `region_discovery` Core API + +### Primary Entry Point + +```python +result = region_discovery.scan_genome( + samples=samples, + contrast=contrast_spec, + window_size=2000, + step_size=500, + signal_source="pileup_counts", + score="beta_binomial", + merge_hits=True, +) +``` + +`region_discovery` should: + +- generate candidate windows or intervals +- score them using a discovery-specific engine +- rank them +- optionally merge or refine neighboring hits +- export discovered intervals into BED-like outputs + +These outputs should be directly consumable by `region_contrasts` and `cluster`. + +## Shared Evidence Table + +Both `region_discovery` and `region_contrasts` should rely on a common internal region evidence table when possible. + +Example schema: + +```python +RegionEvidence +- region_id +- chrom +- start +- end +- sample_id +- condition +- replicate +- modified_count +- valid_count +- modified_fraction +- coverage +- analysis_unit +- representation +- signal_source +- metadata... +``` + +This shared evidence table keeps discovery and contrast workflows comparable without conflating their user-facing roles. + +## Integration With Clustering + +### `region_contrasts -> cluster` + +Defined-region contrasts should be able to export ranked or significant BEDs for downstream deep clustering analysis. + +Examples: + +- take top significant regions from beta-binomial testing +- cluster reads within those selected loci +- compare region-level cluster occupancy afterward + +### `cluster -> region_contrasts` + +Clustering workflows should be able to emit region-level occupancy artifacts that `region_contrasts` can score. + +Examples: + +- test whether cluster C3 occupancy differs between conditions at known regions +- compare cluster entropy across matched loci + +### `global_analysis -> region_discovery` + +Global summaries should provide coarse substrate for discovery: + +- broad tracks +- normalization factors +- window-level summaries + +### `region_discovery -> region_contrasts` + +Discovered loci should be easy to hand off into formal defined-region testing and ranking workflows. + +## Module Layout + +### `dimelo/global_analysis.py` + +Responsibilities: + +- broad summaries +- QC and global scaling diagnostics +- control-region normalization support + +### `dimelo/region_discovery.py` + +Responsibilities: + +- de novo scans +- candidate interval generation +- discovery scoring +- hit merging and refinement + +### `dimelo/region_contrasts.py` + +Responsibilities: + +- defined-region evidence building +- contrast specification handling +- beta-binomial and effect-size scoring +- result tables and export helpers + +### `dimelo/cluster.py` + +Responsibilities: + +- structure analysis +- cluster occupancy outputs +- integration with defined-region analyses + +## Output Contracts + +### `RegionContrastResult` + +```python +@dataclass +class RegionContrastResult: + regions: pd.DataFrame + summary: pd.DataFrame + contrast: ContrastSpec + plot_data: dict[str, pd.DataFrame | dict[str, Any]] + metadata: dict[str, Any] + figures: dict[str, Any] +``` + +The metadata must include: + +- contrast mode +- analysis unit +- representation +- signal source +- test +- normalization mode +- a short plain-language description of what the contrast means biologically +- renderer used for any built-in figures + +### `RegionDiscoveryResult` + +```python +@dataclass +class RegionDiscoveryResult: + hits: pd.DataFrame + windows: pd.DataFrame + contrast: ContrastSpec | None + plot_data: dict[str, pd.DataFrame | dict[str, Any]] + metadata: dict[str, Any] + figures: dict[str, Any] +``` + +The outputs should be easy to export as BEDs and easy to pass into follow-on workflows. + +### Plotting Contract + +Plotting should be layered: + +1. canonical result tables +2. plot-ready tables or lightweight plot specs +3. renderer-specific figure objects + +This keeps the analysis outputs stable while making plotting flexible. + +Default behavior: + +- built-in renderers use Matplotlib for continuity +- users can skip built-in plotting and work directly from `regions`, `summary`, `hits`, `windows`, or `plot_data` +- future renderer adapters can be added without changing the analysis APIs + +## Documentation Requirements For User Flow + +The user-facing docs should include one concise “how to proceed” table: + +- if you know regions and care about average motif abundance: use `region_contrasts` with pileup-backed inputs +- if you know regions and care about single-read structure: use extract-backed workflows and then optionally `region_contrasts` +- if you do not know regions: use `region_discovery` first +- if you want to compare state mixtures: run clustering first, then feed occupancy summaries into `region_contrasts` + +This guidance should appear in both the API docs and at least one tutorial notebook. + +## Testing Strategy + +### Unit Tests + +Add tests for: + +- `ContrastSpec` validation across supported modes +- legal and illegal combinations of `analysis_unit`, `representation`, `signal_source`, and `test` +- region evidence table construction +- effect-size-only ranking +- beta-binomial path over predefined region counts +- handoff from discovered regions into defined-region contrasts +- regression coverage proving continuity for pre-existing pileup/extract-driven region-targeted workflows + +### Integration Tests + +Add synthetic tests that verify: + +- a de novo scan can produce intervals that are then consumed by `region_contrasts` +- a clustering workflow can emit cluster occupancy summaries that `region_contrasts` can score +- metadata clearly records what was contrasted and at what observational level + +## Documentation Strategy + +Add: + +- one doc for global summaries and preprocessing handoff +- one doc for de novo region discovery +- one doc for defined-region contrasts +- one doc showing how discovered or contrasted regions feed into clustering +- one doc showing how to build custom plots from returned tables without using package renderers + +## Implementation Plan Shape + +The implementation should be split into small pieces: + +1. add `ContrastSpec` and result models +2. add `global_analysis` scaffolding +3. add `region_contrasts` evidence-building and effect-size workflows +4. add beta-binomial testing for `ensemble_region` plus `pileup_counts` +5. add `region_discovery` scanning and BED export +6. add clustering integration points +7. add tests +8. add docs/examples + +## Implementation Defaults + +These defaults are fixed for the first implementation: + +- `region_discovery` is for de novo loci only +- `region_contrasts` is for known regions only +- default `analysis_unit`: `ensemble_region` +- default `representation`: `modified_fraction` +- default `signal_source`: `pileup_counts` +- default `test`: `beta_binomial` when the combination is valid +- default plotting contract: canonical tables first, Matplotlib renderer second +- existing parsing entry points remain the continuity-preserving preprocessing layer + +This architecture should make it obvious what is being compared and why, while still supporting clean handoff into clustering and other downstream workflows. diff --git a/docs/superpowers/specs/2026-03-31-shared-cluster-distribution-design.md b/docs/superpowers/specs/2026-03-31-shared-cluster-distribution-design.md new file mode 100644 index 0000000..8d656d7 --- /dev/null +++ b/docs/superpowers/specs/2026-03-31-shared-cluster-distribution-design.md @@ -0,0 +1,801 @@ +# Shared Cluster Distribution Design + +## Summary + +Add a package-level workflow that defines a single set of clustering boundaries across multiple matched datasets, applies those boundaries consistently to every dataset, and reports how cluster occupancy changes across conditions. The workflow must support two related use cases: + +1. `read_global`: cluster reads pooled across datasets independent of locus, then project cluster assignments back onto matched regions. +2. `region_anchored`: build matched region-level feature rows across datasets, cluster those rows once, and compare condition-level shifts in region-cluster occupancy. + +The workflow should scale from thousands to millions of reads or regions per dataset, with roughly a dozen datasets per run. + +## Goals + +- Define cluster boundaries once and reuse them across all datasets in a run. +- Make the shared-boundary workflow available as a stable package API rather than notebook-only code. +- Preserve compatibility with existing `dimelo` extract outputs and existing feature-engineering code in `dimelo.cluster`. +- Support matched-region analyses while also allowing read clustering that is independent of region. +- Return structured tabular results suitable for downstream notebook analysis, plus a small standard plot set for quick interpretation. +- Keep plotting flexible by making tables the source of truth, shipping simple Matplotlib renderers by default, and not locking users into one plotting stack. +- Use a performant default that remains usable at million-row scale. +- Introduce a hybrid batch model that supports both independent dataset processing and flexible cohort-level shared workflows. +- Make downstream workflows consume reusable artifacts when available, while still being able to rebuild from raw inputs when necessary. +- Preserve continuity with prior package versions by keeping the existing low-level parsing layer stable and building new workflows on top of it. + +## Non-Goals + +- Reproduce every ad hoc plot or helper from the collaborator notebook as package API. +- Ship experiment-specific hard-coded condition schemes as core logic. +- Recluster separately for each condition or each region. +- Store large raw matrices on result objects by default. +- Rewrite or substantially alter the behavior of the existing parsing internals unless behavior-preserving refactors are required. + +## User-Facing Workflow + +### Primary Entry Point + +```python +from dimelo.models import SampleSpec +from dimelo import workflows + +samples = [ + SampleSpec( + sample_id="sample_1", + condition="NS", + extract_h5="path/to/sample_1.h5", + regions_bed="path/to/sample_1_regions.bed", + replicate=1, + ), + SampleSpec( + sample_id="sample_2", + condition="15min", + extract_h5="path/to/sample_2.h5", + regions_bed="path/to/sample_2_regions.bed", + replicate=1, + ), +] + +result = workflows.shared_cluster_distribution( + samples=samples, + mode="read_global", + motifs=["A,0"], + matched_regions=None, + signal_normalization="none", + feature_scaling="robust_zscore", + cluster_basis="shape_plus_level", + clusterer="minibatch_kmeans", + n_clusters=8, + training_sample_per_dataset=100_000, + random_state=42, + make_plots=True, +) +``` + +### Supported Modes + +#### `read_global` + +- Extract read-level windows and read-level feature matrices from every dataset. +- Build one balanced pooled training set across datasets. +- Fit one shared clustering model. +- Assign cluster labels to every read in every dataset. +- Optionally map those read labels back to matched regions for per-region occupancy summaries. + +#### `region_anchored` + +- Build one feature row per matched region per dataset from aggregated read-level metrics. +- Fit one shared clustering model across the pooled region rows. +- Assign cluster labels to every matched region row. +- Summarize cluster distribution shifts across conditions. + +## Recommended Default Algorithm + +### Default Strategy + +Use a balanced pooled reference with `MiniBatchKMeans`. + +1. Extract features separately per dataset. +2. Sample up to `training_sample_per_dataset` rows from each dataset. +3. Concatenate those samples into a pooled training matrix. +4. Fit one preprocessing pipeline on the pooled matrix. +5. Fit one shared clustering model on the pooled matrix. +6. Apply the frozen preprocessing and clustering model to the full datasets in chunks. +7. Summarize cluster distributions globally and by matched region. + +The preprocessing pipeline in this algorithm must distinguish between: + +- biological signal normalization before feature construction +- feature scaling after feature construction + +### Why This Default + +- More fair than anchoring cluster boundaries on one reference dataset. +- More performant than full pooled `KMeans` when rows scale into the millions. +- Simpler and more interpretable than per-region or per-condition reclustering. +- Produces fixed centroids, which makes prediction for full datasets cheap. + +### Alternatives Kept Open + +- `KMeans` for smaller runs. +- `GaussianMixture`, `Birch`, or `HDBSCAN` if later needed, but not as the initial default. +- Reference-dataset anchoring as an explicit option rather than the default path. + +## Architecture + +### Module Layout + +#### `dimelo/models.py` + +Owns the workflow dataclasses: + +- `SampleSpec` +- `SharedClusterModel` +- `SharedClusterResult` + +#### `dimelo/distribution.py` + +Owns generic summary logic: + +- counts per dataset and condition +- cluster fractions +- baseline deltas +- log2 fold changes +- replicate-aware aggregations + +#### `dimelo/region_analysis.py` + +Owns region-aware helpers: + +- matched-region validation +- read-to-region projection +- region-by-condition occupancy summaries +- matched region feature table construction for `region_anchored` + +#### `dimelo/workflows.py` + +Owns orchestration: + +- input validation +- dataset iteration +- balanced pooled training subset construction +- fit-once / assign-many execution +- standard plot creation +- result packaging +- artifact resolution for workflow inputs + +#### `dimelo/artifacts.py` + +Owns reusable dataset-level derived outputs and lookup helpers: + +- artifact key definitions +- artifact metadata and provenance +- cache hit / miss resolution +- compatibility checks between requested parameters and stored artifacts + +#### `dimelo/batch.py` + +Owns hybrid batch orchestration: + +- independent dataset processing jobs +- named cohort jobs +- workflow recipes +- batch manifests for downstream workflow execution + +#### `dimelo/cluster.py` + +Retains low-level primitives already present on the branch: + +- `extract_read_windows` +- `read_window_feature_matrix` +- `cluster_read_windows` +- generic cluster profile plotting +- generic region-cluster plotting and summaries + +The new workflow should call these lower-level functions rather than duplicate them. + +## Compatibility And Continuity Requirements + +The shared clustering work must be additive around the existing parsing architecture. + +Required constraints: + +- keep `parse_bam.pileup()` and `parse_bam.extract()` as the core preprocessing entry points +- keep existing modkit-backed parsing logic intact unless a change is behavior-preserving and covered by regression tests +- avoid breaking existing output concepts and file semantics +- prefer additive metadata or manifest layers over changing current parsed output formats +- ensure new workflow wrappers call into existing parsing outputs rather than replacing them + +User-facing consequence: + +- previous preprocessing habits should still work +- existing pileup or extract outputs should remain usable in the updated package +- users should learn new downstream workflows without needing to relearn the parsing layer + +## Data Model + +### `SampleSpec` + +```python +@dataclass +class SampleSpec: + sample_id: str + condition: str + extract_h5: str | Path + regions_bed: str | Path | None = None + replicate: int | None = None + metadata: dict[str, Any] | None = None +``` + +### `SharedClusterModel` + +```python +@dataclass +class SharedClusterModel: + mode: str + motifs: list[str] + feature_names: list[str] + preprocessing: dict[str, Any] + estimator: Any + cluster_labels: list[str] + fit_metadata: dict[str, Any] +``` + +### `SharedClusterResult` + +```python +@dataclass +class SharedClusterResult: + model: SharedClusterModel + assignments: pd.DataFrame + cluster_distribution: pd.DataFrame + condition_distribution: pd.DataFrame + distribution_change: pd.DataFrame | None + cluster_profiles: pd.DataFrame + region_summaries: pd.DataFrame | None + plot_data: dict[str, pd.DataFrame | dict[str, Any]] + figures: dict[str, Any] + metadata: dict[str, Any] +``` + +### `DatasetArtifact` + +```python +@dataclass +class DatasetArtifact: + sample_id: str + artifact_type: str + path: str | Path + format: str + params: dict[str, Any] + provenance: dict[str, Any] +``` + +### `CohortSpec` + +```python +@dataclass +class CohortSpec: + cohort_id: str + sample_ids: list[str] + workflow: str + params: dict[str, Any] + metadata: dict[str, Any] | None = None +``` + +### `BatchJob` + +```python +@dataclass +class BatchJob: + job_id: str + workflow: str + cohorts: list[CohortSpec] + artifact_policy: str = "prefer_cached" + metadata: dict[str, Any] | None = None +``` + +## Hybrid Batch Model + +The package should support two complementary batch concepts rather than forcing one batch unit everywhere. + +### Dataset-Centric Processing + +Each dataset can be processed independently into a standard artifact bundle. This is the high-throughput, cache-friendly layer. + +Examples of dataset-level artifacts: + +- QC summaries +- read-level feature tables +- region-mapping tables +- cluster-ready metadata tables +- cached transformed feature matrices + +This layer is best for: + +- parallel processing of many unrelated datasets +- retry and resume at per-dataset granularity +- feeding many downstream workflows from the same reusable intermediates + +### Cohort-Centric Workflows + +Named cohorts should define which datasets participate in a shared analysis recipe. + +Examples: + +- shared-boundary clustering over six matched samples +- region occupancy analysis over one subset +- a different recipe over an unrelated subset processed in parallel + +This layer is best for: + +- pooled fitting +- consistent cluster boundary definition +- any workflow where the group of samples matters scientifically + +### Recommended Hybrid Structure + +The system should use both layers: + +1. dataset processing produces reusable standardized artifacts +2. cohort workflows consume those artifacts to run shared analyses + +This preserves flexibility while avoiding repeated raw-input processing. + +## Artifact Resolution Policy + +Downstream workflows should prefer cached artifacts but fall back to raw inputs automatically when required artifacts are missing or incompatible. + +### Default Policy + +Use `artifact_policy="prefer_cached"` as the default. + +Resolution order: + +1. look for a compatible artifact +2. if found, load it +3. if missing or incompatible, rebuild from raw inputs +4. record whether the workflow used cache hits or rebuilt inputs + +### Additional Policies + +- `prefer_cached` + default pragmatic mode +- `require_cached` + fail if required artifacts are unavailable +- `rebuild` + ignore cached artifacts and regenerate needed intermediates + +### Provenance Requirements + +Every workflow result should record: + +- which artifacts were loaded from cache +- which artifacts were rebuilt +- the parameters used to determine compatibility +- the raw input paths used when fallback occurred + +This keeps the fallback behavior auditable rather than implicit. + +## Shared Workflow Input Contract + +Shared comparison workflows should consume artifact manifests or dataset specs, not notebook-era ad hoc dictionaries. + +### Accepted Input Forms + +1. `SampleSpec` objects with raw input paths +2. artifact manifests derived from those samples +3. `CohortSpec` objects that name a workflow and a set of samples + +The shared workflow should resolve the required inputs internally through the artifact layer. + +### Why This Contract Matters + +- the same sample can participate in multiple shared workflows +- expensive preprocessing is reused +- workflows remain runnable even when some artifacts are absent +- batch orchestration stays decoupled from notebook-specific path plumbing + +## Detailed Workflow Behavior + +## Normalization And Scaling Model + +The workflow must treat three concepts independently: + +1. biological signal normalization +2. feature scaling for model fitting +3. cluster basis selection + +These must not be collapsed into one generic "scale" option, because they answer different questions. + +### Biological Signal Normalization + +This operates on the raw or aggregated assay signal before clustering features are built. + +Supported modes: + +- `none` + Preserve absolute global shifts across datasets. + Use when broad changes in accessibility or compaction may be biologically real. + +- `per_sample_global` + Normalize each dataset by a global per-sample factor. + Useful when technical global shifts are suspected, but this can remove true biology. + +- `control_regions` + Normalize using designated control loci expected to be stable across conditions. + This is the preferred correction mode when suitable controls are available. + +- `quantile` + Force distributions into alignment across datasets. + This is a strong technical correction mode and should not be the default. + +### Feature Scaling + +This operates only on the feature matrix passed to PCA or clustering. It does not redefine the underlying biological signal in reported outputs. + +Supported modes: + +- `none` +- `zscore` +- `robust_zscore` + +Default: `robust_zscore` + +### Cluster Basis Selection + +This determines whether shared cluster boundaries should use absolute level, local pattern shape, or both. + +Supported modes: + +- `level_only` + Shared boundaries are driven primarily by absolute accessibility level. + This is sensitive to broad global shifts. + +- `shape_only` + Shared boundaries are driven by local pattern shape after removing row-level global offset. + This is useful when asking whether local patterns change independently of global level. + +- `shape_plus_level` + Use both shape-derived features and explicit global-level features. + This is the recommended default because it helps disentangle global compaction from local pattern reorganization. + +### Recommended Default Interpretation + +The first implementation should default to: + +- `signal_normalization="none"` +- `feature_scaling="robust_zscore"` +- `cluster_basis="shape_plus_level"` + +This preserves potential biological global shifts while still making the model numerically stable and keeping both local-pattern and global-level information available for clustering. + +### Comparison-Oriented Helper Workflow + +The package should eventually support a helper that runs the same shared-boundary workflow under multiple normalization or basis settings and compares the outputs. + +Example shape: + +```python +result = workflows.compare_shared_cluster_modes( + samples=samples, + modes=[ + { + "signal_normalization": "none", + "feature_scaling": "robust_zscore", + "cluster_basis": "shape_plus_level", + }, + { + "signal_normalization": "control_regions", + "feature_scaling": "robust_zscore", + "cluster_basis": "shape_plus_level", + }, + { + "signal_normalization": "none", + "feature_scaling": "robust_zscore", + "cluster_basis": "shape_only", + }, + ], +) +``` + +This comparison-oriented wrapper is not required for the first implementation, but the shared clustering workflow should be designed so that it can support this later without API breakage. + +### Shared Validation + +Before any feature extraction: + +- Require at least two datasets. +- Require `sample_id`, `condition`, and `extract_h5` for every sample. +- Require consistent `motifs` and compatible extracted vector lengths across datasets. +- In `region_anchored`, require resolvable matched-region inputs for every dataset. +- Record dataset sizes and rows remaining after filtering. +- Resolve required artifacts using the selected artifact policy before rebuilding from raw inputs. +- Validate `signal_normalization`, `feature_scaling`, and `cluster_basis` as independent parameters. +- If `signal_normalization="control_regions"`, require control-region inputs or a resolvable control-region artifact. + +### `read_global` Flow + +1. For each sample, load read windows from extract output using existing cluster helpers. +2. Apply biological signal normalization if requested. +3. Generate read-level feature rows. +4. Apply cluster-basis transformation and feature scaling. +5. Build a balanced pooled training subset across samples. +6. Fit preprocessing on the pooled subset: + - optional valid-site filtering + - feature scaling + - PCA or other dimensionality reduction +7. Fit `MiniBatchKMeans` on the pooled subset. +8. Transform and assign the full dataset for each sample in chunks. +9. Build a long-form assignments table with: + - `sample_id` + - `condition` + - `replicate` + - `cluster` + - read metadata columns + - optional region keys if present +10. Summarize: + - global cluster occupancy per sample + - cluster occupancy per condition + - read-cluster occupancy by region when matched-region keys are available + +### `region_anchored` Flow + +1. For each sample, project reads onto matched regions. +2. Apply biological signal normalization if requested. +3. Aggregate region-level metrics per matched region. +4. Build one aligned feature row per region per dataset. +5. Apply cluster-basis transformation and feature scaling. +6. Build a balanced pooled training subset across datasets. +7. Fit preprocessing and one shared clustering model. +8. Assign cluster labels to all region rows. +9. Summarize region-cluster occupancy shifts across conditions. + +## Performance Strategy + +### Core Rules + +- Fit on bounded pooled subsets, not on all rows. +- Use `MiniBatchKMeans` as the default estimator. +- Use chunked prediction for full assignment. +- Convert feature matrices to `float32` after extraction when precision permits. +- Avoid storing raw read-window matrices in the returned object unless explicitly requested. + +### Sampling Strategy + +The pooled training set should be balanced across datasets by default: + +- choose `min(dataset_size, training_sample_per_dataset)` rows per dataset +- concatenate sampled rows +- record actual sampled counts in result metadata + +Optional future extension: + +- stratified sampling by region or prior label if metadata is available + +### Scaling And Normalization Performance Notes + +- `signal_normalization="none"` should be the cheapest mode because it avoids extra correction passes. +- `per_sample_global` should remain lightweight because it only requires one global factor per dataset. +- `control_regions` will require extra region-level resolution work but is still acceptable if control-region inputs are cached as artifacts. +- `quantile` is likely the most expensive normalization mode and should not be used implicitly. +- `shape_only` can often be implemented as a per-row centering or offset-removal transform and should remain computationally cheap. + +## Outputs + +### `assignments` + +Long-form row-level output: + +- one row per read in `read_global` +- one row per region in `region_anchored` + +Minimum columns: + +- `sample_id` +- `condition` +- `replicate` +- `cluster` +- mode-specific identifiers + +### `cluster_distribution` + +Per-sample cluster summaries: + +- `sample_id` +- `condition` +- `cluster` +- `count` +- `fraction` + +### `condition_distribution` + +Condition-level aggregate summary: + +- `condition` +- `cluster` +- `count` +- `fraction` +- `replicate_n` + +### `distribution_change` + +Optional comparison against a reference condition: + +- `condition` +- `cluster` +- `fraction` +- `delta_fraction` +- `log2_fc` + +### `cluster_profiles` + +Interpretability table: + +- mean feature values per cluster +- optional mean profile vectors summarized into compact columns + +### `region_summaries` + +Only when region mapping is possible: + +- `region_id` +- `sample_id` +- `condition` +- `cluster` +- `count` +- `fraction` + +### `metadata` + +The top-level metadata payload should also include: + +- artifact policy used +- artifact cache hits and misses +- per-sample rebuild decisions +- cohort identifier when the workflow is cohort-driven +- signal normalization mode +- feature scaling mode +- cluster basis mode +- any per-sample normalization factors used + +### `plot_data` + +Plotting should be data-first. + +The result object should always expose plot-ready tables or spec-like payloads, even when no figure objects are requested. + +Examples: + +- `cluster_distribution_bar` +- `cluster_distribution_heatmap` +- `cluster_profiles` +- `top_region_changes` when available + +## Standard Plots + +The package should separate: + +1. analysis tables +2. plot-ready data or plot specs +3. renderer-specific figure generation + +Matplotlib should remain the default built-in renderer for continuity, but workflows should not require Matplotlib-specific figure handling in order to be useful. + +The initial standardized plot set should be small: + +1. stacked bar plot of cluster fraction by dataset +2. stacked bar plot of cluster fraction by condition +3. heatmap of cluster fraction or delta/log2FC across conditions +4. cluster profile plot for interpretation + +Optional later additions: + +- top-changing region heatmap +- baseline-vs-induction scatter plots +- preset study-specific figure suites + +### Plotting Architecture + +Recommended layers: + +- results tables + always returned, canonical source of truth +- plotting helpers + convert result tables into plot-ready payloads +- default renderers + Matplotlib-based functions that consume those payloads + +Consequences: + +- users familiar with older package versions can keep using simple Matplotlib outputs +- users with their own preferred plotting stack can ignore built-in renderers and plot directly from returned tables +- future renderer adapters such as Plotly or Altair can be added without changing analysis APIs + +## Error Handling + +The workflow should fail fast for structural issues: + +- incompatible motifs across datasets +- inconsistent extracted vector lengths +- missing matched-region metadata in `region_anchored` +- zero usable rows after filtering +- duplicate `sample_id` + +The workflow should warn but continue when possible for soft issues: + +- a dataset contributes fewer rows than requested to the training pool +- region mapping drops a substantial number of reads +- some clusters are absent from some conditions after assignment + +## Testing Strategy + +### Unit Tests + +Add new tests for: + +- balanced pooled sampling +- fit-once / assign-many workflow behavior +- condition distribution summaries +- region summary generation +- reference-condition delta calculations +- chunked assignment equivalence versus unchunked assignment +- artifact resolution under `prefer_cached`, `require_cached`, and `rebuild` +- cohort-driven execution that reuses dataset-level artifacts +- `shape_only`, `level_only`, and `shape_plus_level` feature-path behavior +- preservation of global shifts under `signal_normalization="none"` +- expected correction behavior for `per_sample_global` and `control_regions` +- regression coverage showing that pre-existing pileup/extract-driven workflows still work after the new shared clustering layer is added + +### Integration Tests + +Add small synthetic multi-sample fixtures that verify: + +- shared centroids are reused across samples +- `read_global` assignments can be projected back onto region summaries +- `region_anchored` produces stable per-condition cluster distributions +- cohort workflows can mix cache hits and raw-input fallbacks without changing result schema + +### Regression Tests + +Retain and extend the clustering tests already on the branch rather than reimplementing feature extraction from scratch. + +## Documentation Strategy + +Add: + +- one API-oriented doc for the workflow +- one example notebook for `read_global` +- one example notebook for `region_anchored` +- one short doc showing how to use returned tables with custom plotting code instead of package renderers + +The collaborator notebook should be preserved as exploratory history but should not remain the primary interface for this analysis. + +## Implementation Plan Shape + +The implementation should be split into small steps: + +1. add models +2. add artifact definitions and resolution helpers +3. add batch and cohort orchestration models +4. add distribution summaries +5. add region-aware summarization helpers +6. add workflow orchestration +7. add standardized plots +8. add tests +9. update docs/examples + +## Implementation Defaults + +These defaults are fixed for the first implementation: + +- default clusterer: `MiniBatchKMeans` +- default cluster-boundary source: balanced pooled subset across all datasets +- default result mode: return structured tables and figures, not raw matrices +- default plotting contract: return canonical tables plus plot-ready payloads, with Matplotlib as the compatibility renderer +- default artifact policy: `prefer_cached` +- default signal normalization: `none` +- default feature scaling: `robust_zscore` +- default cluster basis: `shape_plus_level` +- existing low-level parsing entry points remain the continuity-preserving preprocessing surface + +No experiment-specific cluster template system is required for the first version of this workflow. diff --git a/docs/superpowers/specs/2026-04-01-paired-region-discovery-design.md b/docs/superpowers/specs/2026-04-01-paired-region-discovery-design.md new file mode 100644 index 0000000..1186dc8 --- /dev/null +++ b/docs/superpowers/specs/2026-04-01-paired-region-discovery-design.md @@ -0,0 +1,370 @@ +# Paired Region Discovery Design + +## Goal + +Extend `dimelo.region_discovery` with paired discovery modes that support: + +- two-condition matched discovery +- ordered paired time-course discovery + +The paired framework should preserve the current pileup-backed, deterministic tiled-window architecture while making pairing explicit and auditable. + +## Why This Exists + +The current `region_discovery.scan_genome(...)` implementation only supports pooled `pairwise` and `group_vs_group` contrasts. That is adequate for simple discovery, but it is not correct for common experimental designs where samples are matched across conditions: + +- non-targeting versus targeting with matched replicates +- before/after on the same biological sample +- paired time-course trajectories across ordered conditions + +If these are analyzed as pooled groups, the pairing structure is discarded and the result can be misleading. The paired discovery layer should answer a different biological question: + +> Which windows change consistently within matched units? + +## Scope + +This design covers: + +- new paired discovery modes in `region_discovery` +- pairing resolution rules +- paired ranking metrics for two-condition and ordered time-course discovery +- result metadata and output-table requirements + +This design does not cover: + +- mixed-effects or repeated-measures inferential models +- single-read paired discovery +- cluster-occupancy paired discovery +- adaptive candidate interval generation + +## Architectural Rule + +Build one shared paired-discovery framework and expose multiple contrast modes on top of it. + +That means: + +- `matched_pairwise` is a two-condition special case +- paired ordered time-course is another consumer of the same pairing engine +- pairing resolution happens once +- per-window paired summaries are the canonical substrate + +This avoids creating separate, incompatible systems for paired comparisons and paired trajectories. + +## Core User Model + +The user still calls `region_discovery.scan_genome(...)`, but with a paired `ContrastSpec`. + +Examples: + +```python +ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="replicate_id", +) +``` + +```python +ContrastSpec( + mode="time_course", + time_order=["0min", "15min", "30min", "45min"], + pairing_key="sample_id", +) +``` + +The key principle is: + +- pooled modes compare condition groups +- paired modes compare within matched units first, then summarize across units + +## Pairing Resolution + +### Required Input + +Paired discovery requires an explicit `pairing_key`. + +The pairing key should identify the matched biological or technical unit across conditions, for example: + +- replicate identity +- donor identity +- sample identity +- before/after subject identity + +If the requested discovery mode is paired and `pairing_key` is missing, the workflow must fail fast. + +### Default Pairing Policy + +Default policy: + +- `pairing_policy="complete_pairs_only"` + +Behavior: + +- use only matched units that have all required conditions +- drop unmatched units from scoring +- record dropped units in metadata + +This is the most robust default because it preserves paired semantics without making imperfect datasets unusable. + +### Strict Option + +Support: + +- `pairing_policy="error_on_missing"` + +Behavior: + +- fail if any expected matched unit is incomplete + +This is useful for highly controlled analyses where partial data should be treated as an error. + +### Not In V1 + +Do not support a silent fallback from paired discovery to pooled discovery. That would blur the biological interpretation too much. + +## Internal Data Model + +Add a canonical internal paired table concept: + +```text +PairedWindowTable +- window_id +- chromosome +- start +- end +- strand +- pair_id +- condition +- modified_count +- valid_count +- window_fraction +``` + +For paired discovery: + +1. build the ordinary window summary table from `global_analysis.build_window_summary(...)` +2. resolve pair membership from sample metadata using `pairing_key` +3. keep only complete matched units under the active pairing policy +4. compute paired summaries from the per-pair window table + +This table is internal, but it should be conceptually stable because it will support both matched-pairwise and paired time-course modes. + +## Supported Paired Modes + +### `matched_pairwise` + +Use when there are exactly two condition sets to compare within matched pairs. + +Examples: + +- non-targeting versus targeting +- before versus after + +Required `ContrastSpec` fields: + +- `mode="matched_pairwise"` +- `numerator` +- `denominator` +- `pairing_key` + +### `time_course` + +Use when the same matched unit is measured across an ordered series of conditions. + +Examples: + +- `0min -> 15min -> 30min -> 45min` + +Required `ContrastSpec` fields: + +- `mode="time_course"` +- `time_order` +- `pairing_key` + +In the paired-discovery context, `time_course` means ordered paired time-course, not pooled trajectory analysis. + +## Ranking Families + +Two ranking families should be supported. + +### 1. Magnitude-First Ranking + +This emphasizes effect size. + +#### Matched Pairwise + +For each pair and each window: + +- `delta_i = numerator_fraction_i - denominator_fraction_i` + +Canonical summary columns: + +- `mean_delta` +- `mean_abs_delta` +- `median_abs_delta` +- `delta_sd` +- `sign_agreement` +- `n_pairs_used` + +Recommended default ranking: + +- `rank_by="mean_abs_delta"` + +This is the best first-pass default because it is simple, interpretable, and stable for small numbers of pairs. + +#### Paired Time Course + +For each pair and each window: + +- compute trajectory amplitude within that pair: + `trajectory_amplitude_i = max(fractions_i_over_time) - min(fractions_i_over_time)` + +Canonical summary columns: + +- `trajectory_amplitude_mean` +- `trajectory_amplitude_median` +- `trajectory_amplitude_sd` +- `n_pairs_used` + +Recommended default ranking: + +- `rank_by="trajectory_amplitude_mean"` + +This is the right first ranking mode because it is general and does not prematurely force windows into pattern classes. + +### 2. Consistency-Weighted Ranking + +This emphasizes reproducibility across matched units. + +#### Matched Pairwise + +Candidate summary metrics: + +- `consistency_weighted_delta = mean_abs_delta / (delta_sd + epsilon)` +- `mean_abs_delta * sign_agreement` + +#### Paired Time Course + +Candidate summary metrics: + +- `trajectory_amplitude_mean / (trajectory_amplitude_sd + epsilon)` +- trajectory amplitude weighted by pairwise direction agreement between endpoints + +These should be available as alternate ranking modes, but not the default. + +## Recommended V1 Defaults + +### Matched Pairwise + +- default `rank_by="mean_abs_delta"` +- always return: + - `mean_delta` + - `mean_abs_delta` + - `delta_sd` + - `sign_agreement` + - `n_pairs_used` + +### Paired Time Course + +- default `rank_by="trajectory_amplitude_mean"` +- always return: + - `trajectory_amplitude_mean` + - `trajectory_amplitude_sd` + - `n_pairs_used` + +These defaults balance interpretability and robustness. + +## Not In First Paired Implementation + +Do not add these yet: + +- pattern labels like `monotonic_gain`, `transient_peak`, `late_loss` +- paired inferential models +- automatic fallback to pooled modes + +Those can come later once the paired summary tables are stable. + +## Result Contract Additions + +`RegionDiscoveryResult` can remain the same shape, but paired discovery should add metadata fields and columns. + +### Metadata + +Required additions when using paired modes: + +- `pairing_key` +- `pairing_policy` +- `n_pairs_used` +- `n_pairs_dropped` +- `paired_mode` +- `rank_by` + +For time-course: + +- `time_order` + +### Hit/Window Columns + +For matched pairwise windows and hits: + +- `mean_delta` +- `mean_abs_delta` +- `delta_sd` +- `sign_agreement` +- `n_pairs_used` + +For paired time-course windows and hits: + +- `trajectory_amplitude_mean` +- `trajectory_amplitude_sd` +- `n_pairs_used` + +## Error Handling + +The paired workflow must fail fast when: + +- `pairing_key` is missing for paired modes +- required conditions are absent +- `time_order` references conditions not present +- no complete pairs remain after filtering + +It should not silently fall back to pooled analysis. + +If incomplete pairs are dropped under `complete_pairs_only`, the result metadata should clearly report that. + +## Testing Requirements + +Add tests for: + +- paired pair-resolution with complete pairs only +- strict error on missing pairs +- matched-pairwise ranking by `mean_abs_delta` +- paired time-course ranking by trajectory amplitude +- dropped-pair accounting in metadata +- failure when `pairing_key` is omitted +- failure when `time_order` is incomplete or invalid + +Also add one integration test that confirms paired discovery still feeds into: + +- `hits_to_bed(...)` +- `region_contrasts.score_regions(...)` + +## Implementation Shape + +I would implement this in two stages: + +1. paired pair-resolution plus `matched_pairwise` +2. paired ordered `time_course` + +Both stages should reuse the same internal pairing resolver and paired-window table builder. + +## Recommendation + +Implement `matched_pairwise` first, but design the internal table and ranking interfaces so `time_course` can land immediately after with minimal churn. + +That gives you: + +- robust paired discovery for your most common `nontargeting vs targeting` case +- a clean path to ordered paired time-course discovery +- no need to redesign the module later diff --git a/docs/superpowers/specs/2026-04-01-plotting-axis-architecture-design.md b/docs/superpowers/specs/2026-04-01-plotting-axis-architecture-design.md new file mode 100644 index 0000000..ce0007b --- /dev/null +++ b/docs/superpowers/specs/2026-04-01-plotting-axis-architecture-design.md @@ -0,0 +1,538 @@ +# Plotting Axis Architecture Design + +## Purpose + +This design defines a compatibility-safe plotting architecture for `dimelo-toolkit` that: + +- preserves the existing plotting entry points and familiar arguments +- introduces a shared internal model for positional plotting +- supports `5'->3'` orientation consistently across region-aligned plots +- supports both fixed-window and variable-length aggregate region normalization +- keeps single-read plots coordinate-preserving +- keeps plotting data-first so users can use Matplotlib, seaborn, Plotly, Altair, or their own stack + +This is an additive architecture around the current package. It must not require a rewrite of the parsing layer or force existing users to adopt a new plotting API immediately. + +## Compatibility Constraint + +Backward continuity is a hard requirement. + +The update must: + +- keep existing plotters working +- preserve current public plotting signatures unless a change is strictly additive +- preserve familiar flags such as `relative`, `single_strand`, and `regions_5to3prime` +- preserve current behavior by default for legacy plotting entry points +- keep Matplotlib as the built-in default renderer + +The new plotting system becomes the canonical internal model. Existing plotting functions remain supported as wrappers or adapters over that model. + +## Problem Statement + +The package currently has several plotting paths with overlapping ideas: + +- region-relative plotting +- strand-aware plotting +- `regions_5to3prime` flipping +- single-read displays +- aggregate enrichment and depth profiles + +These concepts already exist, but they are distributed across multiple modules and are not expressed as one shared plotting contract. + +The new analysis layers also need more explicit plotting semantics: + +- global summaries +- region discovery scan plots +- known-region contrasts +- clustering summaries +- occupancy follow-up + +Users also need more flexible positional plotting for region-aligned analyses, especially: + +- fixed centered windows +- variable-length region normalization for aggregate plots +- arbitrary segment maps for reference-defined feature structure +- support for both contiguous and non-contiguous feature segments + +The design must make those capabilities available without making single-read plots scientifically misleading. + +## Core Design Rule + +There are three separate concerns that must stay independent: + +1. `plot_family` + What kind of visualization is being produced. + +2. `axis_spec` + How x coordinates are defined and aligned. + +3. `aggregation_spec` + How values are summarized across reads, regions, or conditions. + +The term “normalization” must not be used ambiguously across these concerns. + +## Safe Semantic Boundary + +Single-read plots and aggregate plots must not share the same x-axis freedoms. + +### Single-read plots + +Single-read plots must remain coordinate-preserving. + +Allowed: + +- genomic coordinates +- fixed-window relative coordinates +- `5'->3'` orientation flips for strand-consistent viewing +- segment-based faceting or panel splits + +Not allowed: + +- stretched or continuously scaled x-axes across variable-length regions +- metaregion-style body rescaling on a continuous single-read canvas + +This is required to avoid misleading geometric interpretations of read structure. + +### Aggregate plots + +Aggregate positional plots may use normalized axes because they explicitly represent a summarized metaregion view rather than raw per-read geometry. + +Allowed: + +- genomic or region-oriented axes +- fixed-window aggregation +- scaled contiguous region bodies +- arbitrary segment maps +- contiguous and non-contiguous segment composition +- concatenated or faceted segment layouts + +## Canonical Axis Model + +The canonical internal model should be a shared positional axis system used by plot-data preparation. + +### Axis families + +There are three user-facing axis families: + +1. `fixed_window` + One continuous anchored window around a position, center, or landmark. + +2. `scaled_region` + One contiguous variable-length region body, optionally with fixed flanks. + +3. `segment_map` + One plotted axis assembled from explicit per-region reference segments. + +Internally: + +- `fixed_window` compiles to a one-segment representation +- `scaled_region` compiles to a structured segment representation +- `segment_map` is the canonical general form + +### Orientation + +Orientation is independent of coordinate family. + +Supported values: + +- `genomic` +- `region_5to3` + +`region_5to3` means region-aligned plots are oriented along the reference feature direction. For negative-strand regions, this implies flipping positional coordinates in the derived plot view. + +This is conceptually the generalization of today’s `regions_5to3prime` behavior. + +## Segment Model + +The plotting system must support arbitrary segments keyed directly in reference coordinates. + +The base contract is explicit per-region segment definitions. Feature-derived helpers are convenience layers, not the core contract. + +### Segment fields + +Each segment should support: + +- `segment_id` +- `label` +- `region_id` +- `start_ref` +- `end_ref` +- `mode` + - `raw` + - `scaled` +- `bins` or `bin_width` +- `contiguous_with_previous` +- `plot_gap_after` + +### Contiguous vs non-contiguous segments + +Non-contiguous segments are not a separate analysis mode. They are represented by segment metadata. + +This allows one common substrate to support: + +- `upstream | body | downstream` +- exon-only plots +- motif neighborhoods +- arbitrary user-specified feature composites + +Renderers must be able to express discontinuity either through: + +- separators or gaps on one concatenated axis +- faceted small multiples by segment + +## Aggregation Model + +Aggregate positional plotting must make weighting explicit. + +This is separate from x-axis normalization. + +### Weighting options + +The design should support: + +- `equal_region` + Each region contributes equally after within-region summarization. + +- `equal_observation` + Each observation contributes equally. + +- `coverage_weighted` + Regions are weighted by coverage or another confidence-like quantity. + +The recommended default is `equal_region`. + +### Within-region summary + +The design should support at least: + +- `mean` +- `fraction` +- `density` + +This defines how a region is summarized internally before any cross-region aggregation. + +### Signal normalization + +Signal normalization must be separate from x-axis normalization and weighting. + +The plotting contract should support metadata for at least: + +- `none` +- `per_region` +- `global` +- `control_regions` + +Whether all of these are implemented in the first slice is a separate scope decision. The design requires the conceptual separation now. + +### Order-of-operations clarity + +The plotting system must keep the following choices explicit: + +- normalize each region then average +- average raw signal then normalize once + +These are not interchangeable and should not be hidden behind one plotting flag. + +## Plot Families + +The plotting system should define explicit plot families rather than one generic plotting function. + +### 1. `single_read_raster` + +For read-level displays. + +Typical uses: + +- read rasters +- read event/lollipop plots +- browser-like region views + +Constraints: + +- coordinate-preserving only +- may use `genomic` or `region_5to3` +- may use `fixed_window` +- may use segmented faceting +- must not use continuous scaled metaregion axes + +### 2. `aggregate_profile` + +For aggregated enrichment, depth, abundance, or occupancy-like profiles over aligned regions. + +Typical uses: + +- promoter-centered metaprofiles +- gene-body plots +- upstream/body/downstream summaries +- segment-based aggregate traces + +Capabilities: + +- `fixed_window` +- `segment_map` +- `region_5to3` +- aggregate-only scaled segments +- concatenated or faceted segment layout + +### 3. `aggregate_heatmap` + +For region-by-position summaries. + +Capabilities: + +- same axis semantics as `aggregate_profile` +- rows can represent regions, conditions, clusters, or ranked loci +- supports faceted or concatenated segment layouts + +### 4. `distribution_summary` + +For non-positional summary plots. + +Typical uses: + +- cluster distribution bars +- condition heatmaps +- volcano plots +- rank plots +- occupancy summary charts + +No positional axis normalization applies. + +### 5. `track_scan` + +For genome scan or local discovery tracks. + +Typical uses: + +- region discovery scan tracks +- hit-context plots +- chromosome-level summaries + +These are primarily genomic-coordinate plots. + +## Analysis-to-Plot Mapping + +### `global_analysis` + +Primary plot families: + +- `distribution_summary` +- `track_scan` +- `aggregate_profile` where region-aligned summaries exist + +### `region_discovery` + +Primary plot families: + +- `track_scan` +- `distribution_summary` +- local `aggregate_profile` views for top hits + +### `region_contrasts` + +Primary plot families: + +- `distribution_summary` +- `aggregate_profile` +- `aggregate_heatmap` + +### `shared_clustering` + +Primary plot families: + +- `distribution_summary` +- positional `aggregate_profile` later where region-aligned occupancy summaries are meaningful + +### Legacy read/profile plotting + +Legacy plotting modules should gradually route through the same shared positional data-prep semantics where feasible. + +## API Model + +The advanced plotting API should live primarily in a shared plotting module, with thin wrappers elsewhere. + +Recommended shared objects: + +```python +@dataclass +class AxisSpec: + orientation: str # "genomic" | "region_5to3" + coordinate_mode: str # "fixed_window" | "segment_map" + anchor: str | None = None + upstream_bp: int | None = None + downstream_bp: int | None = None + segments: list["SegmentSpec"] | None = None + + +@dataclass +class SegmentSpec: + segment_id: str + label: str + start_ref: int + end_ref: int + mode: str # "raw" | "scaled" + bins: int | None = None + plot_gap_after: bool = False + contiguous_with_previous: bool = True + + +@dataclass +class AggregationSpec: + weighting: str # "equal_region" | "equal_observation" | "coverage_weighted" + within_region_summary: str # "mean" | "fraction" | "density" + signal_normalization: str # "none" | "per_region" | "global" | "control_regions" + layout: str # "concatenated" | "faceted" + + +@dataclass +class PlotRequest: + plot_family: str + axis: AxisSpec | None = None + aggregation: AggregationSpec | None = None + renderer: str = "matplotlib" +``` + +Recommended shared prep entry points: + +- `prepare_single_read_plot_data(...)` +- `prepare_aggregate_plot_data(...)` + +These prepare data and metadata only. Rendering is a separate thin layer. + +## Data Contract + +Every plot-prep function should return structured plotting payloads rather than figures as the canonical contract. + +Recommended outputs: + +- `plot_table` + Long-form plotting data. + +- `axis_table` + Segment boundaries, axis labels, gap markers, and orientation metadata. + +- `metadata` + Plot semantics, units, normalization mode, weighting mode, and family identifiers. + +- `summary_table` + Optional pre-aggregated values when useful. + +This keeps the plotting system backend-neutral and aligned with the existing data-first direction of the newer workflows. + +## Legacy Wrapper Strategy + +The new plotting architecture should not replace current plotters at once. + +Instead: + +- legacy functions remain public +- legacy flags are translated into the new shared specs internally +- defaults remain behaviorally close to the current package +- advanced users can opt into the richer plotting API directly + +Examples of translation: + +- `regions_5to3prime=True` + -> `AxisSpec(orientation="region_5to3", ...)` + +- `relative=True` with centered windows + -> `AxisSpec(coordinate_mode="fixed_window", ...)` + +- older profile plotting + -> `plot_family="aggregate_profile"` + +### User-facing guidance + +The docs should explicitly map old concepts to new ones: + +- old `regions_5to3prime` + -> new `orientation="region_5to3"` + +- old relative region profile plotting + -> new `fixed_window` + +- new scaled or segmented metaregion plotting + -> aggregate-only advanced capability + +- single-read plots remain geometry-preserving + +## Renderer Strategy + +Built-in rendering should remain Matplotlib-first for compatibility. + +The package should still encourage users to plot returned tables with their own preferred stack. + +So the plotting stack becomes: + +1. canonical plot-data preparation +2. thin built-in Matplotlib rendering +3. optional user-managed rendering elsewhere + +This is consistent with the current direction of `plot_data` in newer workflow results. + +## Module Layout + +Recommended direction: + +- expand [plotting.py](../../../dimelo/plotting.py) + to hold the shared plot-data preparation core + +- keep existing modules such as: + - [plot_enrichment_profile.py](../../../dimelo/plot_enrichment_profile.py) + - [plot_depth_profile.py](../../../dimelo/plot_depth_profile.py) + - [plot_reads.py](../../../dimelo/plot_reads.py) + as wrappers or adapters during the transition + +- route newer workflow `plot_data` preparation through the shared plotting core where it makes sense + +## Failure and Validation Rules + +The shared plotting layer should validate semantic misuse early. + +Required validations: + +- reject continuous scaled metaregion axes for single-read plots +- reject positional axis specs on non-positional plot families where not applicable +- reject malformed segment maps +- require explicit segment discontinuity metadata for non-contiguous segment layouts +- preserve legacy defaults when wrappers are used without advanced options + +## Testing Requirements + +The implementation plan for this design must include: + +- regression coverage proving existing plotters still work with legacy arguments +- unit tests for shared `AxisSpec` and segment validation +- unit tests for `region_5to3` flipping in plot-data prep +- unit tests proving single-read plotting rejects scaled continuous metaregion axes +- unit tests for aggregate segment-map prep with: + - contiguous segments + - non-contiguous segments + - concatenated layout metadata + - faceted layout metadata +- tests for weighting modes in aggregate profile prep + +## Implementation Order + +Recommended implementation sequence: + +1. Add shared plotting spec dataclasses and validation in `plotting.py`. +2. Add shared positional plot-data prep for `fixed_window`. +3. Add aggregate-only segment-map prep with contiguous and non-contiguous support. +4. Add `region_5to3` orientation handling in the shared prep layer. +5. Route one aggregate legacy plotter through the shared core. +6. Route one single-read legacy plotter through the shared core. +7. Extend newer workflow `plot_data` helpers where useful. +8. Add compatibility docs mapping old plotting flags to the new model. + +## Recommendation + +The package should standardize on a shared plotting axis model while keeping plotting behavior safe and scientifically honest: + +- orientation is general +- scaled axes are aggregate-only +- single-read plots preserve geometry +- legacy plotting calls remain supported +- plotting data stays the canonical contract + +This gives users more expressive aggregate plotting for variable-length features and arbitrary reference-defined segments without breaking the mental model of existing plotting routines. diff --git a/docs/superpowers/specs/2026-04-01-region-contrasts-plotting-design.md b/docs/superpowers/specs/2026-04-01-region-contrasts-plotting-design.md new file mode 100644 index 0000000..60d1565 --- /dev/null +++ b/docs/superpowers/specs/2026-04-01-region-contrasts-plotting-design.md @@ -0,0 +1,297 @@ +# Region Contrasts Plotting Design + +## Purpose + +This design defines the first analysis-specific plotting helpers built on top of the shared plotting-axis architecture. The goal is to support positional plotting for `dimelo.region_contrasts` while preserving the package's current separation of responsibilities: + +- `region_contrasts` scores known loci +- parse/load workflows provide positional substrates +- plotting helpers join scored loci to positional summaries and return data-first plot payloads + +This is an additive design. It must not replace existing plotting entry points or change the semantics of the current parsing layer. + +## Design Boundary + +The first plotting slice is for `region_contrasts` only. + +It should support: + +- aggregate positional profiles across known loci +- per-region positional heatmaps +- numerator, denominator, and delta payloads in one prepared result +- both pileup-backed abundance contrasts and cluster-occupancy follow-up, as long as a compatible positional source table is supplied + +It should not require `RegionContrastResult` to carry heavy positional tables internally. + +## Core Rule + +`RegionContrastResult` is the scored-region contract. + +Positional information comes from an explicit `position_table` derived from parsing/loading workflows. Plotting helpers merge the two. + +This keeps responsibilities clean: + +- scoring stays in `dimelo.region_contrasts` +- positional extraction stays in the existing parse/load layer +- plotting prep stays in `dimelo.plotting` + +## Public API + +Add two public helpers in [plotting.py](../../../dimelo/plotting.py): + +- `prepare_region_contrast_profile_data(...)` +- `prepare_region_contrast_heatmap_data(...)` + +Recommended signature shape: + +```python +payload = plotting.prepare_region_contrast_profile_data( + result=contrast_result, + position_table=position_table, + axis=axis_spec, + aggregation=aggregation_spec, + value_mode="all", +) +``` + +```python +payload = plotting.prepare_region_contrast_heatmap_data( + result=contrast_result, + position_table=position_table, + axis=axis_spec, + aggregation=aggregation_spec, + value_mode="all", +) +``` + +Where: + +- `result` is a `RegionContrastResult` +- `position_table` is an already-aggregated positional summary table +- `axis` is the shared `AxisSpec` +- `aggregation` is the shared `AggregationSpec` +- `value_mode` controls whether the payload exposes numerator, denominator, delta, or all three + +## Positional Source Contract + +The first implementation should consume already-aggregated per-region positional summaries, not per-read raw events. + +The minimal required columns are: + +- `region_id` +- `position` +- `value` +- `region_strand` + +And at least one grouping key that can be related to the contrast result: + +- `sample_id`, or +- `condition` + +Optional columns that should be preserved when present: + +- `chrom` +- `region_start` +- `region_end` +- `segment_id` +- `weight` +- representation-specific columns such as `cluster` + +The plotting layer should not recompute low-level pileup or extract summaries. It only validates, aligns, aggregates, and annotates. + +## Supported V1 Data Paths + +### 1. Pileup-backed positional abundance plots + +This is the primary first path. + +The positional table represents aggregate region-aligned signal over known loci, for example: + +- per-region modification fraction by position +- per-region enrichment/depth-like positional summaries + +These are joined to `RegionContrastResult` for: + +- numerator profile +- denominator profile +- delta profile +- per-region heatmaps of the same views + +### 2. Cluster-occupancy follow-up + +This is allowed when the caller provides a compatible positional source table. + +The plotting helpers should not assume that occupancy contrasts are inherently positional. They only support them when the positional table carries the needed signal. + +This keeps occupancy plotting opt-in and data-driven rather than pretending all occupancy summaries have x-axis structure. + +## Axis Rules + +These helpers must use the shared plotting-axis model already established in [2026-04-01-plotting-axis-architecture-design.md](2026-04-01-plotting-axis-architecture-design.md). + +### V1 axis support + +Required: + +- `fixed_window` +- `orientation="genomic"` +- `orientation="region_5to3"` + +Deferred but designed-for: + +- aggregate `segment_map` + +Single-read constraints do not apply here because these helpers are aggregate-only. + +## Aggregation Rules + +These helpers must use the shared `AggregationSpec`. + +### V1 requirements + +Required: + +- `weighting="equal_region"` +- `layout="faceted"` as the default + +Supported if straightforward from the current substrate: + +- `equal_observation` +- `coverage_weighted` + +The implementation should keep weighting explicit in metadata even if only a subset is active in the first code slice. + +## Value Modes + +The helpers should expose all three comparison views from one prepared payload: + +- `numerator` +- `denominator` +- `delta` + +Recommended behavior: + +- `value_mode="all"` returns a long-form table with a `value_mode` column +- narrower modes may filter that table without changing the rest of the payload contract + +This allows one prep call to feed: + +- overlay plots +- side-by-side facets +- delta-only tracks +- numerator / denominator / delta heatmaps + +## Output Contract + +Each helper should return a renderer-neutral payload with: + +- `plot_table` +- `axis_table` +- `metadata` +- `summary_table` + +### `plot_table` + +Long-form rows suitable for direct plotting. It should include: + +- mapped `plot_x` +- `region_id` +- `value` +- `value_mode` +- grouping keys such as `condition` or `sample_id` + +For heatmaps, the table may also include row-order metadata. + +### `axis_table` + +Shared axis metadata from the plotting core. + +### `metadata` + +Must include at least: + +- `plot_family` +- `representation` +- `analysis_unit` +- `orientation` +- `coordinate_mode` +- `weighting` +- `layout` +- `value_modes` + +### `summary_table` + +Optional compact aggregate tables useful for default Matplotlib wrappers or quick inspection. + +## Joining Rules + +The plotting prep must join positional rows only for regions represented in the contrast result unless the caller explicitly asks otherwise later. + +V1 behavior: + +- filter `position_table` to contrast-supported `region_id`s +- join on the grouping keys available from the result and positional source +- fail clearly if the positional source cannot be matched to the contrast result + +The helpers should prefer explicit failure over silently mixing incompatible region or condition scopes. + +## Heatmap Semantics + +Heatmaps should be aggregate region heatmaps, not single-read matrices. + +V1 row model: + +- one row per region for a chosen `value_mode` +- optional faceting by numerator / denominator / delta + +Possible row ordering options later: + +- by contrast rank +- by delta magnitude +- by supplied region order + +The first implementation can default to contrast rank order. + +## Compatibility Constraint + +This design must preserve continuity with the existing package. + +That means: + +- legacy plotters remain supported +- new helpers are additive +- old flags such as `regions_5to3prime` continue to map onto the shared orientation model +- users can keep plotting directly from old parse/load outputs if they prefer + +The new helpers are for users who want the newer analysis-layer workflows without losing flexibility over rendering. + +## Failure Modes + +The helpers should fail clearly on: + +- missing required positional columns +- incompatible `region_id` or grouping keys +- unsupported axis modes for the current slice +- ambiguous joins between contrast rows and positional rows +- positional tables that are not already aggregated to the expected resolution + +## Tests + +The implementation plan should include tests for: + +- successful profile prep from a minimal pileup-backed positional table +- successful heatmap prep from the same substrate +- `value_mode="all"` includes numerator, denominator, and delta +- `region_5to3` orientation uses the shared fixed-window axis rules +- rejection of positional tables missing required join keys +- rejection of incompatible contrast / positional scopes +- compatibility with `cluster_occupancy` only when a positional source table is explicitly supplied + +## Recommended Build Order + +1. Add shared internal helpers in `dimelo.plotting` for joining contrast results to positional tables. +2. Implement `prepare_region_contrast_profile_data(...)` for fixed-window aggregate inputs. +3. Implement `prepare_region_contrast_heatmap_data(...)` on the same substrate. +4. Add tests for pileup-backed known-region contrasts. +5. Document how these helpers relate to old plotting entry points and the newer renderer-neutral payload model. + diff --git a/docs/superpowers/specs/2026-04-02-docs-and-integration-cleanup-design.md b/docs/superpowers/specs/2026-04-02-docs-and-integration-cleanup-design.md new file mode 100644 index 0000000..8edfd2c --- /dev/null +++ b/docs/superpowers/specs/2026-04-02-docs-and-integration-cleanup-design.md @@ -0,0 +1,164 @@ +# Docs And Integration Cleanup Design + +## Purpose + +This design defines the next cleanup slice after the `docs/superpowers` index work. + +The goal is to bundle: + +- historical docs path portability cleanup +- targeted codebase integration cleanup around the recently implemented analysis and plotting work + +This is still a cleanup slice, not a feature-building slice. It should improve clarity, portability, and discoverability of work that already landed. + +## Core Direction + +Cleanup should be docs-led. + +The order is: + +1. normalize historical docs so they are portable in the repository +2. perform a narrow integration cleanup around the completed code/docs surface + +This keeps the cleanup low-risk and prevents it from turning into an unfocused refactor. + +## Scope + +This bundled cleanup should include two phases. + +### Phase A: Historical docs normalization + +Normalize machine-specific path leakage in tracked historical docs under: + +- `docs/superpowers/specs/` +- `docs/superpowers/plans/` + +This means: + +- replace machine-local absolute paths with repo-relative or plain repo paths where appropriate +- preserve the original meaning of the documents +- avoid changing the substantive design or implementation content + +This phase is about portability and maintainability of the internal planning trail, not rewriting history. + +### Phase B: Codebase integration cleanup + +Perform a targeted cleanup around the already-implemented analysis and plotting work. + +This should focus on: + +- public surface discoverability +- docs cross-linking +- lightweight consistency fixes around recently added helpers + +It should not introduce new analysis features or broader architecture changes. + +## Out Of Scope + +This cleanup slice should not: + +- redesign existing features +- add new analysis workflows +- rename large parts of the codebase for style reasons alone +- refactor internals that are not causing integration friction +- rewrite historical docs for prose polish beyond what is needed for path portability + +## Phase A Design: Historical Docs Portability + +The tracked historical plan/spec docs currently contain many machine-local absolute paths. + +That is acceptable during active execution, but once the docs are committed as repo artifacts they should be portable enough to read in: + +- GitHub +- local clones on other machines +- future branches + +### Recommended path policy + +For historical docs: + +- use repo-relative markdown links where links are useful +- use plain repo-relative paths in code blocks and command examples when a clickable link is not needed +- avoid machine-local absolute paths unless a document explicitly needs to show an environment-specific example + +### Editing rule + +This phase should make the smallest possible content changes: + +- path normalization only +- no semantic rewrites unless a path reference becomes misleading when converted + +## Phase B Design: Integration Cleanup + +The codebase side should prioritize discoverability over internal churn. + +### Primary targets + +1. public surface and discoverability + - ensure docs point users to the new helpers and workflows clearly + - ensure exports/import surfaces are coherent where users would reasonably expect them + - ensure recent helper behavior is described accurately in the main documentation trail + +2. lightweight consistency cleanup + - only when needed to reduce confusion around completed work + - examples: small naming inconsistencies, stale references, or thin integration gaps across docs/tests + +### Not the focus + +This is not the pass for: + +- broad internal refactors +- large module reorganizations +- cleanup motivated only by aesthetic preference + +## Recommended Files To Inspect + +The implementation plan should likely touch or inspect: + +- `docs/superpowers/specs/` +- `docs/superpowers/plans/` +- [README.md](../../../README.md) +- [docs/region-contrasts.md](../../region-contrasts.md) +- [docs/shared-clustering.md](../../shared-clustering.md) +- [docs/region-discovery.md](../../region-discovery.md) +- [docs/global-analysis.md](../../global-analysis.md) +- [dimelo/__init__.py](../../../dimelo/__init__.py) +- [dimelo/plotting.py](../../../dimelo/plotting.py) + +The implementation plan does not need to modify all of them. This list exists to constrain where the integration cleanup should look. + +## Status / Navigation Interaction + +The new central index in `docs/superpowers/README.md` should remain the status source of truth. + +This cleanup may update it if: + +- normalized paths require link fixes +- status wording needs correction after integration cleanup + +But it should not replace the central-index model introduced in the prior cleanup slice. + +## Verification + +The implementation plan should include two kinds of verification. + +### Docs verification + +- no remaining machine-local absolute paths in tracked `docs/superpowers` historical docs, unless intentionally preserved +- no broken links introduced by normalization +- no remaining untracked plan/spec artifacts related to this cleanup + +### Code/docs integration verification + +- any touched docs reference real current helpers/workflows +- any touched exports or helper references resolve in the repo +- if tests are touched, run the relevant focused test subset + +## Recommended Build Order + +1. Normalize path references in tracked `docs/superpowers/specs` and `docs/superpowers/plans`. +2. Verify no broken relative links were introduced. +3. Inspect the public docs surface for stale or missing integration points around completed work. +4. Apply only the smallest integration cleanup needed for discoverability and consistency. +5. Re-verify docs references and any touched code/test surfaces. + diff --git a/docs/superpowers/specs/2026-04-02-docs-coherence-and-discoverability-design.md b/docs/superpowers/specs/2026-04-02-docs-coherence-and-discoverability-design.md new file mode 100644 index 0000000..7586ad0 --- /dev/null +++ b/docs/superpowers/specs/2026-04-02-docs-coherence-and-discoverability-design.md @@ -0,0 +1,178 @@ +# Docs Coherence And Discoverability Design + +## Goal + +Make the current branch documentation coherent and consistent across the internal planning docs, user-facing guides, and the small package surface users are expected to import from. + +This is a cleanup slice, not a feature slice. The goal is to make the repository easier to understand after the large amount of recent implementation work, without changing analysis behavior. + +## Scope + +This design covers: + +- internal status-map accuracy in [docs/superpowers/README.md](../README.md) +- consistency across user-facing guides: + - [README.md](../../../README.md) + - [docs/global-analysis.md](../../../docs/global-analysis.md) + - [docs/region-discovery.md](../../../docs/region-discovery.md) + - [docs/region-contrasts.md](../../../docs/region-contrasts.md) + - [docs/shared-clustering.md](../../../docs/shared-clustering.md) +- small package-surface cleanup in [dimelo/__init__.py](../../../dimelo/__init__.py) only if docs and the current public import story are clearly misaligned + +This design does not cover: + +- new analysis features +- new plotting features +- API redesign +- broad refactors +- rewriting the historical specs/plans themselves beyond status-map updates + +## Why This Pass Is Needed + +The branch now has: + +- multiple new analysis layers +- multiple new plotting-prep helper families +- discovery-to-clustering and discovery-to-clustering-to-contrast workflows +- internal planning docs that were cleaned up once already, but are now behind again + +The implementation is ahead of the documentation in a few places. The repo needs one coherence pass so a user or future contributor can understand: + +- what is implemented now +- which docs correspond to which module/workflow +- how the modules fit together +- which plotting helpers exist for which analysis families + +## Definition Of “Coherent And Consistent” + +This pass should satisfy four tests. + +### 1. Status-map accuracy + +[docs/superpowers/README.md](../README.md) should: + +- include the newest plotting specs/plans +- stop omitting implemented slices +- label implemented vs partially implemented work in a way that matches repo reality + +The status map should remain lightweight. It is an index, not a changelog. + +### 2. User-flow consistency + +The user-facing docs should tell one consistent story: + +- `parse_bam` remains the preprocessing/materialization foundation +- `global_analysis`, `region_discovery`, `region_contrasts`, and `shared_clustering` are the higher-level analysis layers +- plotting is data-first +- helper coverage differs by analysis family, and the docs should say so honestly + +### 3. Cross-link and naming consistency + +The same helpers and workflows should be named the same way everywhere they appear. In practice, this means consistently using names such as: + +- `shared_cluster_distribution(...)` +- `discovery_cluster_workflow(...)` +- `discovery_cluster_contrast_workflow(...)` +- `prepare_region_contrast_profile_data(...)` +- `prepare_region_contrast_heatmap_data(...)` +- `prepare_region_discovery_scan_data(...)` +- `prepare_region_discovery_hit_context_data(...)` +- `prepare_global_analysis_summary_data(...)` +- `prepare_global_analysis_window_data(...)` + +### 4. Package discoverability consistency + +If the docs imply that users should import a module or helper from top-level `dimelo`, the top-level package surface should support that expectation, but only where the fix is small and obvious. + +This does not mean re-exporting every helper automatically. It means removing clear documentation/import mismatches if they exist. + +## Recommended Cleanup Structure + +The cleanup should proceed in three layers. + +### Layer 1: Internal status/docs map + +Update [docs/superpowers/README.md](../README.md) to: + +- add the missing plotting specs/plans +- refresh the status labels for the slices that have landed +- keep the wording concise + +This is the source of truth for planning status and should reflect the current branch state. + +### Layer 2: User-facing guide consistency + +Sweep the main user-facing docs: + +- [README.md](../../../README.md) +- [docs/global-analysis.md](../../../docs/global-analysis.md) +- [docs/region-discovery.md](../../../docs/region-discovery.md) +- [docs/region-contrasts.md](../../../docs/region-contrasts.md) +- [docs/shared-clustering.md](../../../docs/shared-clustering.md) + +Focus on: + +- stale “future” wording for already implemented work +- inconsistent helper/workflow naming +- inconsistent descriptions of module roles +- inconsistent plotting-helper coverage claims +- missing short cross-links where users would reasonably look first + +This should be a targeted consistency sweep, not a rewrite. + +### Layer 3: Small package-surface cleanup + +Inspect [dimelo/__init__.py](../../../dimelo/__init__.py) after the docs sweep. + +Allowed changes: + +- exposing a clearly user-facing module that the docs already treat as a top-level import +- fixing an obvious export omission that makes the docs misleading + +Disallowed changes: + +- broad export churn +- re-exporting many helpers just for convenience +- changing public import style for aesthetic reasons + +## Expected Outcomes + +After the pass: + +- the internal docs index reflects the actual implemented state +- the top-level README and guide docs tell the same architecture story +- plotting coverage is described consistently: + - `region_contrasts` plotting prep exists + - `region_discovery` plotting prep exists + - `global_analysis` plotting prep exists + - `shared_clustering` still has lighter plotting coverage by comparison +- if a small `__init__.py` fix is needed, the docs and package surface agree + +## Non-Goals + +This pass should not try to solve every remaining gap in the branch. In particular, it should not turn into: + +- a renderer implementation slice +- a `shared_clustering` plotting expansion +- a `single_read` contrast design +- a general docs rewrite + +Those are follow-on projects and should remain separate. + +## Validation + +The cleanup should validate: + +- the internal index links still resolve +- the repo docs do not contain stale contradictory statements about which modules are implemented +- any changed top-level exports still allow `import dimelo` cleanly + +## Recommendation + +Do this as a docs-first coherence sweep with three ordered layers: + +1. internal status-map update +2. user-facing guide consistency sweep +3. minimal package-surface cleanup only if the docs clearly demand it + +That keeps the pass focused and lets us build the next implementation plan from a clean, accurate documentation state. diff --git a/docs/superpowers/specs/2026-04-02-global-analysis-plotting-design.md b/docs/superpowers/specs/2026-04-02-global-analysis-plotting-design.md new file mode 100644 index 0000000..4366c32 --- /dev/null +++ b/docs/superpowers/specs/2026-04-02-global-analysis-plotting-design.md @@ -0,0 +1,304 @@ +# Global Analysis Plotting Design + +## Goal + +Add a renderer-neutral plotting-prep layer for `dimelo.global_analysis` that makes both whole-sample summaries and tiled-window outputs easier to visualize without coupling the analysis API to a specific plotting backend. + +The first slice should help users answer two questions: + +- how do global motif fractions and normalization offsets vary across samples and conditions? +- how do tiled genome windows vary across contigs at broad scale? + +The plotting layer should remain additive and data-first, using the existing `GlobalAnalysisResult` outputs as the source of truth. + +## Scope + +This design covers: + +- plotting-prep helpers for whole-sample global summaries +- plotting-prep helpers for tiled-window broad genome views +- per-contig organization as the default window layout +- condition-level aggregation alongside per-sample summaries + +This design does not cover: + +- renderer functions +- cumulative genome axes across contigs +- normalized region-axis or segment-map plotting +- direct integration with discovery hit overlays +- any new normalization method beyond what `GlobalAnalysisResult` already provides + +## User-Facing API + +Add two public helpers in [plotting.py](../../../dimelo/plotting.py): + +- `prepare_global_analysis_summary_data(...)` +- `prepare_global_analysis_window_data(...)` + +These helpers should consume a [GlobalAnalysisResult](../../../dimelo/models.py) and return renderer-neutral payload dictionaries with plot-ready tables plus metadata. + +## Design Principles + +### Keep the analysis layer authoritative + +The plotting helpers should expose and reshape the values already computed by [global_analysis.py](../../../dimelo/global_analysis.py). They should not invent new biological normalization logic or re-derive alternative summary quantities beyond simple aggregations. + +### Separate summary and window concerns + +`GlobalAnalysisResult` naturally splits into: + +- global per-sample summary values and normalization factors +- tiled broad-window values across contigs + +The plotting API should mirror that split rather than combining both families behind a single mode switch. + +### Keep window views genome-native + +Broad window plots should stay per-contig by default. The helper should not flatten contigs into one synthetic cumulative axis in the first slice. + +## Plot Family 1: Global Summary + +### Purpose + +Prepare whole-sample and condition-level plotting payloads for global motif fractions and normalization offsets. + +### Public Entry Point + +```python +payload = plotting.prepare_global_analysis_summary_data( + result=result, + motifs=None, + aggregate_conditions=True, +) +``` + +### Inputs + +- `result: GlobalAnalysisResult` +- `motifs: list[str] | None` + - optional motif filter +- `aggregate_conditions: bool` + - when `True`, prepare condition-level summaries in addition to sample-level rows + +### Outputs + +Return a dictionary with: + +- `sample_summary` + - one row per sample and motif + - includes: + - `sample_id` + - `condition` + - `replicate` + - `motif` + - `global_fraction` +- `condition_summary` + - one row per condition and motif + - includes aggregated summaries such as: + - `condition` + - `motif` + - `global_fraction_mean` + - `global_fraction_median` + - `sample_n` + - this table may be empty when `aggregate_conditions=False` +- `normalization_table` + - sample-level normalization factors + - includes: + - `sample_id` + - `condition` + - `replicate` + - `motif` + - `global_fraction` + - `reference_fraction` + - `global_offset` +- `metadata` + - includes: + - `motifs` + - `aggregate_conditions` + +### Aggregation rules + +The helper should: + +- preserve per-sample rows directly from `result.summary` +- preserve sample-level normalization rows directly from `result.normalization_factors` +- compute condition-level summaries by grouping per sample, not by inventing new weighted schemes + +The first implementation should use simple unweighted condition-level summaries, with mean, median, and sample count as the primary outputs. + +## Plot Family 2: Broad Window Views + +### Purpose + +Prepare tiled-window plotting payloads for broad genome inspection by sample or condition. + +### Public Entry Point + +```python +payload = plotting.prepare_global_analysis_window_data( + result=result, + contigs=None, + motifs=None, + aggregate_conditions=False, +) +``` + +### Inputs + +- `result: GlobalAnalysisResult` +- `contigs: list[str] | None` + - optional filter and ordering override +- `motifs: list[str] | None` + - optional motif filter +- `aggregate_conditions: bool` + - when `True`, provide condition-level window summaries in addition to sample-level windows + +### Outputs + +Return a dictionary with: + +- `window_table` + - sample-level tiled windows + - includes: + - `sample_id` + - `condition` + - `replicate` + - `motif` + - `contig` + - `start` + - `end` + - `window_midpoint` + - `window_fraction` +- `condition_window_table` + - optional condition-level aggregation of windows + - includes: + - `condition` + - `motif` + - `contig` + - `start` + - `end` + - `window_midpoint` + - `window_fraction_mean` + - `window_fraction_median` + - `sample_n` + - may be empty when `aggregate_conditions=False` +- `metadata` + - includes: + - `contig_order` + - `motifs` + - `aggregate_conditions` + +### Default layout semantics + +The helper should default to per-contig organization, not cumulative genome coordinates. Renderers are expected to facet or panel by contig by default. + +### Derived columns + +The helper should add: + +- `contig` + - normalized alias of the `chromosome` field +- `window_midpoint` + - `(start + end) / 2` + +The helper should not alter the underlying window values beyond optional condition-level aggregation. + +## Defaults And Validation + +### Result validation + +Helpers should validate they received a `GlobalAnalysisResult`-like object with the required attributes: + +- `summary` +- `windows` +- `normalization_factors` +- `metadata` + +### Motif filtering + +If `motifs` is provided: + +- filter the relevant tables to those motifs +- raise a clear error if none of the requested motifs are present + +### Contig filtering + +If `contigs` is provided: + +- filter the window table and condition-window table to those contigs +- preserve the given order in `metadata["contig_order"]` +- raise only if none of the requested contigs are present + +### Empty results + +If the underlying tables are empty: + +- return empty stable tables with expected columns +- preserve metadata where possible +- do not raise unless required structure is missing + +## Relationship To Existing `plot_data` + +`GlobalAnalysisResult.plot_data` currently includes lightweight payloads: + +- `global_fraction_bar` +- `window_fraction_table` + +The new helpers should not depend on those names as their public contract. They may reuse the underlying tables internally if clean, but the canonical inputs remain: + +- `result.summary` +- `result.windows` +- `result.normalization_factors` + +## Backward Compatibility + +This design is additive. + +- it does not change `run_global_analysis()` +- it does not change `GlobalAnalysisResult` +- it does not remove existing `plot_data` +- it does not introduce renderer-specific coupling + +## Testing + +Add focused tests in [tests/test_plotting.py](../../../tests/test_plotting.py) for: + +- stable summary payload columns +- condition-level aggregation behavior for summary data +- stable window payload columns +- per-contig ordering behavior +- motif and contig filtering +- empty-result behavior + +Use synthetic `GlobalAnalysisResult` fixtures for most helper tests rather than full end-to-end global-analysis runs. + +## Documentation + +Update: + +- [docs/global-analysis.md](../../../docs/global-analysis.md) +- [README.md](../../../README.md) + +Document: + +- the two new helpers +- the distinction between sample-level and condition-level summaries +- the per-contig default for window payloads +- that the helpers remain data-prep only and renderer-neutral + +## Recommended Implementation Order + +1. Add plotting helper tests for global summary prep +2. Implement `prepare_global_analysis_summary_data(...)` +3. Add plotting helper tests for broad window prep +4. Implement `prepare_global_analysis_window_data(...)` +5. Update docs + +## Recommendation + +Build `global_analysis` plotting as a simple, result-centric extension of the current data-first architecture: + +- `prepare_global_analysis_summary_data(...)` +- `prepare_global_analysis_window_data(...)` + +Keep the first slice additive, per-contig by default for window views, and explicitly expose both per-sample and condition-level summaries so users can move quickly between QC-style inspection and broader comparison views. diff --git a/docs/superpowers/specs/2026-04-02-region-discovery-plotting-design.md b/docs/superpowers/specs/2026-04-02-region-discovery-plotting-design.md new file mode 100644 index 0000000..5fd7112 --- /dev/null +++ b/docs/superpowers/specs/2026-04-02-region-discovery-plotting-design.md @@ -0,0 +1,288 @@ +# Region Discovery Plotting Design + +## Goal + +Add a small, data-first plotting-prep layer for `dimelo.region_discovery` that makes discovery results easier to inspect without coupling the analysis API to a specific renderer. The first slice should help users answer two questions: + +- where are the interesting windows across the genome scan? +- what does the local scored neighborhood around the top hits look like? + +The new helpers should sit on top of the existing `RegionDiscoveryResult` contract rather than expanding `scan_genome()` itself. + +## Scope + +This design covers: + +- renderer-neutral plotting prep for discovery scan overviews +- renderer-neutral plotting prep for local top-hit context views +- per-contig organization as the default scan layout +- continued use of `RegionDiscoveryResult.windows` and `RegionDiscoveryResult.hits` as the source of truth + +This design does not cover: + +- Matplotlib or seaborn renderer functions +- cumulative genome axes across contigs +- normalized region-axis plotting +- direct single-read plotting +- segment-map or metaregion plotting semantics + +## User-Facing API + +Add two public helpers in [plotting.py](../../../dimelo/plotting.py): + +- `prepare_region_discovery_scan_data(...)` +- `prepare_region_discovery_hit_context_data(...)` + +These should accept a [RegionDiscoveryResult](../../../dimelo/models.py) and return renderer-neutral payload dictionaries with plot-ready tables plus metadata. + +## Design Principles + +### Keep discovery plotting genome-native + +Discovery is still a tiled genome scan. The first plotting helpers should preserve genomic structure rather than forcing the result into a normalized region axis. The primary organization is: + +- per-contig scan windows +- ranked hit overlays +- local window neighborhoods around selected hits + +### Keep `scan_genome()` focused on analysis + +`scan_genome()` already returns `result.windows`, `result.hits`, and lightweight `plot_data`. The richer plotting helpers should live in [plotting.py](../../../dimelo/plotting.py), not inside [region_discovery.py](../../../dimelo/region_discovery.py). That keeps analysis and plotting concerns separate and matches the newer `region_contrasts` plotting architecture. + +### Prefer result-centric helpers + +The primary public input should be `RegionDiscoveryResult`, not raw tables. Internally, the helpers can still work against `result.windows` and `result.hits`, but the public API should stay aligned with the analysis workflow output. + +## Plot Family 1: Scan Overview + +### Purpose + +Prepare data for genome-scan views that show the scored window landscape and optionally overlay ranked discovery hits. + +### Public Entry Point + +```python +payload = plotting.prepare_region_discovery_scan_data( + result=result, + contigs=None, + top_n_hits=100, + score_column=None, + include_all_windows=True, +) +``` + +### Inputs + +- `result: RegionDiscoveryResult` +- `contigs: list[str] | None` + - optional filter and ordering override for contigs to include +- `top_n_hits: int | None` + - optional cap on hit overlays used for display +- `score_column: str | None` + - defaults to the canonical discovery score field for the result shape +- `include_all_windows: bool` + - when `True`, return the full filtered `scan_table` + - when `False`, allow callers to request a lighter payload later + +### Outputs + +Return a dictionary with: + +- `scan_table` + - one row per scanned window + - minimally includes: + - `contig` + - `start` + - `end` + - `score_value` or the chosen score field + - any rank/effect columns already present in `result.windows` + - `is_hit` +- `hit_table` + - one row per displayed hit + - top-N filtered if requested +- `metadata` + - includes: + - `contig_order` + - `score_column` + - `score_mode` + - `contrast_mode` + - `merge_hits` + - `top_n_hits` + +### Default layout semantics + +The helper should default to per-contig organization, not a cumulative genome coordinate. Renderers are expected to facet or panel by contig by default. + +### Derived columns + +The helper should add: + +- `contig` + - normalized alias of the discovery `chromosome` field for plotting clarity +- `window_midpoint` + - useful for line/scatter/track renderers +- `is_hit` + - `True` for windows that correspond to the overlay hit set + +The helper should not mutate or reinterpret the discovery score itself. + +## Plot Family 2: Hit Context + +### Purpose + +Prepare local drill-down tables around selected hits so users can inspect the scored neighborhood before exporting BEDs or moving into clustering and contrasts. + +### Public Entry Point + +```python +payload = plotting.prepare_region_discovery_hit_context_data( + result=result, + top_n=12, + hit_ids=None, + padding_windows=5, + padding_bp=None, +) +``` + +### Inputs + +- `result: RegionDiscoveryResult` +- `top_n: int | None` + - number of ranked hits to include by default +- `hit_ids: list[str] | None` + - explicit hit/window selection override +- `padding_windows: int | None` + - include this many neighboring windows on each side of each selected hit +- `padding_bp: int | None` + - optional physical-distance expansion alternative + +The first implementation should support `top_n` and `padding_windows` first. `hit_ids` and `padding_bp` can be part of the public contract now if the implementation still keeps the first slice modest. + +### Outputs + +Return a dictionary with: + +- `context_table` + - local windows surrounding each selected hit + - includes: + - `contig` + - `start` + - `end` + - `window_midpoint` + - score columns + - `selected_hit_id` + - `selected_hit_rank` + - `relative_window_offset` + - `is_selected_hit` +- `selected_hits` + - the anchor hits used for the context extraction +- `metadata` + - includes: + - `selection_mode` + - `top_n` + - `padding_windows` + - `padding_bp` + - `score_column` + +### Layout semantics + +This payload is designed for small multiples or per-hit panels. Each selected hit becomes a local plotting context, usually faceted by `selected_hit_id` or `selected_hit_rank`. + +## Defaults And Validation + +### Score column selection + +If `score_column` is omitted, use: + +- `score_value` when present +- otherwise the canonical primary score column for the result shape + +If no usable score column exists, raise a clear error instead of guessing silently. + +### Empty results + +If `result.windows` or `result.hits` is empty: + +- return empty plot tables with stable columns +- preserve metadata +- do not raise unless required input structure is missing + +### Contig filtering + +If `contigs` is provided: + +- filter both windows and hits to the requested contigs +- preserve the given order in `metadata["contig_order"]` +- error only if none of the requested contigs are present + +### Hit selection + +For hit-context prep: + +- `top_n` should use the ranked order already present in `result.hits` +- if both `top_n` and explicit IDs are provided later, explicit IDs should win +- if no hits are available, return an empty stable payload + +## Relationship To Existing `plot_data` + +The helpers should treat the discovery result tables as the canonical source of truth: + +- `result.windows` +- `result.hits` + +Existing lightweight `result.plot_data["window_score_table"]` and `result.plot_data["top_hits_table"]` can remain available, but they should not block the helper design. The new helpers may reuse them internally if that is clean, but the public contract should not depend on those specific precomputed payload names. + +## Backward Compatibility + +This design is additive. + +- it does not change `scan_genome()` signatures +- it does not change `RegionDiscoveryResult` +- it does not remove existing `plot_data` +- it does not change older plotting modules + +This keeps the discovery workflow stable while making downstream plotting more coherent. + +## Testing + +Add focused tests in [tests/test_plotting.py](../../../tests/test_plotting.py) for: + +- stable scan payload columns +- per-contig ordering behavior +- top-N hit overlay filtering +- local hit-context extraction using neighboring windows +- empty-result behavior +- explicit score-column validation + +Use mocked or synthetic `RegionDiscoveryResult` objects rather than full end-to-end discovery runs for most helper tests. + +## Documentation + +Update: + +- [docs/region-discovery.md](../../../docs/region-discovery.md) +- [README.md](../../../README.md) + +Document: + +- the two new helpers +- the per-contig default +- the difference between scan overview prep and hit-context prep +- that discovery plotting remains data-first and renderer-neutral + +## Recommended Implementation Order + +1. Add plotting helper tests for scan overview prep +2. Implement `prepare_region_discovery_scan_data(...)` +3. Add plotting helper tests for hit-context prep +4. Implement `prepare_region_discovery_hit_context_data(...)` +5. Update docs + +## Recommendation + +Build `region_discovery` plotting as a result-centric, data-prep-only layer with two helpers: + +- `prepare_region_discovery_scan_data(...)` +- `prepare_region_discovery_hit_context_data(...)` + +Keep the first slice genome-native, per-contig by default, and centered on discovery windows and hits rather than normalized positional region views. diff --git a/docs/superpowers/specs/2026-04-02-shared-clustering-plotting-design.md b/docs/superpowers/specs/2026-04-02-shared-clustering-plotting-design.md new file mode 100644 index 0000000..1b25626 --- /dev/null +++ b/docs/superpowers/specs/2026-04-02-shared-clustering-plotting-design.md @@ -0,0 +1,269 @@ +# Shared Clustering Plotting Design + +## Goal + +Add a richer, renderer-neutral plotting-prep layer for `dimelo.workflows.shared_cluster_distribution()` that standardizes the main `SharedClusterResult` plotting families: + +- distribution and change views +- cluster profile views +- region-level occupancy views + +This should deepen the current lightweight `result.plot_data` contract without replacing it. + +## Scope + +This design covers: + +- plotting-prep helpers for `SharedClusterResult` +- distribution and change payloads from: + - `cluster_distribution` + - `condition_distribution` + - `distribution_change` +- cluster profile payloads from `cluster_profiles` +- region-level occupancy payloads from `region_summaries` +- optional condition-level aggregation for region occupancy + +This design does not cover: + +- renderer implementations +- new clustering logic +- new region statistics +- new occupancy models +- normalized region-axis plotting + +## User-Facing API + +Add three public helpers in [plotting.py](../../../dimelo/plotting.py): + +- `prepare_shared_cluster_distribution_data(...)` +- `prepare_shared_cluster_profile_data(...)` +- `prepare_shared_cluster_region_data(...)` + +All three should accept a [SharedClusterResult](../../../dimelo/models.py) and return renderer-neutral payload dictionaries with plot-ready tables plus metadata. + +## Design Principles + +### Keep `SharedClusterResult` authoritative + +The helpers should use the existing `SharedClusterResult` tables as the source of truth. They should not derive new biological interpretations or replace the workflow’s current `plot_data`. + +### Keep the helpers family-specific + +The plotting families are different enough that one generic mode-switched helper would become opaque. The API should follow the same pattern already used for `region_contrasts`, `region_discovery`, and `global_analysis`: multiple small public helpers with shared internals. + +### Preserve the existing lightweight `plot_data` + +The current `SharedClusterResult.plot_data` should remain available. The new helper layer is additive and richer, not a replacement. + +## Plot Family 1: Distribution And Change Views + +### Purpose + +Prepare plot-ready tables for sample-level distributions, condition-level distributions, and condition-change summaries. + +### Public Entry Point + +```python +payload = plotting.prepare_shared_cluster_distribution_data(result=result) +``` + +### Outputs + +Return a dictionary with: + +- `sample_distribution` + - rows from `result.cluster_distribution` +- `condition_distribution` + - rows from `result.condition_distribution` +- `distribution_change` + - rows from `result.distribution_change` + - may be empty if no reference condition/change table exists +- `metadata` + - includes: + - `mode` + - `motifs` + - `reference_condition` + +### Intended use + +This should support: + +- cluster-fraction bars by sample +- condition-level heatmaps +- change/delta heatmaps or scatter summaries + +## Plot Family 2: Cluster Profile Views + +### Purpose + +Prepare plot-ready tables for visualizing the feature profile of each cluster. + +### Public Entry Point + +```python +payload = plotting.prepare_shared_cluster_profile_data(result=result) +``` + +### Outputs + +Return a dictionary with: + +- `profile_table` + - rows from `result.cluster_profiles` + - standardized so cluster identifier and feature columns are easy to consume +- `metadata` + - includes: + - `mode` + - `motifs` + - `feature_names` + +### Intended use + +This should support: + +- per-cluster profile panels +- line/scatter plots over feature dimensions +- summary comparisons of cluster centroids or average patterns + +The helper should not assume any one renderer or feature geometry. + +## Plot Family 3: Region Occupancy Views + +### Purpose + +Prepare plot-ready tables for region-level occupancy inspection from `result.region_summaries`. + +### Public Entry Point + +```python +payload = plotting.prepare_shared_cluster_region_data( + result=result, + aggregate_conditions=True, +) +``` + +### Inputs + +- `result: SharedClusterResult` +- `aggregate_conditions: bool` + - when `True`, provide condition-level region summaries in addition to sample-level rows + +### Outputs + +Return a dictionary with: + +- `region_table` + - sample-level rows from `result.region_summaries` +- `condition_region_table` + - optional condition-level aggregation over `region_table` + - may be empty when `aggregate_conditions=False` +- `metadata` + - includes: + - `mode` + - `motifs` + - `aggregate_conditions` + +### Aggregation rules + +This helper should only aggregate what `result.region_summaries` already contains. It should not invent a new occupancy model. + +When condition aggregation is enabled, aggregate from sample-level region rows first so the summary semantics match the reported counts. + +## Defaults And Validation + +### Result validation + +Helpers should validate they received a `SharedClusterResult`-like object with the required attributes for the requested family. + +Examples: + +- distribution helper requires: + - `cluster_distribution` + - `condition_distribution` + - `metadata` +- profile helper requires: + - `cluster_profiles` + - `model` +- region helper requires: + - `region_summaries` + - `metadata` + +### Empty tables + +If a relevant table is empty or absent: + +- return stable empty tables with expected columns +- preserve metadata where possible +- do not raise unless required structure is missing for the helper contract + +### Region helper mode handling + +If `result.region_summaries` is `None`, the region helper should return stable empty outputs rather than failing, because `read_global` clustering does not always produce region summaries. + +## Relationship To Existing `plot_data` + +`SharedClusterResult.plot_data` currently includes lightweight payloads such as: + +- `cluster_distribution_bar` +- `cluster_distribution_heatmap` + +Those should remain in place. + +The new helpers should: + +- preserve compatibility with the existing workflow outputs +- provide a more structured, result-centric plotting-prep interface +- avoid depending on the existing lightweight `plot_data` keys as their public contract + +## Backward Compatibility + +This design is additive. + +- it does not change `shared_cluster_distribution()` +- it does not change `SharedClusterResult` +- it does not remove existing `result.plot_data` + +## Testing + +Add focused tests in [tests/test_plotting.py](../../../tests/test_plotting.py) for: + +- stable distribution payload keys and table propagation +- empty change-table behavior +- profile-table passthrough and metadata +- region helper empty behavior when `region_summaries` is `None` +- condition-level region aggregation behavior + +Use synthetic `SharedClusterResult` fixtures rather than end-to-end clustering runs for most helper tests. + +## Documentation + +Update: + +- [docs/shared-clustering.md](../../../docs/shared-clustering.md) +- [README.md](../../../README.md) + +Document: + +- the three new helpers +- the distinction between the old lightweight `result.plot_data` and the richer helper layer +- the fact that `shared_clustering` now has dedicated renderer-neutral helper prep, not just raw workflow payloads + +## Recommended Implementation Order + +1. Add plotting helper tests for distribution/change prep +2. Implement `prepare_shared_cluster_distribution_data(...)` +3. Add plotting helper tests for profile prep +4. Implement `prepare_shared_cluster_profile_data(...)` +5. Add plotting helper tests for region prep +6. Implement `prepare_shared_cluster_region_data(...)` +7. Update docs + +## Recommendation + +Build `shared_clustering` plotting as a result-centric, data-first layer with three helper families: + +- `prepare_shared_cluster_distribution_data(...)` +- `prepare_shared_cluster_profile_data(...)` +- `prepare_shared_cluster_region_data(...)` + +That brings `shared_clustering` into line with the richer plotting-prep coverage now available for `region_contrasts`, `region_discovery`, and `global_analysis`, while preserving the existing lightweight `result.plot_data` contract. diff --git a/docs/superpowers/specs/2026-04-02-superpowers-docs-cleanup-design.md b/docs/superpowers/specs/2026-04-02-superpowers-docs-cleanup-design.md new file mode 100644 index 0000000..f6728bb --- /dev/null +++ b/docs/superpowers/specs/2026-04-02-superpowers-docs-cleanup-design.md @@ -0,0 +1,191 @@ +# Superpowers Docs Cleanup Design + +## Purpose + +This design defines the first cleanup pass for the internal planning trail under `docs/superpowers`. + +The goal is to make the accumulated specs and plans: + +- tracked in git +- easy to navigate +- clear about what has been implemented versus what remains follow-on work + +This is a docs-first cleanup slice. It does not yet change package code or user-facing APIs. + +## Problem + +The repository now contains a substantial internal design and planning trail: + +- specs under `docs/superpowers/specs` +- plans under `docs/superpowers/plans` + +Some of the plan files exist on disk but are still untracked. The docs set is also missing a simple navigation layer, so it is difficult to tell: + +- which specs belong to which plans +- which plans were executed already +- which slices are still only planned +- which later plans supersede or extend earlier ones + +The cleanup should preserve this history without requiring every historical file to be rewritten. + +## Core Design Rule + +Status should be tracked centrally, not embedded into every spec and plan file. + +This avoids: + +- rewriting historical design docs +- duplicating status information across many files +- future drift between per-file annotations + +The first cleanup slice should therefore introduce one central index file instead of editing every existing file header. + +## Scope + +This cleanup slice should do three things: + +1. track the currently untracked plan files in git +2. add a central navigation/index document for `docs/superpowers` +3. map specs and plans to current implementation status in that index + +It should not: + +- rewrite the historical spec content +- merge or delete old spec/plan files +- introduce a heavy metadata system +- touch code outside documentation cleanup + +## Recommended File Structure + +Add one central index file: + +- [README.md](../README.md) + +Keep the existing layout: + +- `docs/superpowers/specs/` +- `docs/superpowers/plans/` + +Do not reorganize those directories in this slice. + +## Index Content + +The new central index should have four sections: + +### 1. Overview + +Briefly explain what `docs/superpowers` contains: + +- specs are design documents +- plans are executable implementation breakdowns +- statuses in the index reflect the current branch/repo state + +### 2. Specs + +List the existing specs grouped by theme, with a short description and one of: + +- `implemented` +- `partially implemented` +- `planned` + +### 3. Plans + +List the implementation plans grouped by theme, with a short description and one of: + +- `implemented` +- `partially implemented` +- `not started` + +### 4. Current Themes + +Group related work so the trail is readable, for example: + +- shared clustering +- region contrasts +- region discovery +- global analysis +- plotting architecture + +This section can point users to the most relevant spec/plan sequence for each theme. + +## Status Semantics + +The cleanup should use lightweight status labels only. + +Recommended meanings: + +- `implemented` + The planned slice has been completed and corresponding code/docs are in the repo. + +- `partially implemented` + Some downstream or follow-on pieces exist, but the spec/plan family is not fully exhausted. + +- `planned` + The design exists, but implementation for that slice has not yet landed. + +- `not started` + A plan exists but has not been executed. + +These labels should be applied only in the central index. + +## Treatment of Existing Untracked Plans + +The current untracked files under `docs/superpowers/plans` should be committed rather than removed. + +Rationale: + +- they correspond to real design/implementation work +- they are useful historical context +- they make the executed development trail auditable + +This cleanup slice should not try to prune them aggressively. + +## Minimal Editing Principle + +This slice should prefer: + +- adding one new index +- committing the existing plan files as-is + +over: + +- editing every plan/spec header +- retrofitting status fields into older docs +- renaming the existing files + +That keeps the cleanup low-risk and easy to maintain. + +## Implementation Notes + +The index should reflect the current codebase state on the branch where the cleanup is executed. + +That means it should call out, at minimum, the slices that have clearly landed: + +- shared clustering foundations +- region contrasts foundations +- global analysis foundations +- region discovery foundations +- discovery to clustering workflow +- discovery to clustering to contrasts workflow +- cluster occupancy region contrasts +- plotting axis architecture +- region contrasts plotting + +If a broader feature area still has follow-on work remaining, mark the family as `partially implemented` rather than overstating completion. + +## Tests / Verification + +This is docs-only cleanup, so verification can stay lightweight: + +- confirm the new index references the actual existing files +- confirm the untracked plan files are now staged/committed +- confirm there are no broken file paths in the index text + +## Recommended Build Order + +1. Add `docs/superpowers/README.md`. +2. Populate it with grouped spec/plan links and lightweight status labels. +3. Add the currently untracked plan docs to git. +4. Verify the index paths match the actual repo contents. +5. Commit the docs cleanup slice. + diff --git a/docs/superpowers/specs/2026-04-03-single-read-region-contrasts-design.md b/docs/superpowers/specs/2026-04-03-single-read-region-contrasts-design.md new file mode 100644 index 0000000..c634fa8 --- /dev/null +++ b/docs/superpowers/specs/2026-04-03-single-read-region-contrasts-design.md @@ -0,0 +1,384 @@ +# Single-Read Region Contrasts Design + +## Summary + +Extend `dimelo.region_contrasts` with a first `analysis_unit="single_read"` family for +defined-region comparison on extract-backed read data. + +The first version should support two parallel representations: + +- `representation="read_mod_fraction"` +- `representation="read_window_features"` + +Both representations should remain inside `region_contrasts.score_regions(...)`, +reuse the existing explicit `contrast + analysis_unit + representation` contract, +and keep the inferential unit at the sample level rather than treating reads as +independent biological replicates. + +## Goals + +- Add a first `single_read` analysis path to `region_contrasts` without creating a new top-level module. +- Support direct read-level defined-region comparison on top of the existing extract-backed workflows. +- Make it explicit what kind of read quantity is being contrasted. +- Preserve the current `region_contrasts` mental model: + one entry point, explicit contrast semantics, explicit unit of analysis. +- Support two first-class `single_read` representations in parallel: + - `read_mod_fraction` + - `read_window_features` +- Keep the inferential unit sample-aware rather than pooling reads as if they were replicates. +- Support built-in read-window features by default while allowing user-supplied feature tables. +- Support `pairwise`, `group_vs_group`, and `matched_pairwise` in the first version. +- Preserve continuity with the current extract-backed clustering/window feature logic. + +## Non-Goals + +- Create a separate `read_contrasts` module in v1. +- Add `single_read` support for every possible representation in the first release. +- Treat reads as the formal inferential unit across conditions. +- Add time-course or background-adjusted `single_read` contrasts in the first version. +- Build full single-read positional plotting in this slice. +- Replace or redefine existing `ensemble_region` or `cluster_occupancy` behavior. + +## Why `single_read` Belongs Inside `region_contrasts` + +`single_read` is still a defined-region contrast problem: + +- the loci are already known +- the user still needs explicit contrast semantics +- the user still needs explicit unit and representation semantics + +So the right public API is still: + +```python +region_contrasts.score_regions(...) +``` + +with a new: + +- `analysis_unit="single_read"` + +This keeps the package coherent: + +- `ensemble_region` asks whether locus-aggregated abundance changed +- `single_read` asks whether read-level behavior within known loci changed +- `cluster_occupancy` asks whether the mixture of inferred read states changed + +## First-Version Representations + +### `representation="read_mod_fraction"` + +Each read contributes one scalar value per `region x sample`: + +- modified motifs / valid motifs on that read within that region + +This representation is for asking: + +- did the distribution of per-read modification levels shift? +- did read-level heterogeneity change even when the locus average looked similar? + +This is the strongest first `single_read` path because it is: + +- easy to interpret +- close to the current motif-abundance framing +- a natural bridge between `ensemble_region` and richer read-structure analyses + +### `representation="read_window_features"` + +Each read contributes a feature vector per `region x sample`. + +The default first source of features should be the current built-in feature set already +used in clustering/window workflows. The API should also allow a validated user-supplied +feature table override. + +This representation is for asking: + +- did structured read-level summaries change? +- did central-versus-flank, density-like, or shape-like read summaries reorganize? + +This representation should be more flexible than `read_mod_fraction`, but also more +conservative in what it claims inferentially in v1. + +## Supported Contrast Modes In v1 + +The first `single_read` release should support: + +- `pairwise` +- `group_vs_group` +- `matched_pairwise` + +It should not yet support: + +- `time_course` +- `background_adjusted` +- more complex repeated-measures or mixed-model paths + +### Why `matched_pairwise` Is Included Early + +`single_read` summaries become scientifically much cleaner once the inferential unit +stays at the sample level. That makes matched before/after and targeting versus +nontargeting comparisons natural extensions of the first sample-aware design. + +## Replication And Statistical Unit + +The central statistical rule is: + +- reads are observational units +- samples remain the inferential units + +The implementation should therefore follow this pattern: + +1. compute read-level values within each `region x sample` +2. summarize those read-level values into sample-level region statistics +3. compare those sample-level summaries across contrast sides + +This avoids the major failure mode of pooled-read inference: + +- pretending thousands of reads are thousands of biological replicates + +### Consequence + +`analysis_unit="single_read"` does **not** mean: + +- every read is treated as an independent test unit across conditions + +It means: + +- read-level observations are used to build sample-aware contrast summaries + +## Evidence Model + +### Shared Required Columns + +Both `single_read` representations should compile into an evidence table that preserves: + +- `region_id` +- `sample_id` +- `condition` +- `read_id` +- optional `replicate` +- optional pairing metadata copied from the sample metadata when needed + +The evidence table then diverges by representation. + +### `read_mod_fraction` Evidence + +Required columns: + +- `region_id` +- `sample_id` +- `condition` +- `read_id` +- `modified_count` +- `valid_count` +- `read_mod_fraction` + +Optional columns: + +- `replicate` +- sample metadata fields used by pairing or grouping + +### `read_window_features` Evidence + +Required columns: + +- `region_id` +- `sample_id` +- `condition` +- `read_id` +- one or more feature columns + +Optional columns: + +- `replicate` +- sample metadata fields used by pairing or grouping + +## Feature Source Contract + +### Built-In Default + +If the user does not supply a feature table, `single_read` feature contrasts should use +the current built-in feature set already used in extract-backed clustering/window workflows. + +This preserves continuity with the package’s current feature engineering path. + +### User-Supplied Override + +If the user supplies `feature_table=...`, the contrast layer should validate and use that +table instead of the built-in features. + +Required constraints for user feature tables: + +- same `region_id` +- same `sample_id` +- same `condition` +- same `read_id` +- explicit feature columns + +The downstream contrast logic should not need to care whether the feature rows came from +built-in extraction or user-supplied features once validation is complete. + +## API Shape + +The public API should remain: + +```python +result = region_contrasts.score_regions( + samples=samples, + regions=regions, + contrast=contrast_spec, + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="extract_reads", + test="sample_distribution_shift", +) +``` + +and: + +```python +result = region_contrasts.score_regions( + samples=samples, + regions=regions, + contrast=contrast_spec, + analysis_unit="single_read", + representation="read_window_features", + signal_source="extract_features", + test="feature_summary_shift", + feature_table=user_feature_table, # optional +) +``` + +### Required Validation Rules + +`analysis_unit="single_read"` should require: + +- a supported `representation` +- a supported `signal_source` +- a supported `test` +- only supported contrast modes + +## v1 Support Matrix + +### `analysis_unit="single_read"` + +#### `representation="read_mod_fraction"` + +Allowed: + +- `signal_source="extract_reads"` +- `test="effect_size_only"` +- `test="sample_distribution_shift"` +- `contrast.mode in {"pairwise", "group_vs_group", "matched_pairwise"}` + +#### `representation="read_window_features"` + +Allowed: + +- `signal_source="extract_features"` +- `test="effect_size_only"` +- `test="feature_summary_shift"` +- `contrast.mode in {"pairwise", "group_vs_group", "matched_pairwise"}` +- built-in features by default +- user-supplied `feature_table` override + +Disallowed in v1: + +- `time_course` +- `background_adjusted` +- `beta_binomial` +- pooled-read inferential tests + +## Statistical Meaning + +### `read_mod_fraction` + +This representation should support the stronger first inferential `single_read` path. + +Suggested v1 outputs: + +- sample-level mean read fraction +- sample-level median read fraction +- sample-level variance or spread summary +- contrast-side deltas across sample summaries + +`test="sample_distribution_shift"` should be defined at the sample-summary level rather +than on pooled reads. + +### `read_window_features` + +This representation should start more conservatively. + +Suggested v1 outputs: + +- per-feature sample-level means +- per-feature sample-level medians +- per-feature effect-size summaries across contrast sides + +`test="feature_summary_shift"` should primarily be a sample-summary comparison path in v1. +It may be descriptive or lightly inferential, but it should not overstate multivariate +read-feature inference. + +## Result Contract + +`RegionContrastResult` should remain the top-level return type. + +For `analysis_unit="single_read"`, result metadata must explicitly carry: + +- `analysis_unit` +- `representation` +- `signal_source` +- `test` +- `contrast.mode` + +The result tables should make it obvious that the path is sample-aware: + +- read-level evidence table retained where appropriate for traceability +- sample-level summary table +- contrast summary table + +The summary outputs should not look interchangeable with `ensemble_region` summaries. + +## Continuity Requirements + +This extension must remain additive around the current parsing and extract-backed workflow model. + +Required constraints: + +- do not redefine `parse_bam.extract()` +- do not change existing `ensemble_region` or `cluster_occupancy` scoring semantics +- reuse the current extract-backed read or feature outputs where possible +- treat built-in read-window features as the default path for continuity +- keep the explicit `analysis_unit` and `representation` contract rather than inferring behavior + +## Testing Requirements + +The implementation plan for this spec must include: + +- validation tests for legal and illegal `single_read` combinations +- tests for `read_mod_fraction` evidence building +- tests for `read_window_features` evidence building +- tests for built-in feature-source behavior +- tests for user-supplied feature-table validation +- tests for `pairwise`, `group_vs_group`, and `matched_pairwise` +- tests that prove sample-aware summaries are used rather than pooled-read inference +- docs updates describing what these contrasts mean biologically + +## Recommended Implementation Order + +1. add request validation for `analysis_unit="single_read"` +2. implement `read_mod_fraction` evidence building +3. implement `read_mod_fraction` sample-aware scoring +4. implement `read_window_features` evidence building using the built-in feature set +5. add user-supplied `feature_table` support +6. implement `matched_pairwise` support for both tracks +7. update docs and examples + +## Open Items Explicitly Deferred + +These are intentionally not part of the first implementation plan: + +- time-course `single_read` contrasts +- background-adjusted `single_read` contrasts +- full multivariate inferential modeling for feature vectors +- single-read positional plotting +- additional `single_read` representations beyond `read_mod_fraction` and `read_window_features` diff --git a/docs/superpowers/specs/2026-04-08-matplotlib-renderers-design.md b/docs/superpowers/specs/2026-04-08-matplotlib-renderers-design.md new file mode 100644 index 0000000..99be738 --- /dev/null +++ b/docs/superpowers/specs/2026-04-08-matplotlib-renderers-design.md @@ -0,0 +1,249 @@ +# Matplotlib Renderers Design + +## Summary + +Add a first built-in Matplotlib renderer layer on top of the existing renderer-neutral +plot-preparation payloads. + +The goal is to give users a familiar, backward-compatible plotting path without +breaking the data-first architecture that now underpins `region_contrasts`, +`region_discovery`, and `global_analysis`. + +This renderer slice should: + +- keep `dimelo.plotting` as the renderer-neutral prep layer +- add a separate Matplotlib-specific module for figure construction +- support optional file export helpers +- avoid moving biological aggregation logic back into renderer functions + +## Goals + +- Add thin Matplotlib renderers for the most mature plotting payloads. +- Preserve the data-first contract: prep helpers remain the source of truth. +- Provide a simple built-in plotting path for users who prefer Matplotlib. +- Support convenient figure export to image or PDF files. +- Keep the renderer implementation small, explicit, and easy to test. + +## Non-Goals + +- Build a backend plugin/registry system in this slice. +- Add Plotly, Altair, or other non-Matplotlib renderers in this slice. +- Rework the renderer-neutral payload schemas. +- Add new biological summaries or plotting prep behavior just to support rendering. +- Cover every analysis family at once. + +## Why A Separate Matplotlib Module + +The repo now has a clear prep layer in `dimelo.plotting`: + +- prep helpers validate inputs +- prep helpers align coordinates and aggregation semantics +- prep helpers return payload dictionaries/tables + +If Matplotlib rendering is added into the same module, that clean separation will erode +quickly. A separate module keeps responsibilities explicit: + +- `dimelo.plotting`: data prep and renderer-neutral payloads +- `dimelo.plotting_matplotlib`: figure construction and file export + +This also keeps future renderer additions straightforward. + +## First-Version Scope + +The first renderer slice should cover: + +- `region_contrasts` + - profile renderer + - heatmap renderer +- `region_discovery` + - scan renderer + - hit-context renderer +- `global_analysis` + - summary renderer + - window renderer + +It should not yet cover: + +- `shared_clustering` +- full legacy wrapper migration +- new single-read Matplotlib renderers beyond what already exists in legacy plotting modules + +## Public API + +Add a new module: + +- `dimelo.plotting_matplotlib` + +It should expose: + +```python +plot_region_contrast_profile_matplotlib(payload, *, value_mode="delta", ax=None, title=None) +plot_region_contrast_heatmap_matplotlib(payload, *, value_mode="delta", ax=None, title=None) +plot_region_discovery_scan_matplotlib(payload, *, axes=None, title=None) +plot_region_discovery_hit_context_matplotlib(payload, *, axes=None, title=None) +plot_global_analysis_summary_matplotlib(payload, *, level="condition", ax=None, title=None) +plot_global_analysis_window_matplotlib(payload, *, axes=None, title=None) +save_figure(fig, path, *, dpi=300, bbox_inches="tight", transparent=False) +``` + +Exact argument names may vary slightly, but the public contract should stay: + +- renderer takes one prepared payload +- optional Matplotlib axes can be injected +- renderer returns figure/axes +- export helper takes a figure and destination path + +## Renderer Contracts + +### General Rules + +Each renderer should: + +- accept a prepared payload dict from `dimelo.plotting` +- validate only the keys/shapes it needs +- avoid mutating the payload +- avoid recomputing biological summaries already expressed in payload tables +- return `(fig, ax)` or `(fig, axes)` depending on the layout + +Each renderer should not: + +- call `prepare_*` internally from result objects +- infer new biological quantities +- write files automatically + +### Region Contrast Profile Renderer + +Input: + +- payload from `prepare_region_contrast_profile_data(...)` + +Responsibilities: + +- render numerator / denominator / delta views from prepared profile rows +- respect axis labels and metadata from the payload +- support a `value_mode` switch: + - `numerator` + - `denominator` + - `delta` + +The renderer should assume the payload already contains the aligned plot table. + +### Region Contrast Heatmap Renderer + +Input: + +- payload from `prepare_region_contrast_heatmap_data(...)` + +Responsibilities: + +- render a per-region heatmap for one `value_mode` +- preserve region rank order from the payload +- use payload axis metadata for x labels or range annotations where appropriate + +### Region Discovery Scan Renderer + +Input: + +- payload from `prepare_region_discovery_scan_data(...)` + +Responsibilities: + +- render per-contig score tracks by default +- overlay or highlight hits when `hit_table` is non-empty +- keep contigs separated rather than forcing a cumulative pseudo-genome axis + +### Region Discovery Hit-Context Renderer + +Input: + +- payload from `prepare_region_discovery_hit_context_data(...)` + +Responsibilities: + +- render local scored neighborhoods around selected hits +- support small-multiple style layouts by hit + +### Global Analysis Summary Renderer + +Input: + +- payload from `prepare_global_analysis_summary_data(...)` + +Responsibilities: + +- render sample-level or condition-level global summaries +- expose `level="sample"` or `level="condition"` +- optionally show normalization-side quantities from the payload tables + +### Global Analysis Window Renderer + +Input: + +- payload from `prepare_global_analysis_window_data(...)` + +Responsibilities: + +- render per-contig broad-window summaries +- support both sample-level and condition-level window tables where present + +## File Export Helper + +Add: + +```python +save_figure(fig, path, *, dpi=300, bbox_inches="tight", transparent=False) +``` + +Responsibilities: + +- create parent directories if needed +- call `fig.savefig(...)` +- leave closing policy to the caller + +This helper is intentionally thin. It exists for convenience and consistency, not to +become an output manager. + +## Testing Strategy + +Tests should verify: + +- renderer returns Matplotlib figure/axes objects +- renderer handles empty or minimal valid payloads where appropriate +- renderer uses the expected payload table for the selected mode/level +- `save_figure(...)` writes a file successfully + +Tests should not try to enforce pixel-perfect rendering. + +## Documentation Expectations + +User-facing docs should describe the new layer as: + +- optional +- Matplotlib-specific +- built on top of the existing prep payloads + +The docs should preserve the current message: + +- prep helpers are still the canonical interface +- users can still ignore built-in renderers and use their own plotting stack + +## Compatibility + +This slice should be additive: + +- existing payload-prep helpers stay unchanged +- existing legacy Matplotlib plotters remain available +- the new module adds a more coherent built-in rendering surface without removing older paths + +## Implementation Order + +Recommended order: + +1. add `dimelo.plotting_matplotlib` module +2. implement `save_figure(...)` +3. implement region-contrast renderers +4. implement region-discovery renderers +5. implement global-analysis renderers +6. add focused renderer tests +7. update docs and README + diff --git a/docs/superpowers/specs/2026-04-10-shared-clustering-matplotlib-renderers-design.md b/docs/superpowers/specs/2026-04-10-shared-clustering-matplotlib-renderers-design.md new file mode 100644 index 0000000..3b6688b --- /dev/null +++ b/docs/superpowers/specs/2026-04-10-shared-clustering-matplotlib-renderers-design.md @@ -0,0 +1,201 @@ +# Shared Clustering Matplotlib Renderers Design + +## Summary + +Add a `shared_clustering` Matplotlib renderer slice on top of the existing +renderer-neutral payloads in `dimelo.plotting`. + +The goal is to close the built-in renderer gap that remains after the first +Matplotlib slice landed for: + +- `region_contrasts` +- `region_discovery` +- `global_analysis` + +This slice should keep the same architecture: + +- `dimelo.plotting` remains the source of truth for payload preparation +- `dimelo.plotting_matplotlib` remains a thin figure-construction layer +- only small prep-layer adjustments are allowed when current payloads are + awkward to render cleanly + +## Goals + +- Add built-in Matplotlib renderers for the three main `shared_clustering` + plotting families: + - distribution/change + - cluster profiles + - region occupancy +- Keep the renderer layer thin and payload-driven. +- Preserve compatibility with the existing lightweight `result.plot_data` + tables. +- Make condition-level occupancy the default visual story while preserving + sample-level detail in the payload and renderer options. + +## Non-Goals + +- Add new clustering outputs or new clustering algorithms. +- Add new occupancy metrics or new feature extraction logic. +- Rework `SharedClusterResult` semantics. +- Replace the renderer-neutral prep helpers. +- Add non-Matplotlib backends in this slice. + +## Why This Slice Now + +`shared_clustering` now has the renderer-neutral prep layer that the other +analysis families already use: + +- `prepare_shared_cluster_distribution_data(...)` +- `prepare_shared_cluster_profile_data(...)` +- `prepare_shared_cluster_region_data(...)` + +What it does not yet have is the same built-in plotting path that now exists +for the other major result objects. That makes it the most obvious plotting +consistency gap in the repo. + +## Public API + +Add these functions to `dimelo.plotting_matplotlib`: + +```python +plot_shared_cluster_distribution_matplotlib(payload, *, level="condition", ax=None, title=None) +plot_shared_cluster_change_matplotlib(payload, *, ax=None, title=None) +plot_shared_cluster_profile_heatmap_matplotlib(payload, *, ax=None, title=None) +plot_shared_cluster_profile_series_matplotlib(payload, *, ax=None, title=None) +plot_shared_cluster_region_matplotlib(payload, *, level="condition", ax=None, title=None) +``` + +Public contract: + +- renderer takes one prepared payload +- optional Matplotlib axis can be injected +- renderer returns `(fig, ax)` +- renderers do not mutate payloads +- renderers do not recompute clustering results + +## Renderer Families + +### Distribution Renderer + +Input: + +- payload from `prepare_shared_cluster_distribution_data(...)` + +Responsibilities: + +- render sample-level or condition-level cluster fractions +- default to `level="condition"` +- use condition/sample labels from the prepared payload + +This renderer should expose the current cluster-distribution story in a built-in +bar-like form without changing the payload contract. + +### Change Renderer + +Input: + +- payload from `prepare_shared_cluster_distribution_data(...)` + +Responsibilities: + +- render `distribution_change` as a cluster-by-condition-change heatmap or + equivalent matrix-style view +- preserve stable cluster ordering from the payload + +This should give users an immediate built-in view of condition-level occupancy +shifts. + +### Profile Heatmap Renderer + +Input: + +- payload from `prepare_shared_cluster_profile_data(...)` + +Responsibilities: + +- render cluster-by-feature summaries as a heatmap +- act as the default first-class profile view +- preserve cluster and feature ordering from the payload + +This is the best default because profile tables are naturally matrix-shaped and +scale better than many overlaid series. + +### Profile Series Renderer + +Input: + +- payload from `prepare_shared_cluster_profile_data(...)` + +Responsibilities: + +- render a simple per-cluster profile series across features +- serve as the lighter alternate view for small feature sets + +This renderer should stay simple and not attempt to solve every profile layout. + +### Region Occupancy Renderer + +Input: + +- payload from `prepare_shared_cluster_region_data(...)` + +Responsibilities: + +- default to `level="condition"` +- render condition-aggregated region occupancy as a heatmap +- support `level="sample"` to render the sample-level region table instead + +This preserves the recommended interpretation split: + +- default figure tells the condition-level biological story +- sample-level occupancy remains available for replicate inspection and QC + +## Allowed Prep-Layer Adjustments + +Small prep changes are allowed only if rendering exposes a real payload friction. +Examples of acceptable changes: + +- explicit metadata for default sort or label order +- stable ordering columns that are already implicit in the payload +- a small long-to-wide convenience field when it prevents renderer-side data + reshaping from becoming too opaque + +Not acceptable: + +- new clustering outputs +- new summary statistics +- new biological interpretation layers + +## Testing Strategy + +Tests should verify: + +- each renderer returns Matplotlib figure/axis objects +- each renderer consumes the expected payload family +- default level selection behaves as intended +- simple sample/condition switching works where supported +- empty or minimal valid payloads are handled where the prep layer allows them + +Tests should not try to enforce pixel-perfect output. + +## Documentation Expectations + +User-facing docs should describe these renderers as: + +- optional built-in Matplotlib views on top of prepared clustering payloads +- consistent with the earlier Matplotlib renderer slice +- complementary to the older lightweight `result.plot_data` tables, not a + replacement for them + +## Recommendation + +Implement this as one bounded slice: + +1. add failing renderer tests +2. allow only narrowly justified prep tweaks if the payloads are awkward +3. add the five renderers above +4. document the new built-in views in `docs/shared-clustering.md` and + `README.md` + +This keeps the plotting architecture coherent while filling the last major +built-in renderer gap among the current high-level analysis families. diff --git a/docs/superpowers/specs/2026-04-12-shared-cluster-tests-design.md b/docs/superpowers/specs/2026-04-12-shared-cluster-tests-design.md new file mode 100644 index 0000000..4984575 --- /dev/null +++ b/docs/superpowers/specs/2026-04-12-shared-cluster-tests-design.md @@ -0,0 +1,449 @@ +# Shared Cluster Tests Design + +## Summary + +Add a new inference layer for shared clustering that stays separate from +`workflows.shared_cluster_distribution(...)` and consumes an existing +`SharedClusterResult`. + +This layer should answer two related but distinct questions: + +- global cluster-composition change across conditions or timepoints +- per-region occupancy change through the existing `region_contrasts` path + +The new work in this spec is the global composition side. Region-level occupancy +inference should remain in `region_contrasts.score_regions(...)`, which already +accepts `analysis_unit="cluster_occupancy"` and consumes +`SharedClusterResult.region_summaries`. + +## Goals + +- Add a user-facing inference entry point for global shared-cluster composition. +- Reuse the existing `ContrastSpec` model for package-wide consistency. +- Keep replicate-aware testing as the default inferential path. +- Support pooled count tests such as chi-squared only as explicit screening + options. +- Support both pairwise/group contrasts and ordered time-course contrasts. +- Support both unpaired and paired designs through explicit `pairing_key` + declarations. +- Return a result object shaped similarly to current contrast-style results. +- Provide renderer-neutral `plot_data` payloads for summary and per-cluster + follow-up views. + +## Non-Goals + +- Do not add inferential testing directly into `shared_cluster_distribution(...)`. +- Do not replace or subsume `region_contrasts` region-level occupancy inference. +- Do not add formal v1 testing for `dominant_cluster` or `cluster_entropy`. +- Do not implement a batch multi-contrast API in v1. +- Do not add Matplotlib renderers in the same slice unless a later plan + explicitly calls for them. +- Do not add model-heavy compositional methods such as Dirichlet-multinomial + regression in v1. + +## Why A Separate Shared-Cluster Test Layer + +`SharedClusterResult` is currently descriptive. It stores assignments, +distribution summaries, feature profiles, and optional region occupancy tables. +That is the correct boundary for the clustering workflow itself. + +Formal testing should sit downstream for the same reasons the plotting layer now +sits downstream: + +- clustering should remain a reusable descriptive artifact +- testing has different assumptions and failure modes than clustering +- users should be able to choose descriptive use only, inferential use only, or + both + +The design should therefore add a new analysis helper rather than overloading +the workflow that constructs `SharedClusterResult`. + +## Recommended API Shape + +Add a new public entry point: + +```python +shared_cluster_tests( + *, + result: SharedClusterResult, + contrast: ContrastSpec, + test: str = "permutation", + multiple_testing: str = "fdr_bh", + n_permutations: int = 1000, + random_state: int | None = 42, + include_pairwise: bool = False, +) -> SharedClusterContrastResult +``` + +Exact keyword names can vary slightly, but the public contract should stay: + +- one `SharedClusterResult` per call +- one `ContrastSpec` per call +- explicit test selection +- optional paired structure declared explicitly inside `ContrastSpec` +- one result object with canonical tables plus `plot_data` + +## Why Reuse `ContrastSpec` + +The package already has a contrast vocabulary in `dimelo.models.ContrastSpec`: + +- `pairwise` +- `matched_pairwise` +- `group_vs_group` +- `time_course` +- `pairing_key` + +Reusing `ContrastSpec` is the least confusing option because users do not need a +second contrast schema for shared-clustering inference. + +Shared-cluster testing should still validate which modes are supported. In v1: + +- supported: + - `pairwise` + - `matched_pairwise` + - `group_vs_group` + - `time_course` +- unsupported: + - `background_adjusted` + - `single_dataset` + +`ContrastSpec.pairing_key` should be the only pairing declaration surface for +shared-cluster testing. The inference entry point should not add a second +parallel `pairing_key` argument. + +## Analysis Boundaries + +### Global Composition Testing + +This new layer should test condition- or time-associated changes in overall +cluster composition using: + +- `result.cluster_distribution` for sample-level cluster fractions +- `result.condition_distribution` for descriptive summaries + +The biological question is: + +- do samples differ consistently in their cluster mixture? + +This is distinct from per-region occupancy testing. + +### Region Occupancy Testing + +Region occupancy inference should remain in `region_contrasts.score_regions(...)` +with: + +- `analysis_unit="cluster_occupancy"` +- `signal_source="cluster_occupancy"` +- `occupancy_table=result.region_summaries` + +For v1 robustness: + +- formal inference remains focused on `representation="cluster_fraction"` +- `dominant_cluster` and `cluster_entropy` remain descriptive + +This preserves existing architecture and avoids a confusing “one function does +everything” surface. + +## Statistical Policy + +### Default Inference Policy + +Formal inference should be replicate-aware by default. + +Reasoning: + +- pooled read counts are not biological replicates +- pooled tests can become misleadingly significant with deep read counts +- sample-level cluster fractions better reflect biological variability + +Therefore: + +- default v1 global test path should operate at the sample level +- pooled count tests are allowed only as explicit screening options + +### Pooled Screening Tests + +Allowed only when the user explicitly requests them: + +- `chi_squared` +- `g_test` + +These tests should be clearly labeled in result metadata as pooled count +screening analyses, not the default primary inference result when replicates are +available. + +### Replicate-Aware Primary Tests + +The recommended v1 primary test family is permutation-based inference on +sample-level cluster fractions. + +Reasoning: + +- more robust than asymptotic pooled-count tests +- more flexible for awkward cluster-fraction distributions +- easier to extend to paired and time-course designs +- easier to explain than model-heavy compositional approaches + +V1 should support: + +- omnibus permutation testing for global composition change +- per-cluster permutation follow-up tests + +### Deferred Model-Based Methods + +The following should be deferred to a later version: + +- Dirichlet-multinomial regression +- multinomial logistic regression +- more complex mixed models for repeated measures + +Those methods may become useful later, but they add implementation and +interpretation complexity that is unnecessary for the first version. + +## Supported Contrast Families + +### Pairwise And Group-Vs-Group + +Support: + +- unpaired `pairwise` +- paired `matched_pairwise` +- `group_vs_group` + +The default result should include: + +- omnibus composition-level inference +- per-cluster follow-up inference + +### Time-Course + +Support ordered time-course testing in v1. + +This should include two distinct outputs: + +- ordered trend test +- omnibus “any timepoint differs” test + +Optional pairwise follow-up may be included when explicitly requested, but it +should not be the primary headline result. + +### Paired Longitudinal Designs + +Paired time-course support should be allowed when a valid explicit pairing +structure is supplied. + +The implementation does not need to solve every longitudinal model in v1; it +does need to: + +- validate matched structure explicitly +- apply a paired-compatible permutation or resampling scheme +- fail clearly when the requested design is unsupported by the observed sample + layout + +## Pairing Declaration + +Pairing should be declared explicitly through a `pairing_key`, not inferred from +sample IDs and not hidden inside condition group definitions. + +The least confusing and most robust policy is: + +- unpaired analyses: `pairing_key is None` +- paired analyses: `pairing_key` names a metadata field or canonical sample + column identifying matched subjects/blocks + +The implementation should validate: + +- all requested paired samples have non-null pairing keys +- each matched pair/block has the required conditions or timepoints +- no ambiguous duplicate mapping exists within a pair/block for the requested + test + +## Result Object Shape + +Add a new result model: + +```python +SharedClusterContrastResult +``` + +It should follow the same broad style as current contrast-style outputs: + +- `summary` +- `details` +- optional `pairwise` +- `plot_data` +- `metadata` + +### `summary` + +This is the headline table. + +For pairwise/group contrasts it should usually have one row per contrast. + +Recommended fields: + +- `contrast_id` +- `mode` +- `test` +- `composition_effect_size` +- `effect_size_metric` +- `omnibus_p_value` +- `omnibus_adjusted_p_value` when relevant +- `trend_p_value` when relevant +- `top_cluster` +- `top_cluster_delta_fraction` +- `numerator_replicate_n` +- `denominator_replicate_n` +- `rank` + +### `details` + +This is the main per-cluster follow-up table. + +Recommended fields for pairwise/group contrasts: + +- `cluster` +- `fraction` +- `reference_fraction` +- `delta_fraction` +- `log2_fc` +- `effect_size` +- `p_value` +- `adjusted_p_value` +- `numerator_replicate_n` +- `denominator_replicate_n` +- `rank` + +For time-course, `details` may additionally include: + +- `timepoint` +- `mean_fraction` +- `trend_statistic` +- `trend_p_value` + +### `pairwise` + +Optional table produced only when requested for time-course or multi-group +follow-up. + +This should stay secondary. Users should not have to read `pairwise` to answer +the primary question. + +### `metadata` + +This should capture: + +- test family +- permutation count +- random seed when used +- multiple-testing policy +- whether the analysis is pooled or replicate-aware +- whether the design is paired +- warnings about missing replicates, degenerate clusters, or unsupported + follow-up paths + +## Effect Size Policy + +The most interpretable top-level effect size for global composition is a single +overall composition-distance metric. + +V1 should use a simple, explainable measure such as: + +- total variation distance / L1-derived composition distance + +This is preferable to leading with only a p-value or only the top changed +cluster. + +The top-level summary should therefore combine: + +- one overall composition effect size +- one overall inferential result +- one concise cluster-level highlight such as `top_cluster` + +## Plot Data + +The result should include renderer-neutral `plot_data`. + +V1 should provide payloads for both: + +- global summary views +- per-cluster follow-up views + +Recommended payload families: + +- `summary_table` + - headline contrast-level rows for report-card style summaries +- `cluster_effect_table` + - per-cluster fractions, deltas, effect sizes, p-values, adjusted p-values +- `time_course_table` + - per-timepoint cluster fractions when `contrast.mode == "time_course"` +- optional `pairwise_table` + - only when pairwise follow-up is explicitly requested + +These payloads should remain renderer-neutral so later Matplotlib or external +plotting code can consume them directly. + +## Validation Rules + +The implementation should fail early and clearly for: + +- unsupported `ContrastSpec.mode` +- missing required tables on `SharedClusterResult` +- missing requested conditions or timepoints +- paired analyses without valid `pairing_key` +- paired analyses with incomplete or ambiguous matching +- replicate-aware tests requested with too few replicates +- unsupported `multiple_testing` policy +- unsupported `test` choice for the requested contrast/design + +Where possible, the function should return descriptive effect sizes even when a +formal p-value path is unavailable, but only if that behavior is explicit in the +API and metadata. + +## Documentation Expectations + +User-facing docs should make three boundaries explicit: + +- `shared_cluster_distribution(...)` remains descriptive +- `shared_cluster_tests(...)` handles global composition inference +- `region_contrasts.score_regions(..., analysis_unit="cluster_occupancy")` + remains the route for per-region occupancy inference + +The docs should also be explicit that: + +- pooled chi-squared or G-test is screening-oriented +- replicate-aware tests are the default inferential path +- region-level formal testing in v1 focuses on `cluster_fraction` + +## Implementation Order + +Recommended order: + +1. add the new result model +2. add failing tests for global shared-cluster inference entry point +3. implement validation and sample-level summary helpers +4. implement pairwise/group replicate-aware permutation tests +5. implement pooled chi-squared / G-test screening option +6. implement time-course omnibus and trend paths +7. add `plot_data` +8. add docs + +## Recommended Approach + +Three approaches were considered: + +1. add a dedicated `shared_cluster_tests(...)` layer +2. extend `shared_cluster_distribution(...)` directly with testing +3. fold global composition testing into `region_contrasts` + +Approach 1 is recommended because it: + +- preserves the descriptive boundary of clustering +- matches the existing result-centric architecture +- keeps global composition and per-region occupancy conceptually separate +- allows reuse of `ContrastSpec` without overloading unrelated region-level + semantics + +Approaches 2 and 3 would be more confusing because they would mix descriptive +workflow construction with inference, or mix global and region-level biology +under one API surface. diff --git a/environment.yml b/environment.yml index d74efd5..99af718 100644 --- a/environment.yml +++ b/environment.yml @@ -1,8 +1,13 @@ name: dimelo-toolkit channels: - conda-forge + - bioconda - nanoporetech - defaults dependencies: - python=3.11 - - nanoporetech::modkit==0.2.4 \ No newline at end of file + - pip + - libcurl + # Project default for parsing/DMR workflows and notebooks. + - nanoporetech::modkit==0.6.1 + - bioconda::crossmap=0.7.3 diff --git a/modkit_update_tests.ipynb b/modkit_update_tests.ipynb new file mode 100644 index 0000000..9003c44 --- /dev/null +++ b/modkit_update_tests.ipynb @@ -0,0 +1,436 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "36fd64e1", + "metadata": {}, + "source": [ + "# Modkit Update Testing: v0.2.4 vs v0.6.1\n", + "\n", + "This notebook systematically tests the compatibility and performance of updating modkit from the pinned version (0.2.4) to the latest (0.6.1) in the dimelo toolkit.\n", + "\n", + "## Objectives\n", + "- Compare raw modkit outputs for pileup and extract commands\n", + "- Test dimelo integration (parse_bam functions and plotting)\n", + "- Measure performance improvements\n", + "- Identify any breaking changes or incompatibilities\n", + "\n", + "## Setup\n", + "First, ensure both modkit versions are available. We'll modify the environment to allow switching between versions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b970ec8", + "metadata": {}, + "outputs": [], + "source": [ + "# Notebook setup (autonomous, no in-cell environment mutation)\n", + "import json\n", + "import gzip\n", + "import hashlib\n", + "import re\n", + "import shutil\n", + "import subprocess\n", + "import time\n", + "from glob import glob\n", + "from pathlib import Path\n", + "from typing import Dict, List, Tuple\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "# Optional plotting style\n", + "try:\n", + " import seaborn as sns # type: ignore\n", + " sns.set_style(\"whitegrid\")\n", + "except Exception:\n", + " pass\n", + "\n", + "plt.rcParams[\"figure.figsize\"] = (11, 6)\n", + "\n", + "ROOT = Path(\".\").resolve()\n", + "TEST_DATA_DIR = ROOT / \"dimelo\" / \"test\" / \"data\"\n", + "BAM_FILE = TEST_DATA_DIR / \"ctcf_demo.sorted.bam\"\n", + "BAM_INDEX = TEST_DATA_DIR / \"ctcf_demo.sorted.bam.bai\"\n", + "OUTPUT_DIR = ROOT / \"cache\" / \"modkit_benchmark\"\n", + "OUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n", + "\n", + "BENCH_REGION = \"chr1\"\n", + "THREADS = 2\n", + "BENCH_RUNS = 3\n", + "\n", + "if not BAM_FILE.exists() or not BAM_INDEX.exists():\n", + " raise FileNotFoundError(f\"Missing BAM test assets under {TEST_DATA_DIR}\")\n", + "\n", + "print(f\"BAM: {BAM_FILE}\")\n", + "print(f\"Output dir: {OUTPUT_DIR}\")\n", + "print(f\"Region benchmark target: {BENCH_REGION}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "9819cca5", + "metadata": {}, + "source": [ + "## Modkit Version Management\n", + "\n", + "We'll create a way to switch between modkit versions by temporarily modifying the PATH." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a9935c54", + "metadata": {}, + "outputs": [], + "source": [ + "def _parse_modkit_version(text: str) -> str | None:\n", + " match = re.search(r\"(\\d+\\.\\d+\\.\\d+)\", text)\n", + " return match.group(1) if match else None\n", + "\n", + "\n", + "def discover_modkit_versions() -> Dict[str, str]:\n", + " \"\"\"Discover installed modkit binaries and map version -> binary path.\"\"\"\n", + " candidates: set[str] = set()\n", + "\n", + " active = shutil.which(\"modkit\")\n", + " if active:\n", + " candidates.add(active)\n", + "\n", + " conda = shutil.which(\"conda\")\n", + " if conda:\n", + " conda_base = Path(conda).resolve().parents[1]\n", + " for pattern in (\"pkgs/modkit-*/bin/modkit\", \"envs/*/bin/modkit\"):\n", + " for p in conda_base.glob(pattern):\n", + " candidates.add(str(p))\n", + "\n", + " version_to_path: Dict[str, str] = {}\n", + " for binary in sorted(candidates):\n", + " try:\n", + " result = subprocess.run([binary, \"--version\"], capture_output=True, text=True, check=True)\n", + " except Exception:\n", + " continue\n", + " version = _parse_modkit_version((result.stdout + result.stderr).strip())\n", + " if version and version not in version_to_path:\n", + " version_to_path[version] = binary\n", + "\n", + " return version_to_path\n", + "\n", + "\n", + "MODKIT_VERSIONS = discover_modkit_versions()\n", + "PREFERRED_ORDER = [\"0.2.4\", \"0.6.1\"]\n", + "TEST_VERSIONS = [v for v in PREFERRED_ORDER if v in MODKIT_VERSIONS]\n", + "if not TEST_VERSIONS:\n", + " TEST_VERSIONS = sorted(MODKIT_VERSIONS.keys())\n", + "\n", + "print(\"Detected modkit backends:\")\n", + "for version in TEST_VERSIONS:\n", + " print(f\" {version}: {MODKIT_VERSIONS[version]}\")\n", + "\n", + "if len(TEST_VERSIONS) < 2:\n", + " print(\"WARNING: Only one backend detected; cross-version comparisons will be limited.\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "de63818e", + "metadata": {}, + "source": [ + "## Raw Modkit Output Comparison\n", + "\n", + "Test pileup and extract commands directly with both versions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a038eaf", + "metadata": {}, + "outputs": [], + "source": [ + "def _count_lines(path: Path) -> int:\n", + " if path.suffix == \".gz\":\n", + " with gzip.open(path, \"rt\") as handle:\n", + " return sum(1 for _ in handle)\n", + " with path.open(\"rt\") as handle:\n", + " return sum(1 for _ in handle)\n", + "\n", + "\n", + "def _sha256_file(path: Path) -> str:\n", + " digest = hashlib.sha256()\n", + " with path.open(\"rb\") as handle:\n", + " while True:\n", + " chunk = handle.read(1024 * 1024)\n", + " if not chunk:\n", + " break\n", + " digest.update(chunk)\n", + " return digest.hexdigest()\n", + "\n", + "\n", + "def run_modkit_pileup(version: str, *, run_label: str = \"single\") -> Dict:\n", + " \"\"\"Run a standardized pileup command for one backend and return metrics.\"\"\"\n", + " binary = MODKIT_VERSIONS[version]\n", + " output_file = OUTPUT_DIR / f\"pileup_{version}_{run_label}.bed\"\n", + "\n", + " cmd = [\n", + " binary,\n", + " \"pileup\",\n", + " str(BAM_FILE),\n", + " str(output_file),\n", + " \"--threads\",\n", + " str(THREADS),\n", + " \"--region\",\n", + " BENCH_REGION,\n", + " ]\n", + "\n", + " # v0.2.4 needs this for this BAM's implicit tags; v0.6.1 rejects it.\n", + " if version.startswith(\"0.2.\"):\n", + " cmd.append(\"--force-allow-implicit\")\n", + "\n", + " t0 = time.perf_counter()\n", + " proc = subprocess.run(cmd, capture_output=True, text=True)\n", + " runtime_s = time.perf_counter() - t0\n", + "\n", + " metrics = {\n", + " \"version\": version,\n", + " \"run_label\": run_label,\n", + " \"runtime_s\": runtime_s,\n", + " \"exit_code\": proc.returncode,\n", + " \"output_file\": str(output_file),\n", + " \"rows\": _count_lines(output_file) if output_file.exists() and proc.returncode == 0 else 0,\n", + " \"sha256\": _sha256_file(output_file) if output_file.exists() and proc.returncode == 0 else None,\n", + " \"stderr_tail\": \"\\n\".join(proc.stderr.strip().splitlines()[-6:]),\n", + " }\n", + " return metrics\n", + "\n", + "\n", + "single_run_metrics = []\n", + "for version in TEST_VERSIONS:\n", + " metrics = run_modkit_pileup(version, run_label=\"single\")\n", + " single_run_metrics.append(metrics)\n", + "\n", + "single_run_df = pd.DataFrame(single_run_metrics)\n", + "display(single_run_df[[\"version\", \"runtime_s\", \"rows\", \"exit_code\", \"output_file\"]])\n", + "\n", + "comparison = None\n", + "if {\"0.2.4\", \"0.6.1\"}.issubset(set(TEST_VERSIONS)):\n", + " m024 = next(row for row in single_run_metrics if row[\"version\"] == \"0.2.4\")\n", + " m061 = next(row for row in single_run_metrics if row[\"version\"] == \"0.6.1\")\n", + " comparison = {\n", + " \"row_delta\": int(m061[\"rows\"] - m024[\"rows\"]),\n", + " \"row_delta_pct_vs_024\": (float(m061[\"rows\"] - m024[\"rows\"]) / float(m024[\"rows\"]) * 100.0) if m024[\"rows\"] else None,\n", + " \"outputs_identical_sha256\": bool(m061[\"sha256\"] == m024[\"sha256\"]),\n", + " }\n", + " print(\"Single-run comparison:\", comparison)\n" + ] + }, + { + "cell_type": "markdown", + "id": "ea26f007", + "metadata": {}, + "source": [ + "## Dimelo Integration Tests\n", + "\n", + "Test parse_bam functions and plotting with both modkit versions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e48291ba", + "metadata": {}, + "outputs": [], + "source": [ + "# Optional dimelo compatibility smoke-check\n", + "# This notebook avoids hard dependency on dimelo optional extras; this block is best-effort.\n", + "\n", + "dimelo_smoke = {\"available\": False, \"note\": \"dimelo import not attempted\"}\n", + "try:\n", + " import dimelo # noqa: F401\n", + " dimelo_smoke[\"available\"] = True\n", + " dimelo_smoke[\"note\"] = \"dimelo import succeeded\"\n", + "except Exception as exc:\n", + " dimelo_smoke[\"available\"] = False\n", + " dimelo_smoke[\"note\"] = f\"dimelo import unavailable in this kernel: {type(exc).__name__}: {exc}\"\n", + "\n", + "print(dimelo_smoke[\"note\"])\n" + ] + }, + { + "cell_type": "markdown", + "id": "9e62e6e0", + "metadata": {}, + "source": [ + "## Performance Analysis\n", + "\n", + "Detailed performance comparison with multiple runs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de59b4ce", + "metadata": {}, + "outputs": [], + "source": [ + "def benchmark_modkit(version: str, runs: int = BENCH_RUNS) -> pd.DataFrame:\n", + " records = []\n", + " for i in range(runs):\n", + " records.append(run_modkit_pileup(version, run_label=f\"bench{i+1}\"))\n", + " return pd.DataFrame(records)\n", + "\n", + "\n", + "bench_results: Dict[str, pd.DataFrame] = {}\n", + "for version in TEST_VERSIONS:\n", + " bench_results[version] = benchmark_modkit(version, runs=BENCH_RUNS)\n", + "\n", + "benchmark_df = pd.concat(bench_results.values(), ignore_index=True)\n", + "display(benchmark_df[[\"version\", \"run_label\", \"runtime_s\", \"rows\", \"exit_code\"]])\n", + "\n", + "runtime_summary = (\n", + " benchmark_df.groupby(\"version\", as_index=False)\n", + " .agg(\n", + " runtime_mean_s=(\"runtime_s\", \"mean\"),\n", + " runtime_std_s=(\"runtime_s\", \"std\"),\n", + " runtime_min_s=(\"runtime_s\", \"min\"),\n", + " runtime_max_s=(\"runtime_s\", \"max\"),\n", + " )\n", + ")\n", + "display(runtime_summary)\n", + "\n", + "fig, ax = plt.subplots(1, 1, figsize=(8, 4))\n", + "for version, frame in bench_results.items():\n", + " ax.plot(frame.index + 1, frame[\"runtime_s\"], marker=\"o\", label=version)\n", + "ax.set_xlabel(\"Benchmark run\")\n", + "ax.set_ylabel(\"Runtime (s)\")\n", + "ax.set_title(f\"modkit pileup runtime by backend ({BENCH_REGION})\")\n", + "ax.legend()\n", + "plt.tight_layout()\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "721119b6", + "metadata": {}, + "source": [ + "## Edge Cases and Compatibility Checks\n", + "\n", + "Test specific scenarios that might reveal differences." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "180f0d63", + "metadata": {}, + "outputs": [], + "source": [ + "# Edge-case / interface compatibility checks\n", + "\n", + "def supports_flag(version: str, flag: str) -> bool:\n", + " binary = MODKIT_VERSIONS[version]\n", + " help_text = subprocess.run([binary, \"pileup\", \"--help\"], capture_output=True, text=True).stdout\n", + " return flag in help_text\n", + "\n", + "\n", + "edge_case_rows = []\n", + "for version in TEST_VERSIONS:\n", + " edge_case_rows.append(\n", + " {\n", + " \"version\": version,\n", + " \"supports_force_allow_implicit\": supports_flag(version, \"--force-allow-implicit\"),\n", + " \"supports_modified_bases\": supports_flag(version, \"--modified-bases\"),\n", + " \"supports_reference_long_opt\": supports_flag(version, \"--reference\"),\n", + " \"supports_reference_short_opt\": supports_flag(version, \"--ref\"),\n", + " }\n", + " )\n", + "\n", + "edge_case_df = pd.DataFrame(edge_case_rows)\n", + "display(edge_case_df)\n" + ] + }, + { + "cell_type": "markdown", + "id": "b5423112", + "metadata": {}, + "source": [ + "## Summary and Recommendations\n", + "\n", + "Compile all findings into a structured report." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "393269d2", + "metadata": {}, + "outputs": [], + "source": [ + "# Compile summary and recommendations\n", + "\n", + "summary_payload: Dict[str, object] = {\n", + " \"versions_tested\": TEST_VERSIONS,\n", + " \"single_run\": single_run_df.to_dict(orient=\"records\"),\n", + " \"benchmark_runs\": benchmark_df.to_dict(orient=\"records\") if \"benchmark_df\" in globals() else [],\n", + " \"runtime_summary\": runtime_summary.to_dict(orient=\"records\") if \"runtime_summary\" in globals() else [],\n", + " \"pileup_comparison\": comparison,\n", + " \"edge_case_flags\": edge_case_df.to_dict(orient=\"records\") if \"edge_case_df\" in globals() else [],\n", + " \"compatibility_issues\": [],\n", + " \"recommendations\": [],\n", + "}\n", + "\n", + "if comparison is not None and not comparison.get(\"outputs_identical_sha256\", True):\n", + " summary_payload[\"compatibility_issues\"].append(\n", + " \"Pileup outputs differ across versions (non-identical output hash).\"\n", + " )\n", + "\n", + "if {\"0.2.4\", \"0.6.1\"}.issubset(set(TEST_VERSIONS)) and \"runtime_summary\" in globals():\n", + " rt = {row[\"version\"]: row[\"runtime_mean_s\"] for row in runtime_summary.to_dict(orient=\"records\")}\n", + " v024 = rt.get(\"0.2.4\")\n", + " v061 = rt.get(\"0.6.1\")\n", + " if v024 and v061:\n", + " delta_pct = (v024 - v061) / v024 * 100.0\n", + " if delta_pct > 0:\n", + " summary_payload[\"recommendations\"].append(\n", + " f\"Prefer modkit 0.6.1 for pileup runtime in this workflow (~{delta_pct:.1f}% faster on average).\"\n", + " )\n", + " else:\n", + " summary_payload[\"recommendations\"].append(\n", + " f\"No runtime win from 0.6.1 observed in this benchmark ({-delta_pct:.1f}% slower).\"\n", + " )\n", + "\n", + "# Flag version-specific command behavior\n", + "if \"edge_case_df\" in globals() and not edge_case_df.empty:\n", + " flag_024 = edge_case_df.loc[edge_case_df[\"version\"] == \"0.2.4\", \"supports_force_allow_implicit\"]\n", + " flag_061 = edge_case_df.loc[edge_case_df[\"version\"] == \"0.6.1\", \"supports_force_allow_implicit\"]\n", + " if not flag_024.empty and not flag_061.empty and bool(flag_024.iloc[0]) and not bool(flag_061.iloc[0]):\n", + " summary_payload[\"recommendations\"].append(\n", + " \"Backend-aware command building is required: use --force-allow-implicit for 0.2.4 only.\"\n", + " )\n", + "\n", + "results_path = ROOT / \"modkit_test_results.json\"\n", + "with results_path.open(\"w\") as handle:\n", + " json.dump(summary_payload, handle, indent=2)\n", + "\n", + "print(\"Results saved to\", results_path)\n", + "print(json.dumps(summary_payload, indent=2)[:4000])\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/scripts/bootstrap_dimelo_env.sh b/scripts/bootstrap_dimelo_env.sh new file mode 100755 index 0000000..334d24e --- /dev/null +++ b/scripts/bootstrap_dimelo_env.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_NAME="${1:-dimelo-toolkit}" +KERNEL_NAME="${2:-dimelo-test}" +KERNEL_DISPLAY_NAME="${3:-Python (${KERNEL_NAME})}" +MODKIT_VERSION="${4:-}" + +if ! command -v conda >/dev/null 2>&1; then + echo "ERROR: conda is not on PATH." + echo "Install conda/mambaforge first, then rerun this script." + exit 1 +fi + +if command -v mamba >/dev/null 2>&1; then + ENV_TOOL="mamba" +else + ENV_TOOL="conda" +fi + +echo "==> Using ${ENV_TOOL} to provision environment '${ENV_NAME}'" +if conda env list | awk '{print $1}' | grep -Fxq "${ENV_NAME}"; then + CONDA_CHANNEL_PRIORITY=flexible \ + "${ENV_TOOL}" env update -n "${ENV_NAME}" -f "${REPO_ROOT}/environment.yml" --prune +else + CONDA_CHANNEL_PRIORITY=flexible \ + "${ENV_TOOL}" env create -n "${ENV_NAME}" -f "${REPO_ROOT}/environment.yml" +fi + +echo "==> Installing dimelo-toolkit in editable mode with clustering extras" +conda run -n "${ENV_NAME}" python -m pip install --upgrade pip +conda run -n "${ENV_NAME}" python -m pip install -e "${REPO_ROOT}[clustering]" +conda run -n "${ENV_NAME}" python -m pip install pytest pre-commit nbformat + +if [[ -n "${MODKIT_VERSION}" ]]; then + echo "==> Installing modkit ${MODKIT_VERSION} in '${ENV_NAME}'" + "${ENV_TOOL}" install -n "${ENV_NAME}" -y "nanoporetech::modkit==${MODKIT_VERSION}" +fi + +echo "==> Registering Jupyter kernel '${KERNEL_NAME}'" +conda run -n "${ENV_NAME}" python -m ipykernel install \ + --user \ + --name "${KERNEL_NAME}" \ + --display-name "${KERNEL_DISPLAY_NAME}" + +echo "==> Validating environment health" +conda run -n "${ENV_NAME}" python "${REPO_ROOT}/scripts/ensure_dimelo_kernel.py" \ + --modkit-version "${MODKIT_VERSION:-0.6.1}" \ + --expected-env "${ENV_NAME}" + +cat < +EOF diff --git a/scripts/ensure_dimelo_kernel.py b/scripts/ensure_dimelo_kernel.py new file mode 100644 index 0000000..4668ede --- /dev/null +++ b/scripts/ensure_dimelo_kernel.py @@ -0,0 +1,368 @@ +#!/usr/bin/env python3 +""" +Verify that the active Python/kernel environment can run the DiMeLo notebooks. + +Usage: + python scripts/ensure_dimelo_kernel.py + python scripts/ensure_dimelo_kernel.py --fix + python scripts/ensure_dimelo_kernel.py --fix --skip-modkit + python scripts/ensure_dimelo_kernel.py --expected-env dimelo-toolkit +""" + +from __future__ import annotations + +import argparse +import importlib.util +import os +import shutil +import subprocess +import sys +from dataclasses import dataclass +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +DEFAULT_MODKIT_REQUIREMENT = "0.6.1" +DEFAULT_MODKIT_INSTALL_VERSION = "0.6.1" +SUPPORTED_MODKIT_PREFIXES = ("0.6.",) + +# setup.py install_requires +CORE_MODULES = { + "numpy": "numpy", + "seaborn": "seaborn", + "pysam": "pysam", + "h5py": "h5py", + "pyBigWig": "pyBigWig", + "notebook": "notebook", + "ipykernel": "ipykernel", + "ipywidgets": "ipywidgets", + "tqdm": "tqdm", + "plotly": "plotly", + "kaleido": "kaleido", +} + +# setup.py extras_require["clustering"] +CLUSTERING_MODULES = { + "scikit-learn": "sklearn", + "scipy": "scipy", + "hdbscan": "hdbscan", + "umap-learn": "umap", + "pyranges": "pyranges", + "xgboost": "xgboost", +} + + +@dataclass +class CheckReport: + missing_core: list[str] + missing_clustering: list[str] + runtime_failures: dict[str, str] + missing_modkit: bool + modkit_version: str | None + missing_crossmap: bool + crossmap_executable: str | None + conda_env: str | None + expected_env: str | None + + @property + def ok(self) -> bool: + env_ok = self.expected_env is None or self.conda_env == self.expected_env + return ( + len(self.missing_core) == 0 + and len(self.missing_clustering) == 0 + and len(self.runtime_failures) == 0 + and not self.missing_modkit + and not self.missing_crossmap + and env_ok + ) + + +def _module_installed(module_name: str) -> bool: + return importlib.util.find_spec(module_name) is not None + + +def _check_python_modules() -> tuple[list[str], list[str]]: + missing_core = [ + package + for package, module in CORE_MODULES.items() + if not _module_installed(module) + ] + missing_clustering = [ + package + for package, module in CLUSTERING_MODULES.items() + if not _module_installed(module) + ] + return missing_core, missing_clustering + + +def _modkit_version_ok(version_text: str, required_version: str) -> bool: + normalized = required_version.strip().lower() + if normalized in {"supported", "auto"}: + return any(prefix in version_text for prefix in SUPPORTED_MODKIT_PREFIXES) + return required_version in version_text + + +def _check_modkit(required_version: str) -> tuple[bool, str | None]: + modkit_path = shutil.which("modkit") + if modkit_path is None: + return True, None + + try: + result = subprocess.run( + ["modkit", "--version"], check=True, capture_output=True, text=True + ) + except Exception: + return True, None + + version_text = (result.stdout or result.stderr).strip().splitlines()[0] + version_ok = _modkit_version_ok(version_text, required_version) + return (not version_ok), version_text + + +def _check_crossmap() -> tuple[bool, str | None]: + for candidate in ("CrossMap.py", "CrossMap"): + path = shutil.which(candidate) + if path is not None: + return False, path + return True, None + + +def _check_runtime_imports() -> dict[str, str]: + runtime_failures: dict[str, str] = {} + runtime_modules = ("numpy", "pysam", "h5py", "pyBigWig") + for module_name in runtime_modules: + try: + __import__(module_name) + except Exception as exc: # pragma: no cover - exception type varies by platform + runtime_failures[module_name] = f"{type(exc).__name__}: {exc}" + return runtime_failures + + +def run_checks( + required_modkit_version: str, + skip_modkit: bool, + expected_env: str | None, +) -> CheckReport: + missing_core, missing_clustering = _check_python_modules() + runtime_failures = _check_runtime_imports() + if skip_modkit: + missing_modkit = False + modkit_version = None + else: + missing_modkit, modkit_version = _check_modkit(required_modkit_version) + missing_crossmap, crossmap_executable = _check_crossmap() + + return CheckReport( + missing_core=missing_core, + missing_clustering=missing_clustering, + runtime_failures=runtime_failures, + missing_modkit=missing_modkit, + modkit_version=modkit_version, + missing_crossmap=missing_crossmap, + crossmap_executable=crossmap_executable, + conda_env=os.environ.get("CONDA_DEFAULT_ENV"), + expected_env=expected_env, + ) + + +def _run_command(cmd: list[str], cwd: Path | None = None) -> None: + print(f"$ {' '.join(cmd)}") + subprocess.run(cmd, check=True, cwd=cwd) + + +def fix_python_packages(report: CheckReport) -> None: + # Install core and clustering extras in one shot; safe if already installed. + if report.missing_core or report.missing_clustering: + _run_command( + [sys.executable, "-m", "pip", "install", "-e", ".[clustering]"], + cwd=REPO_ROOT, + ) + + +def fix_modkit(required_modkit_version: str) -> None: + requested_version = ( + DEFAULT_MODKIT_INSTALL_VERSION + if required_modkit_version.strip().lower() in {"supported", "auto"} + else required_modkit_version + ) + conda_path = shutil.which("conda") + if conda_path is None: + print( + "modkit is missing/incompatible and `conda` is not on PATH.\n" + "Install manually with: conda install -y nanoporetech::modkit==" + f"{requested_version}" + ) + return + + _run_command( + ["conda", "install", "-y", f"nanoporetech::modkit=={requested_version}"] + ) + + +def fix_crossmap() -> None: + conda_path = shutil.which("conda") + if conda_path is None: + print( + "CrossMap is missing and `conda` is not on PATH.\n" + "Install manually with: conda install -y bioconda::crossmap=0.7.3" + ) + return + _run_command(["conda", "install", "-y", "bioconda::crossmap=0.7.3"]) + + +def print_report( + report: CheckReport, + required_modkit_version: str, + skip_modkit: bool, +) -> None: + print("=== DiMeLo Kernel Environment Check ===") + print(f"Python executable: {sys.executable}") + print(f"Conda env: {report.conda_env or '(not set)'}") + if report.expected_env is not None: + print(f"Expected env: {report.expected_env}") + print(f"Repo root: {REPO_ROOT}") + print("") + + if report.missing_core: + print("Missing core Python packages:") + for pkg in report.missing_core: + print(f" - {pkg}") + else: + print("Core Python packages: OK") + + if report.missing_clustering: + print("Missing clustering Python packages:") + for pkg in report.missing_clustering: + print(f" - {pkg}") + else: + print("Clustering Python packages: OK") + + if report.runtime_failures: + print("Runtime import failures:") + for module_name, message in report.runtime_failures.items(): + print(f" - {module_name}: {message}") + else: + print("Runtime imports: OK") + + if report.expected_env is not None: + if report.conda_env == report.expected_env: + print("Conda env match: OK") + else: + print( + "Conda env match: FAIL " + f"(active='{report.conda_env}', expected='{report.expected_env}')" + ) + + if skip_modkit: + print("modkit check: skipped") + else: + requirement_text = ( + "any supported version (0.6.x)" + if required_modkit_version.strip().lower() in {"supported", "auto"} + else f"version containing '{required_modkit_version}'" + ) + if report.missing_modkit: + if report.modkit_version is None: + print(f"modkit: MISSING (required {requirement_text})") + else: + print( + "modkit: version mismatch " + f"(found '{report.modkit_version}', required {requirement_text})" + ) + else: + print(f"modkit: OK ({report.modkit_version})") + + if report.missing_crossmap: + print("CrossMap: MISSING (expected CrossMap.py or CrossMap on PATH)") + else: + print(f"CrossMap: OK ({report.crossmap_executable})") + + print("") + print("Overall status:", "PASS" if report.ok else "FAIL") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument( + "--fix", + action="store_true", + help="Attempt to install/fix missing dependencies automatically.", + ) + parser.add_argument( + "--skip-modkit", + action="store_true", + help="Skip modkit checks and install attempts.", + ) + parser.add_argument( + "--modkit-version", + default=DEFAULT_MODKIT_REQUIREMENT, + help=( + "Required modkit version marker. " + "Use 'supported' (default) to allow 0.6.x, or pass an explicit marker." + ), + ) + parser.add_argument( + "--expected-env", + default=None, + help=( + "Expected active CONDA_DEFAULT_ENV name (e.g. dimelo-toolkit). " + "When provided, mismatch is treated as failure." + ), + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + + report = run_checks( + required_modkit_version=args.modkit_version, + skip_modkit=args.skip_modkit, + expected_env=args.expected_env, + ) + print_report( + report, + required_modkit_version=args.modkit_version, + skip_modkit=args.skip_modkit, + ) + + if report.ok: + return 0 + + if not args.fix: + print("\nRun with `--fix` to install missing dependencies.") + if report.expected_env and report.conda_env != report.expected_env: + print( + f"Activate expected env first: `conda activate {report.expected_env}`" + ) + if report.runtime_failures: + print( + "For runtime linker/import issues, run scripts/bootstrap_dimelo_env.sh" + ) + return 1 + + try: + fix_python_packages(report) + if not args.skip_modkit and report.missing_modkit: + fix_modkit(args.modkit_version) + if report.missing_crossmap: + fix_crossmap() + except subprocess.CalledProcessError as exc: + print(f"\nInstall step failed: {exc}") + return exc.returncode or 1 + + print("\nRe-checking environment after attempted fixes...\n") + post = run_checks( + required_modkit_version=args.modkit_version, + skip_modkit=args.skip_modkit, + expected_env=args.expected_env, + ) + print_report( + post, + required_modkit_version=args.modkit_version, + skip_modkit=args.skip_modkit, + ) + return 0 if post.ok else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/run_tutorial_offline.py b/scripts/run_tutorial_offline.py new file mode 100644 index 0000000..36f83cb --- /dev/null +++ b/scripts/run_tutorial_offline.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +""" +Execute the deterministic offline tutorial notebook and capture logs/artifacts. +""" + +from __future__ import annotations + +import argparse +import os +import subprocess +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +DEFAULT_NOTEBOOK = REPO_ROOT / "tutorial_offline.ipynb" +DEFAULT_ARTIFACT_DIR = REPO_ROOT / "artifacts" / "tutorial_offline" + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument( + "--notebook", + type=Path, + default=DEFAULT_NOTEBOOK, + help=f"Notebook path to execute (default: {DEFAULT_NOTEBOOK}).", + ) + parser.add_argument( + "--artifact-dir", + type=Path, + default=DEFAULT_ARTIFACT_DIR, + help=f"Directory for executed notebook and logs (default: {DEFAULT_ARTIFACT_DIR}).", + ) + parser.add_argument( + "--timeout-seconds", + type=int, + default=0, + help="Per-cell timeout for nbconvert execution (0 means no timeout).", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + notebook = args.notebook.resolve() + artifact_dir = args.artifact_dir.resolve() + artifact_dir.mkdir(parents=True, exist_ok=True) + + if not notebook.exists(): + raise FileNotFoundError(f"Notebook not found: {notebook}") + + executed_name = notebook.with_suffix(".executed.ipynb").name + log_path = artifact_dir / "tutorial_offline_execution.log" + + command = [ + "python3.11", + "-m", + "jupyter", + "nbconvert", + "--to", + "notebook", + "--execute", + str(notebook), + "--output", + executed_name, + "--output-dir", + str(artifact_dir), + "--ExecutePreprocessor.timeout", + str(args.timeout_seconds), + ] + + env = os.environ.copy() + env.setdefault("MPLBACKEND", "Agg") + + result = subprocess.run( + command, + cwd=REPO_ROOT, + env=env, + capture_output=True, + text=True, + ) + combined_log = "\n".join( + part.strip() for part in (result.stdout, result.stderr) if part and part.strip() + ) + log_path.write_text(combined_log + ("\n" if combined_log else "")) + + print(f"Wrote execution log to {log_path}") + print(f"Executed notebook target: {artifact_dir / executed_name}") + + if result.returncode != 0: + print(combined_log) + return result.returncode + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/update_modkit_version.sh b/scripts/update_modkit_version.sh new file mode 100755 index 0000000..48aa9cf --- /dev/null +++ b/scripts/update_modkit_version.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +ENV_NAME="${1:-${CONDA_DEFAULT_ENV:-dimelo-toolkit}}" +MODKIT_VERSION="${2:-}" + +if [[ -z "${MODKIT_VERSION}" ]]; then + echo "Usage: bash scripts/update_modkit_version.sh " + echo "Example: bash scripts/update_modkit_version.sh dimelo-toolkit 0.6.1" + exit 1 +fi + +if ! command -v conda >/dev/null 2>&1; then + echo "ERROR: conda is not on PATH." + exit 1 +fi + +if command -v mamba >/dev/null 2>&1; then + ENV_TOOL="mamba" +else + ENV_TOOL="conda" +fi + +echo "==> Installing nanoporetech::modkit==${MODKIT_VERSION} in env '${ENV_NAME}'" +"${ENV_TOOL}" install -n "${ENV_NAME}" -y "nanoporetech::modkit==${MODKIT_VERSION}" + +echo "==> Verifying in '${ENV_NAME}'" +conda run -n "${ENV_NAME}" modkit --version +conda run -n "${ENV_NAME}" python scripts/ensure_dimelo_kernel.py \ + --modkit-version "${MODKIT_VERSION}" \ + --expected-env "${ENV_NAME}" + diff --git a/setup.py b/setup.py index 05ec2f1..e5f6471 100644 --- a/setup.py +++ b/setup.py @@ -13,16 +13,18 @@ "notebook", "ipykernel", "ipywidgets", - # ipywidgets is finicky on some platforms - # Local jupyter notebooks: this works best with ipywidgets and jupyter running the latest versions, which is what this file will do. - # Google Colab: ipywidgets 7.7.1 seems to be necessary for Google Colab needed for tqdm.auto progress bars as of Feb 11 2024. - # This says that was fixed in 2022 but it sure wasn't when I tried https://github.com/googlecolab/colabtools/issues/3023. - # You can simply downgrade after installing using pip install ., it won't break anything to our knowledge. - # VS Code: using the VS code plugin to run jupyter, 7.7.1 also works while the latest version does not. Likely that - # some intermediate versions work too. - # On the Berkeley High Performance Computing Cluster, Savio, I needed to install ipywidgets==7.6.5 for jupyter Open On-Demand. "tqdm", "plotly", "kaleido", ], + extras_require={ + "clustering": [ + "scikit-learn", + "scipy", + "hdbscan", + "umap-learn", + "pyranges", + "xgboost", + ] + }, ) diff --git a/tests/test_artifacts.py b/tests/test_artifacts.py new file mode 100644 index 0000000..b892506 --- /dev/null +++ b/tests/test_artifacts.py @@ -0,0 +1,348 @@ +import hashlib +import json +from pathlib import Path + +import pytest + +from dimelo.artifacts import ( + artifact_fingerprint, + artifact_is_compatible, + resolve_artifact, +) +from dimelo.models import DatasetArtifact + + +def make_artifact( + *, + sample_id: str = "sample-1", + artifact_type: str = "extract", + path: str, + params: dict[str, object], + provenance: dict[str, object], + metadata: dict[str, object] | None = None, +) -> DatasetArtifact: + return DatasetArtifact( + sample_id=sample_id, + artifact_type=artifact_type, + path=Path(path), + format="hdf5", + params=params, + provenance=provenance, + metadata=metadata or {}, + ) + + +def test_artifact_fingerprint_includes_required_keys(): + artifact = make_artifact( + path="requested.h5", + params={"window_size": 200, "min_coverage": 5}, + provenance={ + "source_files": ["reads-a.bam", "reads-b.bam"], + "source_fingerprints": [ + {"path": "reads-a.bam", "size": 100, "mtime": 10, "digest": "aaa"}, + {"path": "reads-b.bam", "size": 200, "mtime": 20, "digest": "bbb"}, + ], + "upstream_lineage": ["parse_bam", "normalize"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + fingerprint = artifact_fingerprint(artifact) + + assert set(fingerprint) == { + "schema_version", + "package_version", + "source_files", + "source_fingerprints", + "upstream_lineage", + "params_hash", + } + assert fingerprint["schema_version"] == "artifact-v1" + assert fingerprint["package_version"] == "1.2.3" + assert fingerprint["source_files"] == ("reads-a.bam", "reads-b.bam") + assert fingerprint["source_fingerprints"] == ( + {"path": "reads-a.bam", "size": 100, "mtime": 10, "digest": "aaa"}, + {"path": "reads-b.bam", "size": 200, "mtime": 20, "digest": "bbb"}, + ) + assert fingerprint["upstream_lineage"] == ("parse_bam", "normalize") + expected_params_hash = hashlib.sha256( + json.dumps( + {"min_coverage": 5, "window_size": 200}, + sort_keys=True, + separators=(",", ":"), + ).encode() + ).hexdigest() + assert fingerprint["params_hash"] == expected_params_hash + + +def test_artifact_fingerprint_normalizes_source_file_paths_to_strings(): + artifact = make_artifact( + path="requested.h5", + params={"window_size": 200}, + provenance={ + "source_files": [Path("reads-b.bam"), "reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": [], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + fingerprint = artifact_fingerprint(artifact) + + assert fingerprint["source_files"] == ("reads-a.bam", "reads-b.bam") + + +def test_artifact_fingerprint_treats_single_source_file_string_as_one_path(): + artifact = make_artifact( + path="requested.h5", + params={"window_size": 200}, + provenance={ + "source_files": "reads-a.bam", + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": [], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + fingerprint = artifact_fingerprint(artifact) + + assert fingerprint["source_files"] == ("reads-a.bam",) + + +def test_artifact_is_compatible_rejects_sample_id_mismatch(): + requested = make_artifact( + path="requested.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + candidate = make_artifact( + sample_id="sample-2", + path="cached.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + assert not artifact_is_compatible(requested, candidate) + + +def test_artifact_is_compatible_rejects_artifact_type_mismatch(): + requested = make_artifact( + path="requested.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + candidate = make_artifact( + artifact_type="other", + path="cached.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + assert not artifact_is_compatible(requested, candidate) + + +def test_artifact_is_compatible_rejects_missing_required_lineage_fields(): + requested = make_artifact( + path="requested.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": [], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + candidate = make_artifact( + path="cached.h5", + params={"window_size": 200}, + provenance={}, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + assert not artifact_is_compatible(requested, candidate) + + +def test_artifact_is_compatible_rejects_empty_source_metadata(): + requested = make_artifact( + path="requested.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": [], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + candidate = make_artifact( + path="cached.h5", + params={"window_size": 200}, + provenance={ + "source_files": [], + "source_fingerprints": [], + "upstream_lineage": [], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + assert not artifact_is_compatible(requested, candidate) + + +def test_resolve_artifact_prefers_matching_cached_artifact(): + requested = make_artifact( + path="requested.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + cached_match = make_artifact( + path="cached.h5", + params={"window_size": 200, "threads": 8}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + resolved = resolve_artifact( + requested, + [cached_match], + artifact_policy="prefer_cached", + ) + + assert resolved is cached_match + + +def test_resolve_artifact_returns_none_on_prefer_cached_miss(): + requested = make_artifact( + path="requested.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + candidate = make_artifact( + sample_id="sample-2", + path="cached.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + resolved = resolve_artifact( + requested, + [candidate], + artifact_policy="prefer_cached", + ) + + assert resolved is None + + +def test_resolve_artifact_raises_on_require_cached_miss(): + requested = make_artifact( + path="requested.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + with pytest.raises(FileNotFoundError, match="require_cached"): + resolve_artifact( + requested, + [], + artifact_policy="require_cached", + ) + + +def test_artifact_is_compatible_ignores_non_compatibility_provenance_fields(): + requested = make_artifact( + path="requested.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + "runtime_host": "worker-a", + "invocation_id": "run-123", + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + candidate = make_artifact( + path="cached.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + assert artifact_is_compatible(requested, candidate) + + +def test_resolve_artifact_returns_none_for_rebuild(): + requested = make_artifact( + path="requested.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + candidate = make_artifact( + path="cached.h5", + params={"window_size": 200}, + provenance={ + "source_files": ["reads-a.bam"], + "source_fingerprints": [{"path": "reads-a.bam", "size": 100, "mtime": 10}], + "upstream_lineage": ["parse_bam"], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.2.3"}, + ) + + resolved = resolve_artifact( + requested, + [candidate], + artifact_policy="rebuild", + ) + + assert resolved is None diff --git a/tests/test_chip_atlas.py b/tests/test_chip_atlas.py new file mode 100644 index 0000000..95b6f0d --- /dev/null +++ b/tests/test_chip_atlas.py @@ -0,0 +1,279 @@ +import pandas as pd +import pytest + +from dimelo import chip_atlas + + +def test_region_ids_to_bed_dataframe_parses_region_ids(): + frame = chip_atlas.region_ids_to_bed_dataframe(["chr1:100-200,+", "chr2:500-700"]) + assert frame.shape[0] == 2 + assert frame.loc[0, "chrom"] == "chr1" + assert int(frame.loc[0, "start"]) == 100 + assert int(frame.loc[0, "end"]) == 200 + assert frame.loc[0, "strand"] == "+" + assert frame.loc[1, "strand"] == "." + + +def test_submit_enrichment_extracts_request_id_from_json(monkeypatch): + captured = {} + + def fake_request(**kwargs): + captured.update(kwargs) + return 200, "application/json", '{"id":"REQ123"}' + + monkeypatch.setattr(chip_atlas, "_http_request", fake_request) + result = chip_atlas.submit_enrichment( + regions=pd.DataFrame({"chrom": ["chr1"], "start": [0], "end": [100]}), + genome="hg38", + ) + assert result["request_id"] == "REQ123" + assert captured["url"] == chip_atlas.DEFAULT_SUBMIT_URL + payload = captured["data"].decode("utf-8") + assert "format=text" in payload + assert "result=www" in payload + assert "typeA=bed" in payload + assert "bedAFile=" in payload + assert "typeB=rnd" in payload + assert "bedBFile=empty" in payload + assert "antigenClass=TFs+and+others" in payload + assert "cellClass=No+description" in payload + assert "threshold=100" in payload + + +def test_get_status_uses_request_path_endpoint(monkeypatch): + captured = {} + + def fake_request(**kwargs): + captured.update(kwargs) + return 200, "text/plain", "current-state: running" + + monkeypatch.setattr(chip_atlas, "_http_request", fake_request) + status = chip_atlas.get_status("REQ-PATH") + assert status["status"] == "running" + assert captured["url"] == f"{chip_atlas.DEFAULT_STATUS_URL}REQ-PATH" + + +def test_poll_request_collects_history_until_finished(monkeypatch): + states = iter( + [ + {"status": "running", "raw_status": "running", "status_code": 200}, + {"status": "finished", "raw_status": "finished", "status_code": 200}, + ] + ) + + def fake_get_status(*args, **kwargs): + payload = next(states) + return {"request_id": "REQ1", **payload} + + monkeypatch.setattr(chip_atlas, "get_status", fake_get_status) + poll = chip_atlas.poll_request( + "REQ1", poll_interval_seconds=0.0, timeout_seconds=5.0 + ) + assert poll["status"] == "finished" + assert [step["status"] for step in poll["history"]] == ["running", "finished"] + + +def test_fetch_result_parses_tsv(monkeypatch): + captured = {} + + def fake_request(**kwargs): + captured.update(kwargs) + return 200, "text/tab-separated-values", "target\tcount\nCTCF\t42\n" + + monkeypatch.setattr(chip_atlas, "_http_request", fake_request) + result = chip_atlas.fetch_result("REQ2") + assert isinstance(result, pd.DataFrame) + assert result.loc[0, "target"] == "CTCF" + assert int(result.loc[0, "count"]) == 42 + assert ( + captured["url"] == f"{chip_atlas.DEFAULT_RESULT_URL}REQ2?info=result&format=tsv" + ) + + +def test_run_enrichment_raises_on_terminal_failure(monkeypatch): + monkeypatch.setattr( + chip_atlas, + "submit_enrichment", + lambda **kwargs: {"request_id": "REQ-ERR", "query": {}, "submit_url": "u"}, + ) + monkeypatch.setattr( + chip_atlas, + "poll_request", + lambda *args, **kwargs: {"status": "error", "history": []}, + ) + with pytest.raises(RuntimeError, match="status 'error'"): + chip_atlas.run_enrichment( + regions=["chr1:0-100"], + wait=True, + fetch_results=False, + raise_on_failure=True, + ) + + +def test_submit_enrichment_runs_crossmap_when_regions_genome_differs(monkeypatch): + captured = {} + + def fake_convert(**kwargs): + captured.update(kwargs) + return pd.DataFrame( + { + "chrom": ["chr1"], + "start": [10], + "end": [60], + "name": ["region_0"], + "score": [0], + "strand": ["+"], + } + ) + + def fake_resolve(**kwargs): + return "/tmp/hs1_to_hg38.over.chain.gz" + + def fake_request(**kwargs): + return 200, "application/json", '{"id":"REQX"}' + + monkeypatch.setattr(chip_atlas, "_resolve_chain_file", fake_resolve) + monkeypatch.setattr(chip_atlas, "convert_regions_with_crossmap", fake_convert) + monkeypatch.setattr(chip_atlas, "_http_request", fake_request) + + result = chip_atlas.submit_enrichment( + regions=["chr1:100-200,+"], + genome="hg38", + regions_genome="chm13", + ) + + assert result["request_id"] == "REQX" + assert captured["source_genome"] == "hs1" + assert captured["target_genome"] == "hg38" + assert captured["chain_file"] == "/tmp/hs1_to_hg38.over.chain.gz" + assert result["query"]["crossmap"]["applied"] is True + + +def test_cache_chain_files_uses_resolver(monkeypatch): + calls = [] + + def fake_resolve(**kwargs): + calls.append((kwargs["source_genome"], kwargs["target_genome"])) + return ( + f"/tmp/{kwargs['source_genome']}_to_{kwargs['target_genome']}.over.chain.gz" + ) + + monkeypatch.setattr(chip_atlas, "_resolve_chain_file", fake_resolve) + cached = chip_atlas.cache_chain_files( + source_genome="chm13", target_genomes=("hg38", "hg19") + ) + + assert calls == [("chm13", "hg38"), ("chm13", "hg19")] + assert str(cached["hg38"]).endswith("chm13_to_hg38.over.chain.gz") + assert str(cached["hg19"]).endswith("chm13_to_hg19.over.chain.gz") + + +def test_resolve_crossmap_executable_falls_back_to_crossmap(monkeypatch): + def fake_which(cmd): + return "/usr/local/bin/CrossMap" if cmd == "CrossMap" else None + + monkeypatch.setattr(chip_atlas.shutil, "which", fake_which) + resolved = chip_atlas._resolve_crossmap_executable("CrossMap.py") + assert resolved == "CrossMap" + + +def test_search_peak_datasets_filters_metadata_rows(monkeypatch, tmp_path): + rows = [ + { + "Experimental ID": "SRX1", + "Genome assembly": "hg38", + "Antigen class": "TFs and others", + "Antigen": "CTCF", + "Cell type class": "Blood", + "Cell type": "K562", + "Cell type description": "", + "Processing logs": "", + "Title": "CTCF K562", + "Meta data": "", + "BigWig": "bw1", + "Peak-call (BED) (q < 1E-05)": "http://example.org/srx1.bed", + "Peak-call (BigBed) (q < 1E-05)": "http://example.org/srx1.bb", + }, + { + "Experimental ID": "SRX2", + "Genome assembly": "mm10", + "Antigen class": "TFs and others", + "Antigen": "CTCF", + "Cell type class": "Blood", + "Cell type": "MEL", + "Cell type description": "", + "Processing logs": "", + "Title": "CTCF mouse", + "Meta data": "", + "BigWig": "bw2", + "Peak-call (BED) (q < 1E-05)": "http://example.org/srx2.bed", + "Peak-call (BigBed) (q < 1E-05)": "http://example.org/srx2.bb", + }, + ] + + monkeypatch.setattr( + chip_atlas, + "_ensure_experiment_list_zip", + lambda **kwargs: tmp_path / "fake.zip", + ) + monkeypatch.setattr(chip_atlas, "_iter_experiment_rows", lambda path: iter(rows)) + + found = chip_atlas.search_peak_datasets( + antigen="CTCF", + genome="hg38", + cell_type="k562", + threshold="05", + ) + assert found.shape[0] == 1 + assert found.loc[0, "dataset_id"] == "SRX1" + assert found.loc[0, "bed_url"] == "http://example.org/srx1.bed" + + +def test_download_peak_datasets_writes_variants_and_crossmapped(monkeypatch, tmp_path): + datasets = pd.DataFrame( + [ + { + "dataset_id": "SRX3", + "bed_url": "http://example.org/srx3.bed", + "genome_assembly": "hg38", + } + ] + ) + bed_payload = ( + b"chr1\t10\t20\tp1\t100\t.\n" + b"chr1\t30\t40\tp2\t10\t.\n" + b"chr1\t50\t60\tp3\t50\t.\n" + b"chr1\t70\t80\tp4\t5\t.\n" + ) + monkeypatch.setattr(chip_atlas, "_download_bytes", lambda **kwargs: bed_payload) + monkeypatch.setattr( + chip_atlas, + "convert_regions_with_crossmap", + lambda **kwargs: pd.DataFrame( + { + "chrom": ["chr1"], + "start": [100], + "end": [200], + "name": ["mapped"], + "score": [1], + "strand": ["+"], + } + ), + ) + + manifest = chip_atlas.download_peak_datasets( + datasets=datasets, + output_dir=tmp_path, + include_complete_sorted=True, + include_top_n=2, + include_bottom_n=2, + stratify="quartiles", + crossmap_target_genome="hg19", + ) + variants = set(manifest["variant"].tolist()) + assert "full_sorted" in variants + assert "top_2" in variants + assert "bottom_2" in variants + assert "quantile_1_of_4" in variants + assert "full_sorted_crossmapped" in variants + assert manifest.loc[manifest["crossmapped"] == True].shape[0] > 0 # noqa: E712 diff --git a/tests/test_cluster.py b/tests/test_cluster.py new file mode 100644 index 0000000..334a6bb --- /dev/null +++ b/tests/test_cluster.py @@ -0,0 +1,2779 @@ +import numpy as np +import pandas as pd +import pytest + +from dimelo import cluster + + +@pytest.fixture(autouse=True) +def _close_matplotlib_figures(): + yield + try: + import matplotlib.pyplot as plt + + plt.close("all") + except Exception: + pass + + +def test_region_feature_matrix_from_pileup(monkeypatch): + # Mock the region parsing and loading helpers so we can focus on the feature logic + fake_regions_dict = { + "chr1": [(0, 3, "+")], + "chr2": [(5, 8, "-")], + } + monkeypatch.setattr( + cluster.utils, + "regions_dict_from_input", + lambda *args, **kwargs: fake_regions_dict, + ) + monkeypatch.setattr( + cluster.load_processed, + "regions_to_list", + lambda **kwargs: [ + np.array([0.5, 0.5, 0.0], dtype=float), + np.array([0.0, 0.5, 0.5], dtype=float), + ], + ) + + features, metadata = cluster.region_feature_matrix_from_pileup( + bedmethyl_file="pileup.bed.gz", + motif="A,0", + regions="regions.bed", + pseudo_count=0.0, + ) + + np.testing.assert_allclose( + features, + np.array( + [ + [0.5, 0.5, 0.0], + [0.0, 0.5, 0.5], + ] + ), + ) + assert metadata == [("chr1", 0, 3, "+"), ("chr2", 5, 8, "-")] + + +def test_pileup_fraction_vector_from_bedmethyl(monkeypatch): + monkeypatch.setattr( + cluster.load_processed, + "pileup_vectors_from_bedmethyl", + lambda **kwargs: ( + np.array([1, 1, 0], dtype=float), + np.array([2, 2, 1], dtype=float), + ), + ) + fraction = cluster._pileup_fraction_vector_from_bedmethyl( + bedmethyl_file="pileup.bed.gz", + motif="A,0", + regions="chr1:0-3,+", + pseudo_count=0.0, + ) + np.testing.assert_allclose(fraction, np.array([0.5, 0.5, 0.0], dtype=float)) + + +def test_read_mod_fraction_table(monkeypatch): + datasets = [ + "read_name", + "chromosome", + "motif", + "mod_vector", + "val_vector", + "region_start", + "region_end", + "region_strand", + "read_length", + "A,0_mod_fraction", + "CG,0_mod_fraction", + ] + read_records = [ + ("read1", "chr1", "A,0", None, None, 10, 110, "+", 100, 0.25, 0.75), + ("read2", "chr1", "CG,0", None, None, 20, 140, "-", 120, 0.10, 0.60), + ] + monkeypatch.setattr( + cluster.load_processed, + "read_vectors_from_hdf5", + lambda **kwargs: (read_records, datasets, {"chr1": [(0, 100, "+")]}), + ) + + features, feature_names, metadata, regions_dict = cluster.read_mod_fraction_table( + hdf5_file="reads.h5", + motifs=["A,0", "CG,0"], + metadata_fields=("read_name", "chromosome"), + ) + + np.testing.assert_allclose( + features, + np.array( + [ + [0.25, 0.75], + [0.10, 0.60], + ] + ), + ) + assert feature_names == ["A,0_mod_fraction", "CG,0_mod_fraction"] + assert metadata == [ + {"read_name": "read1", "chromosome": "chr1"}, + {"read_name": "read2", "chromosome": "chr1"}, + ] + assert regions_dict == {"chr1": [(0, 100, "+")]} + + +def test_summarize_read_cluster_region_associations_counts_and_fractions(): + metadata = [ + { + "chromosome": "chr1", + "region_start": 0, + "region_end": 100, + "region_strand": "+", + }, + { + "chromosome": "chr1", + "region_start": 0, + "region_end": 100, + "region_strand": "+", + }, + { + "chromosome": "chr1", + "region_start": 0, + "region_end": 100, + "region_strand": "+", + }, + { + "chromosome": "chr1", + "region_start": 200, + "region_end": 300, + "region_strand": "-", + }, + { + "chromosome": "chr1", + "region_start": 200, + "region_end": 300, + "region_strand": "-", + }, + ] + labels = [0, 0, 1, 1, 1] + + summary = cluster.summarize_read_cluster_region_associations(metadata, labels) + + region1 = summary[ + (summary["chrom"] == "chr1") + & (summary["start"] == 0) + & (summary["end"] == 100) + & (summary["strand"] == "+") + ].sort_values("cluster") + assert region1["count"].tolist() == [2, 1] + np.testing.assert_allclose(region1["fraction"].to_numpy(), np.array([2 / 3, 1 / 3])) + assert region1["total_reads"].tolist() == [3, 3] + + +def test_summarize_read_cluster_region_associations_enrichment_and_multiple_testing(): + metadata = [ + { + "chromosome": "chr1", + "region_start": 0, + "region_end": 100, + "region_strand": "+", + }, + { + "chromosome": "chr1", + "region_start": 0, + "region_end": 100, + "region_strand": "+", + }, + { + "chromosome": "chr1", + "region_start": 0, + "region_end": 100, + "region_strand": "+", + }, + { + "chromosome": "chr1", + "region_start": 200, + "region_end": 300, + "region_strand": "-", + }, + { + "chromosome": "chr1", + "region_start": 200, + "region_end": 300, + "region_strand": "-", + }, + ] + labels = [0, 0, 1, 1, 1] + + summary = cluster.summarize_read_cluster_region_associations(metadata, labels) + + enriched = summary[ + (summary["chrom"] == "chr1") + & (summary["start"] == 200) + & (summary["end"] == 300) + & (summary["strand"] == "-") + & (summary["cluster"] == 1) + ].iloc[0] + assert enriched["log2_enrichment"] > 0 + assert 0.0 <= enriched["p_value"] <= 1.0 + assert 0.0 <= enriched["q_value"] <= 1.0 + + assert set(["p_value", "q_value"]).issubset(summary.columns) + assert summary["p_value"].between(0.0, 1.0).all() + assert summary["q_value"].between(0.0, 1.0).all() + + +def test_summarize_read_cluster_region_associations_min_reads_filter(): + metadata = [ + { + "chromosome": "chr1", + "region_start": 0, + "region_end": 100, + "region_strand": "+", + }, + { + "chromosome": "chr1", + "region_start": 200, + "region_end": 300, + "region_strand": "-", + }, + { + "chromosome": "chr1", + "region_start": 200, + "region_end": 300, + "region_strand": "-", + }, + ] + labels = [0, 1, 1] + + summary = cluster.summarize_read_cluster_region_associations( + metadata, + labels, + min_reads_per_region=2, + ) + + assert summary["start"].tolist() == [200, 200] + assert summary["total_reads"].tolist() == [2, 2] + + +def test_cluster_features_invokes_kmeans(monkeypatch): + class DummyKMeans: + def __init__(self, n_clusters, random_state, **kwargs): + self.n_clusters = n_clusters + self.random_state = random_state + self.kwargs = kwargs + self.observed = None + + def fit_predict(self, matrix): + self.observed = matrix + return np.arange(matrix.shape[0]) + + monkeypatch.setattr(cluster, "_get_kmeans", lambda: DummyKMeans) + + feature_matrix = np.array([[0.1, 0.2], [0.4, 0.6], [0.9, 0.95]]) + labels, estimator = cluster.cluster_features( + feature_matrix, n_clusters=3, random_state=7 + ) + + np.testing.assert_array_equal(labels, np.array([0, 1, 2])) + assert isinstance(estimator, DummyKMeans) + assert estimator.n_clusters == 3 + np.testing.assert_allclose(estimator.observed, feature_matrix) + + +def test_cluster_features_rejects_unknown_method(): + with pytest.raises(ValueError): + cluster.cluster_features(np.ones((2, 2)), method="dbscan") + + +def test_extract_read_windows_orientation(monkeypatch): + dataset_names = [ + "chromosome", + "mod_vector", + "motif", + "read_end", + "read_name", + "read_start", + "strand", + "val_vector", + "region_start", + "region_end", + "region_strand", + "read_length", + ] + record = ( + "chr1", + np.arange(10, dtype=float), + "A,0", + 110, + "read1", + 100, + "-", + np.ones(10, dtype=float), + 104, + 108, + "+", + 10, + ) + + def fake_loader(**kwargs): + return ([record], dataset_names, None) + + monkeypatch.setattr(cluster.load_processed, "read_vectors_from_hdf5", fake_loader) + result = cluster.extract_read_windows( + hdf5_file="reads.h5", + motifs=["A,0"], + config=cluster.ReadWindowExtractionConfig( + window_size=2, + orientation_aware=True, + enforce_thresholded_vectors=False, + ), + ) + + np.testing.assert_allclose(result.data_matrix, np.array([[7.0, 6.0, 5.0, 4.0]])) + assert result.val_matrix is not None + assert len(result.metadata) == 1 + + +def test_extract_read_windows_filter_multi_region(monkeypatch): + dataset_names = [ + "chromosome", + "mod_vector", + "motif", + "read_end", + "read_name", + "read_start", + "strand", + "val_vector", + "region_start", + "region_end", + "region_strand", + "read_length", + ] + rec1 = ( + "chr1", + np.zeros(10, dtype=float), + "A,0", + 110, + "dup", + 100, + "+", + np.ones(10, dtype=float), + 104, + 108, + "+", + 10, + ) + rec2 = ( + "chr1", + np.ones(10, dtype=float), + "A,0", + 115, + "dup", + 100, + "+", + np.ones(10, dtype=float), + 106, + 110, + "+", + 10, + ) + rec3 = ( + "chr1", + np.arange(10, dtype=float), + "A,0", + 112, + "keep", + 100, + "+", + np.ones(10, dtype=float), + 104, + 108, + "+", + 10, + ) + + def fake_loader(**kwargs): + return ([rec1, rec2, rec3], dataset_names, None) + + monkeypatch.setattr(cluster.load_processed, "read_vectors_from_hdf5", fake_loader) + result = cluster.extract_read_windows( + hdf5_file="reads.h5", + motifs=["A,0"], + config=cluster.ReadWindowExtractionConfig( + window_size=2, + orientation_aware=False, + filter_multi_region_reads=True, + enforce_thresholded_vectors=False, + ), + ) + assert result.data_matrix.shape[0] == 1 + assert result.metadata[0]["read_name"] == "keep" + + +def test_read_window_feature_matrix(): + data = np.array( + [ + [0, 0, 1, 1], + [1, 1, 0, 0], + [0, 1, 0, 1], + ], + dtype=float, + ) + result = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=[], + datasets=[], + regions_dict=None, + ) + features, names = cluster.read_window_feature_matrix( + result, + n_pca=1, + autocorr_lags=(1,), + density_windows=(("center", -2, 2),), + require_nonzero_valid=False, + min_valid_fraction=0.0, + ) + assert features.shape[0] == 3 + assert "pca_0" in names + assert "autocorr_1" in names + assert "center" in names + assert "global_mean" in names + assert "iqr" in names + + +def test_plot_cluster_profiles_motif_index(monkeypatch): + X = np.hstack([np.ones((3, 2)), np.zeros((3, 2))]) + labels = np.array([0, 1, 0]) + import matplotlib + + matplotlib.use("Agg") + fig = cluster.plot_cluster_profiles(X, labels, motif_index=1, view_window_size=1) + assert fig is not None + + +def test_plot_region_cluster_profiles_motif_index(monkeypatch): + X = np.hstack([np.ones((2, 2)), np.zeros((2, 2))]) + labels = np.array([0, 1]) + import matplotlib + + matplotlib.use("Agg") + fig = cluster.plot_region_cluster_profiles( + X, labels, motif_index=1, motif_count=2, window_size=2 + ) + assert fig is not None + + +def test_read_window_feature_matrix_filters(): + data = np.array( + [ + [0, 0, 1, 1], + [1, 1, 0, 0], + ], + dtype=float, + ) + val = np.array( + [ + [0, 0, 1, 1], + [0, 0, 0, 0], + ], + dtype=float, + ) + result = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=val, + metadata=[], + datasets=[], + regions_dict=None, + ) + features, _ = cluster.read_window_feature_matrix( + result, + require_nonzero_valid=True, + min_valid_fraction=0.5, + n_pca=0, + autocorr_lags=(), + ) + # Second row should be dropped due to zero valid fraction + assert features.shape[0] == 1 + + +def test_read_window_feature_matrix_rich_spec_tracks_motifs(): + data = np.array( + [ + [0, 1, 1, 0, 1, 1, 0, 0], + [1, 1, 0, 0, 0, 0, 1, 1], + [0, 0, 1, 1, 1, 0, 1, 0], + ], + dtype=float, + ) + val = np.ones_like(data) + result = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=val, + metadata=[], + datasets=[], + regions_dict=None, + ) + + features, names, feature_table = cluster.read_window_feature_matrix( + result, + feature_spec={ + "motif_mode": "per_motif", + "motif_count": 2, + "motif_labels": ["A,0", "CG,0"], + "pooled": False, + "pca": {"enabled": False}, + "densities": {"enabled": True, "windows": [("center", -1, 1)]}, + "autocorr": {"enabled": False}, + "fft": {"enabled": True, "periods_bp": [2]}, + "asymmetry": {"enabled": True, "spans": [2]}, + "center_edge": {"enabled": True, "center_bp": 1, "edge_bp": 1}, + "cross_motif": {"enabled": True}, + }, + return_feature_table=True, + ) + + assert features.shape[0] == 3 + assert "A,0__center" in names + assert "CG,0__fft_power_period_2bp" in names + assert "A,0_minus_CG,0__mean" in names + assert {"A,0", "CG,0", "A,0|CG,0"}.issubset(set(feature_table["motif"])) + assert {"density", "fft", "cross_motif"}.issubset(set(feature_table["family"])) + + +def test_summarize_and_rank_feature_matrix(): + features = np.array([[0.0, 0.1], [0.2, 0.1], [1.0, 0.1], [1.2, 0.1]]) + names = ["signal", "constant"] + feature_table = pd.DataFrame( + { + "feature_name": names, + "family": ["density", "summary"], + "motif": ["A,0", "pooled"], + } + ) + + summary = cluster.summarize_feature_matrix( + features, + names, + feature_table=feature_table, + labels=["off", "off", "on", "on"], + ) + ranked = cluster.rank_read_features_by_group_difference( + features, + names, + ["off", "off", "on", "on"], + ) + + assert summary["n_reads"] == 4 + assert summary["zero_variance_features"] == 1 + assert summary["feature_counts_by_family"]["n_features"].sum() == 2 + assert ranked.iloc[0]["feature_name"] == "signal" + + +def test_scale_feature_matrix_standardizes_columns(): + features = np.array([[0.0, 10.0], [1.0, 10.0], [2.0, 10.0]]) + scaled, scale_table = cluster.scale_feature_matrix(features, ["a", "constant"]) + + np.testing.assert_allclose(scaled[:, 0].mean(), 0.0, atol=1e-12) + np.testing.assert_allclose(scaled[:, 0].std(), 1.0, atol=1e-12) + np.testing.assert_allclose(scaled[:, 1], np.zeros(3)) + assert scale_table.loc[0, "scaling_method"] == "standard" + assert scale_table.loc[1, "scale"] == 1.0 + + +def test_scale_feature_matrix_can_balance_and_skip_families(): + features = np.array([[0.0, 1.0, 10.0], [1.0, 2.0, 20.0], [2.0, 3.0, 30.0]]) + names = ["density_a", "density_b", "raw_count"] + feature_table = pd.DataFrame( + { + "feature_name": names, + "family": ["density", "density", "count"], + } + ) + + scaled, scale_table = cluster.scale_feature_matrix( + features, + names, + feature_table=feature_table, + family_weighting="equal_family", + unscaled_families=["count"], + ) + + np.testing.assert_allclose(scale_table.loc[:1, "family_weight"], [1 / np.sqrt(2)] * 2) + assert scale_table.loc[2, "scaled"] == np.False_ + np.testing.assert_allclose(scaled[:, 2], features[:, 2]) + + +def test_read_window_feature_matrix_inline_scaling_uses_feature_families_without_return_table(): + data = np.array( + [ + [0, 0, 1, 1], + [0, 1, 1, 1], + [1, 1, 0, 0], + ], + dtype=float, + ) + result = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=np.ones_like(data), + metadata=[], + datasets=[], + regions_dict=None, + ) + + scaled, names = cluster.read_window_feature_matrix( + result, + n_pca=0, + autocorr_lags=(), + density_windows=(("left", -2, 0), ("right", 0, 2)), + scale_features=True, + unscaled_families=["density"], + ) + unscaled, unscaled_names, feature_table = cluster.read_window_feature_matrix( + result, + n_pca=0, + autocorr_lags=(), + density_windows=(("left", -2, 0), ("right", 0, 2)), + return_feature_table=True, + ) + + assert names == unscaled_names + density_cols = feature_table.index[feature_table["family"] == "density"].tolist() + assert density_cols + np.testing.assert_allclose(scaled[:, density_cols], unscaled[:, density_cols]) + + +def test_plot_multisite_read_raster_smoke(monkeypatch): + # Build a minimal ReadWindowExtractionResult with two motifs, two reads + data = np.hstack( + [ + np.tile(np.array([[0, 1, 0, 1]], dtype=float), (2, 1)), + np.tile(np.array([[1, 0, 1, 0]], dtype=float), (2, 1)), + ] + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + import matplotlib + + matplotlib.use("Agg") + fig, stats = cluster.plot_multisite_read_raster( + r, n_windows=2, min_separation_bp=5000, motif_index=0, smoothing=None + ) + assert fig is not None + assert stats["pairs"] >= 1 + assert stats["render_mode"] == "scatter" + + +def test_plot_multisite_read_raster_supports_multimotif_grid(): + import matplotlib + + matplotlib.use("Agg") + # 3 windows from one read, 2 concatenated motifs of width 4 each + motif_a = np.array( + [ + [0, 1, 0, 1], + [1, 0, 1, 0], + [0, 1, 1, 0], + ], + dtype=float, + ) + motif_c = np.array( + [ + [1, 0, 1, 0], + [0, 1, 0, 1], + [1, 1, 0, 0], + ], + dtype=float, + ) + data = np.hstack([motif_a, motif_c]) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 2500, + "region_end": 2504, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 5000, + "region_end": 5004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + n_windows=3, + min_separation_bp=2000, + motif_index=0, + motif_count=2, + plot_all_motifs=True, + motif_labels=["A,0", "CG,0"], + smoothing=None, + ) + assert fig is not None + assert stats["pairs"] >= 1 + assert stats["n_windows"] == 3 + assert stats["motifs_plotted"] == ["A,0", "CG,0"] + assert stats["ml_score_cmaps"] == {"A,0": "Blues", "CG,0": "Oranges"} + + +def test_plot_multisite_read_raster_ml_scatter_uses_sequential_cmaps_for_other_motifs(): + import matplotlib + + matplotlib.use("Agg") + motif_1 = np.array( + [ + [0, 1, 0, 1], + [1, 0, 1, 0], + ], + dtype=float, + ) + motif_2 = np.array( + [ + [1, 0, 1, 0], + [0, 1, 0, 1], + ], + dtype=float, + ) + motif_3 = np.array( + [ + [0, 0, 1, 1], + [1, 1, 0, 0], + ], + dtype=float, + ) + data = np.hstack([motif_1, motif_2, motif_3]) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=5000, + motif_index=0, + motif_count=3, + plot_all_motifs=True, + motif_labels=["X,0", "Y,0", "Z,0"], + smoothing=None, + ) + assert fig is not None + assert stats["ml_score_cmaps"] == {"X,0": "Greens", "Y,0": "Purples", "Z,0": "Reds"} + + +def test_plot_multisite_read_raster_supports_fixed_offsets_and_length_filter(): + import matplotlib + + matplotlib.use("Agg") + motif_a = np.array( + [ + [0, 1, 0, 1], + [1, 0, 1, 0], + [0, 1, 1, 0], + ], + dtype=float, + ) + data = motif_a + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + "read_length": 7000, + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 2500, + "region_end": 2504, + "region_strand": "+", + "read_length": 7000, + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 5000, + "region_end": 5004, + "region_strand": "+", + "read_length": 7000, + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + motif_index=0, + selection_mode="fixed_offsets", + window_offsets_bp=[0, 2500, 5000], + coordinate_mode="relative_to_primary", + smoothing=None, + ) + assert fig is not None + assert stats["n_windows"] == 3 + assert stats["window_offsets_bp"] == [0.0, 2500.0, 5000.0] + assert stats["required_read_length_bp"] > 0 + assert stats["min_read_length_bp"] == stats["required_read_length_bp"] + + +def test_plot_two_site_read_raster_wrapper(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0, 1, 0, 1], + [1, 0, 1, 0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + "read_length": 8000, + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 2000, + "region_end": 2004, + "region_strand": "+", + "read_length": 8000, + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_two_site_read_raster( + r, + second_site_offset_bp=2000, + window_width_bp=4, + motif_index=0, + smoothing=None, + max_rows=1, + downsample_method="auto", + ) + assert fig is not None + assert stats["n_windows"] == 2 + assert stats["selection_mode"] == "fixed_offsets" + assert stats["window_offsets_bp"] == [0.0, 2000.0] + assert stats["window_widths_bp"] == [4, 4] + assert stats["coordinate_mode"] == "relative_to_primary" + assert stats["downsample_method"] == "none" + data_axes = [ax for ax in fig.axes if ax.get_ylabel() == "Reads (sorted)"] + assert data_axes + assert data_axes[0].get_xlim()[0] <= -2 and data_axes[0].get_xlim()[1] >= 2 + assert data_axes[1].get_xlim()[0] <= 1998 and data_axes[1].get_xlim()[1] >= 2002 + + +def test_plot_two_site_read_raster_position_y_keeps_site_limits_independent(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0, 1, 0, 1], + [1, 0, 1, 0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + "read_length": 8000, + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 2000, + "region_end": 2004, + "region_strand": "+", + "read_length": 8000, + }, + ] + r = cluster.ReadWindowExtractionResult(data, None, meta, [], None) + + fig, stats = cluster.plot_two_site_read_raster( + r, + second_site_offset_bp=2000, + window_width_bp=4, + motif_index=0, + smoothing=None, + axis_orientation="position_y", + ) + + assert stats["window_plot_order"] == [1, 0] + data_axes = [ax for ax in fig.axes if ax.get_ylabel() == "Position (bp)"] + assert len(data_axes) == 2 + assert data_axes[0].get_ylim()[0] <= 1998 and data_axes[0].get_ylim()[1] >= 2002 + assert data_axes[1].get_ylim()[0] <= -2 and data_axes[1].get_ylim()[1] >= 2 + point_counts = [ + sum(len(collection.get_offsets()) for collection in ax.collections) + for ax in data_axes + ] + assert point_counts == [2, 2] + + +def test_plot_multisite_read_raster_supports_fixed_offsets_with_primary_highlight(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0, 1, 0, 1], + [1, 0, 1, 0], + [0, 1, 1, 0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + "read_length": 9000, + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 2000, + "region_end": 2004, + "region_strand": "+", + "read_length": 9000, + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 4000, + "region_end": 4004, + "region_strand": "+", + "read_length": 9000, + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + motif_index=0, + selection_mode="fixed_offsets", + window_offsets_bp=[0, 2000, 4000], + primary_window_index=1, + smoothing=None, + ) + assert fig is not None + assert stats["n_windows"] == 3 + assert stats["window_offsets_bp"] == [0.0, 2000.0, 4000.0] + data_axes = [ax for ax in fig.axes if ax.get_ylabel() == "Reads (sorted)"] + assert data_axes[1].spines["left"].get_linewidth() > data_axes[0].spines["left"].get_linewidth() + + +def test_plot_multisite_read_raster_heatmap_window_titles(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0, 1, 0, 1], + [1, 0, 1, 0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, _ = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=5000, + motif_index=0, + render_mode="heatmap", + rotate=False, + smoothing=None, + ) + titles = [axis.get_title() for axis in fig.axes if axis.get_title()] + assert "Site 1 (primary)" in titles + assert "Site 2" in titles + + +def test_plot_multisite_read_raster_accepts_local_window_coordinate_mode(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0, 1, 0, 1], + [1, 0, 1, 0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=5000, + motif_index=0, + coordinate_mode="local_window", + smoothing=None, + ) + + assert fig is not None + assert stats["coordinate_mode"] == "local_window" + data_axes = [ax for ax in fig.axes if ax.lines] + assert len(data_axes) == 2 + assert [float(ax.lines[0].get_xdata()[0]) for ax in data_axes] == [0.0, 0.0] + assert all(ax.get_xlim()[0] < 0 < ax.get_xlim()[1] for ax in data_axes) + + +def test_plot_multisite_read_raster_site_selection_defaults_min_distance_to_window_width(): + import matplotlib + + matplotlib.use("Agg") + data = np.zeros((3, 4000), dtype=float) + data[:, 2000] = 1.0 + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 1000, + "region_end": 1004, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 3000, + "region_end": 3004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult(data, None, meta, [], None) + + fig, stats = cluster.plot_multisite_read_raster( + r, + site_selection={"mode": "cooccurring", "n_windows": 2}, + window_widths_bp=2000, + smoothing=None, + ) + + assert fig is not None + assert stats["site_selection"]["min_distance_bp"] == 2000 + assert stats["observed_window_center_offsets_bp"] == [[0.0], [3000.0]] + assert stats["observed_window_center_offsets_summary_bp"] == [ + {"n": 1, "min": 0.0, "median": 0.0, "max": 0.0, "unique": 1}, + {"n": 1, "min": 3000.0, "median": 3000.0, "max": 3000.0, "unique": 1}, + ] + assert stats["rows_are"] == "reads" + assert stats["unique_reads"] == 1 + assert stats["site_sets"] == 1 + + +def test_plot_multisite_read_raster_site_selection_choose_span_and_max_distance(): + import matplotlib + + matplotlib.use("Agg") + data = np.tile(np.array([[0, 1, 0, 1]], dtype=float), (4, 1)) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": start, + "region_end": start + 4, + "region_strand": "+", + } + for start in [0, 1000, 3000, 7000] + ] + r = cluster.ReadWindowExtractionResult(data, None, meta, [], None) + + _, longest = cluster.plot_multisite_read_raster( + r, + site_selection={ + "mode": "cooccurring", + "n_windows": 2, + "min_distance_bp": 1000, + "max_distance_bp": 4000, + "choose": "longest_span", + }, + window_widths_bp=4, + smoothing=None, + ) + _, shortest = cluster.plot_multisite_read_raster( + r, + site_selection={ + "mode": "cooccurring", + "n_windows": 2, + "min_distance_bp": 1000, + "max_distance_bp": 4000, + "choose": "shortest_span", + }, + window_widths_bp=4, + smoothing=None, + ) + + assert longest["observed_window_center_offsets_bp"] == [[0.0], [4000.0]] + assert shortest["observed_window_center_offsets_bp"] == [[0.0], [1000.0]] + + +def test_plot_multisite_read_raster_site_selection_strand_relation_and_orientation(): + import matplotlib + + matplotlib.use("Agg") + data = np.tile(np.array([[0, 1, 0, 1]], dtype=float), (4, 1)) + meta = [ + { + "read_name": "r_same", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r_same", + "chromosome": "chr1", + "region_start": 3000, + "region_end": 3004, + "region_strand": "+", + }, + { + "read_name": "r_opp", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "-", + }, + { + "read_name": "r_opp", + "chromosome": "chr1", + "region_start": 3000, + "region_end": 3004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult(data, None, meta, [], None) + + _, same = cluster.plot_multisite_read_raster( + r, + site_selection={ + "mode": "cooccurring", + "n_windows": 2, + "min_distance_bp": 1000, + "strand_relation": "same", + }, + window_widths_bp=4, + smoothing=None, + sort_by="read_name", + sort_descending=False, + ) + _, opposite = cluster.plot_multisite_read_raster( + r, + site_selection={ + "mode": "cooccurring", + "n_windows": 2, + "min_distance_bp": 1000, + "strand_relation": "opposite", + "orientation": "anchor_strand", + }, + window_widths_bp=4, + smoothing=None, + ) + + assert same["pairs"] == 1 + assert opposite["pairs"] == 1 + assert opposite["orientation_applied"] == "anchor_strand" + assert opposite["observed_window_center_offsets_bp"] == [[0.0], [-3000.0]] + + +def test_plot_multisite_read_raster_site_selection_excludes_rows_and_regions(tmp_path): + import matplotlib + + matplotlib.use("Agg") + data = np.tile(np.array([[0, 1, 0, 1]], dtype=float), (4, 1)) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": start, + "region_end": start + 4, + "region_strand": "+", + } + for start in [0, 3000, 6000, 9000] + ] + bed = tmp_path / "exclude.bed" + bed.write_text("chr1\t2990\t3010\n") + r = cluster.ReadWindowExtractionResult(data, None, meta, [], None) + + _, stats = cluster.plot_multisite_read_raster( + r, + site_selection={ + "mode": "cooccurring", + "n_windows": 2, + "min_distance_bp": 1000, + "exclude": {"row_indices": [0], "regions": bed}, + }, + window_widths_bp=4, + smoothing=None, + ) + + assert stats["observed_window_center_offsets_bp"] == [[0.0], [3000.0]] + assert stats["site_selection"]["excluded_sites"] == 2 + + +def test_plot_multisite_read_raster_site_selection_random_choice_is_seeded(): + import matplotlib + + matplotlib.use("Agg") + data = np.tile(np.array([[0, 1, 0, 1]], dtype=float), (4, 1)) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": start, + "region_end": start + 4, + "region_strand": "+", + } + for start in [0, 1000, 3000, 6000] + ] + r = cluster.ReadWindowExtractionResult(data, None, meta, [], None) + selector = { + "mode": "cooccurring", + "n_windows": 2, + "min_distance_bp": 1000, + "choose": "random", + "selection_seed": 7, + } + + _, first = cluster.plot_multisite_read_raster( + r, + site_selection=selector, + window_widths_bp=4, + smoothing=None, + ) + _, second = cluster.plot_multisite_read_raster( + r, + site_selection=selector, + window_widths_bp=4, + smoothing=None, + ) + + assert first["observed_window_center_offsets_bp"] == second["observed_window_center_offsets_bp"] + + +def test_plot_multisite_read_raster_site_selection_rejects_all_valid_sets_for_now(): + data = np.tile(np.array([[0, 1, 0, 1]], dtype=float), (2, 1)) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 3000, + "region_end": 3004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult(data, None, meta, [], None) + + with pytest.raises(NotImplementedError, match="all_valid_sets"): + cluster.plot_multisite_read_raster( + r, + site_selection={ + "mode": "cooccurring", + "n_windows": 2, + "selection_multiplicity": "all_valid_sets", + }, + smoothing=None, + ) + + +def test_plot_multisite_read_raster_heatmap_auto_uses_raw_values(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0, 1, 0, 0], + [0, 0, 1, 0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=5000, + motif_index=0, + render_mode="heatmap", + render_values="auto", + axis_orientation="position_x", + smoothing="gaussian", + rotate=False, + ) + assert fig is not None + assert stats["render_values"] == "raw" + + +def test_plot_multisite_read_raster_heatmap_relative_axis_uses_window_offsets(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0.0, 0.8, 0.0, 0.0], + [0.0, 0.0, 0.7, 0.0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, _ = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=5000, + motif_index=0, + render_mode="heatmap", + render_values="smoothed", + smoothing="gaussian", + axis_orientation="position_x", + coordinate_mode="relative_to_primary", + rotate=True, + ) + data_axes = [ax for ax in fig.axes if ax.images] + assert len(data_axes) == 2 + center_0 = float(data_axes[0].lines[0].get_xdata()[0]) + center_1 = float(data_axes[1].lines[0].get_xdata()[0]) + assert abs(center_0) < 5 + assert center_1 > 1000 + + +def test_plot_multisite_read_raster_heatmap_relative_axis_rejects_variable_offsets(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0.0, 0.8, 0.0, 0.0], + [0.0, 0.0, 0.7, 0.0], + [0.0, 0.6, 0.0, 0.0], + [0.0, 0.0, 0.5, 0.0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + }, + { + "read_name": "r2", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r2", + "chromosome": "chr1", + "region_start": 7000, + "region_end": 7004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + + with pytest.raises( + ValueError, + match="render_mode='scatter'.*coordinate_mode='local_window'.*fixed offsets", + ): + cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=5000, + motif_index=0, + render_mode="heatmap", + coordinate_mode="relative_to_primary", + smoothing=None, + ) + + +def test_plot_multisite_read_raster_heatmap_smoothing_label_mentions_per_read_axis(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0.0, 0.8, 0.0, 0.0], + [0.0, 0.0, 0.7, 0.0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, _ = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=5000, + motif_index=0, + render_mode="heatmap", + render_values="smoothed", + smoothing="gaussian", + axis_orientation="position_x", + rotate=True, + ) + colorbar_axes = [ + ax + for ax in fig.axes + if "Signal heatmap" in ax.get_ylabel() + and "smoothed" in ax.get_ylabel() + and "gaussian" in ax.get_ylabel() + ] + assert colorbar_axes + + +def test_plot_multisite_read_raster_heatmap_thresholds_still_use_colorbar(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0.0, 0.9, 0.0, 0.0], + [0.0, 0.0, 0.4, 0.0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=5000, + motif_index=0, + render_mode="heatmap", + ml_score_thresholds=[0.5], + smoothing=None, + ) + + assert fig is not None + assert stats["render_mode"] == "heatmap" + assert stats["ml_score_thresholds"] is None + assert any("Signal heatmap" in ax.get_ylabel() for ax in fig.axes) + assert not any( + legend.get_title().get_text() == "Motif thresholds" for legend in fig.legends + ) + + +def test_plot_multisite_read_raster_accepts_axis_orientation_and_sort_modes(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0, 1, 0, 0], + [0, 0, 1, 0], + [1, 0, 0, 0], + [0, 0, 0, 1], + ], + dtype=float, + ) + meta = [ + { + "read_name": "z_read", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + "read_length": 7000, + }, + { + "read_name": "z_read", + "chromosome": "chr1", + "region_start": 3000, + "region_end": 3004, + "region_strand": "+", + "read_length": 7000, + }, + { + "read_name": "a_read", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + "read_length": 6500, + }, + { + "read_name": "a_read", + "chromosome": "chr1", + "region_start": 3000, + "region_end": 3004, + "region_strand": "+", + "read_length": 6500, + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=2000, + motif_index=0, + render_mode="scatter", + axis_orientation="position_y", + sort_by="read_name", + sort_descending=False, + smoothing=None, + ) + assert fig is not None + assert stats["axis_orientation"] == "position_y" + assert stats["sort_by"] == "read_name" + # At least one data axis should now expose reads on x. + assert any(ax.get_xlabel() == "Reads (sorted)" for ax in fig.axes) + + +def test_plot_multisite_read_raster_default_scatter_uses_ml_score_colorbar(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0.0, 0.9, 0.0, 0.0], + [0.0, 0.0, 0.4, 0.0], + ], + dtype=float, + ) + val = np.array( + [ + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=val, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=5000, + motif_index=0, + render_mode="scatter", + smoothing=None, + ) + assert fig is not None + assert stats["scatter_color_values"] == "ml_score" + assert any("normalized ML score" in ax.get_ylabel() for ax in fig.axes) + # Ensure ML coloring tracks mod values (0.9 / 0.4), not val-mask (1.0 / 1.0). + scatter_arrays = [ + np.asarray(collection.get_array(), dtype=float) + for axis in fig.axes + if axis.get_ylabel() == "Reads (sorted)" + for collection in getattr(axis, "collections", []) + if collection.get_array() is not None and len(collection.get_array()) > 0 + ] + assert any(np.isclose(arr, 0.9).any() for arr in scatter_arrays) + assert any(np.isclose(arr, 0.4).any() for arr in scatter_arrays) + + +def test_plot_multisite_read_raster_scatter_auto_downsamples_uniformly(): + import matplotlib + + matplotlib.use("Agg") + n_reads = 12 + data_rows = [] + meta = [] + for read_idx in range(n_reads): + left = np.zeros(4, dtype=float) + right = np.zeros(4, dtype=float) + left[read_idx % 4] = 1.0 + right[(read_idx + 1) % 4] = 1.0 + data_rows.extend([left, right]) + meta.extend( + [ + { + "read_name": f"r{read_idx}", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + "read_length": 7000, + }, + { + "read_name": f"r{read_idx}", + "chromosome": "chr1", + "region_start": 3000, + "region_end": 3004, + "region_strand": "+", + "read_length": 7000, + }, + ] + ) + r = cluster.ReadWindowExtractionResult( + data_matrix=np.asarray(data_rows), + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + + fig, stats = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=2000, + motif_index=0, + render_mode="scatter", + smoothing=None, + max_rows=5, + ) + + assert fig is not None + assert stats["downsampled"] is True + assert stats["downsample_method"] == "uniform" + scatter_arrays = [ + np.asarray(collection.get_array(), dtype=float) + for axis in fig.axes + if axis.get_ylabel() == "Reads (sorted)" + for collection in getattr(axis, "collections", []) + if collection.get_array() is not None and len(collection.get_array()) > 0 + ] + assert scatter_arrays + assert all(set(np.unique(arr)).issubset({1.0}) for arr in scatter_arrays) + + +def test_plot_multisite_read_raster_uniform_downsampling_stats(): + import matplotlib + + matplotlib.use("Agg") + data = np.tile(np.array([[0.0, 0.9, 0.0, 0.0]], dtype=float), (20, 1)) + meta = [] + for idx in range(20): + meta.append( + { + "read_name": f"r{idx}", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + } + ) + meta.append( + { + "read_name": f"r{idx}", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + } + ) + r = cluster.ReadWindowExtractionResult( + data_matrix=np.vstack([data, data]), + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=5000, + motif_index=0, + render_mode="scatter", + max_rows=8, + downsample_method="uniform", + smoothing=None, + ) + assert fig is not None + assert stats["downsampled"] is True + assert stats["downsample_method"] == "uniform" + assert stats["rows_before_downsample"] > stats["rows_after_downsample"] + + +def test_plot_multisite_read_raster_drops_reads_that_do_not_span_display_windows(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0.0, 1.0, 0.0, 0.0], # long read, window 1 + [0.0, 1.0, 0.0, 0.0], # long read, window 2 + [0.0, 1.0, 0.0, 0.0], # short read, window 1 + [0.0, 1.0, 0.0, 0.0], # short read, window 2 (outside read_end) + ], + dtype=float, + ) + meta = [ + { + "read_name": "r_long", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + "read_start": -10, + "read_end": 3000, + }, + { + "read_name": "r_long", + "chromosome": "chr1", + "region_start": 2000, + "region_end": 2004, + "region_strand": "+", + "read_start": -10, + "read_end": 3000, + }, + { + "read_name": "r_short", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + "read_start": -10, + "read_end": 2001, + }, + { + "read_name": "r_short", + "chromosome": "chr1", + "region_start": 2000, + "region_end": 2004, + "region_strand": "+", + "read_start": -10, + "read_end": 2001, + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=1000, + motif_index=0, + window_widths_bp=4, + enforce_full_window_span=True, + smoothing=None, + ) + assert fig is not None + assert stats["pairs"] == 1 + assert stats["dropped_for_window_span"] >= 1 + + +def test_plot_multisite_read_raster_ml_thresholds_use_motif_colors(): + import matplotlib + + matplotlib.use("Agg") + data = np.hstack( + [ + np.array( + [ + [0, 1, 0, 0], + [0, 0, 1, 0], + ], + dtype=float, + ), + np.array( + [ + [0, 1, 0, 0], + [0, 1, 0, 0], + ], + dtype=float, + ), + ] + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 6000, + "region_end": 6004, + "region_strand": "+", + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + n_windows=2, + min_separation_bp=5000, + motif_index=0, + motif_count=2, + plot_all_motifs=True, + motif_labels=["A,0", "CG,0"], + render_mode="scatter", + ml_score_thresholds=[0.5, 0.75], + motif_colors=["#1f77b4", "#ff7f0e"], + smoothing=None, + ) + assert fig is not None + assert stats["ml_score_thresholds"] == {"A,0": 0.5, "CG,0": 0.75} + assert stats["read_order_consistent"] is True + assert any( + legend.get_title().get_text() == "Motif thresholds" for legend in fig.legends + ) + + +def test_plot_multisite_read_raster_multimotif_nonrotated_uses_effective_window_count(): + import matplotlib + + matplotlib.use("Agg") + motif_a = np.array( + [ + [0, 1, 0, 1], + [1, 0, 1, 0], + [0, 1, 1, 0], + ], + dtype=float, + ) + motif_c = np.array( + [ + [1, 0, 1, 0], + [0, 1, 0, 1], + [1, 1, 0, 0], + ], + dtype=float, + ) + data = np.hstack([motif_a, motif_c]) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + "read_length": 7000, + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 2500, + "region_end": 2504, + "region_strand": "+", + "read_length": 7000, + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 5000, + "region_end": 5004, + "region_strand": "+", + "read_length": 7000, + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + fig, stats = cluster.plot_multisite_read_raster( + r, + motif_index=0, + motif_count=2, + plot_all_motifs=True, + motif_labels=["A,0", "CG,0"], + selection_mode="fixed_offsets", + window_offsets_bp=[0, 2500, 5000], + rotate=False, + render_mode="scatter", + coordinate_mode="relative_to_primary", + smoothing=None, + ) + assert stats["n_windows"] == 3 + # 2 motifs x 3 windows + one ML-score colorbar per motif + assert len(fig.axes) == 8 + assert any("A,0 normalized ML score" in ax.get_ylabel() for ax in fig.axes) + assert any("CG,0 normalized ML score" in ax.get_ylabel() for ax in fig.axes) + + +def test_plot_multisite_read_raster_rejects_too_small_min_read_length(): + data = np.array( + [ + [0, 1, 0, 1], + [1, 0, 1, 0], + [0, 1, 1, 0], + ], + dtype=float, + ) + meta = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + "region_strand": "+", + "read_length": 7000, + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 2500, + "region_end": 2504, + "region_strand": "+", + "read_length": 7000, + }, + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 5000, + "region_end": 5004, + "region_strand": "+", + "read_length": 7000, + }, + ] + r = cluster.ReadWindowExtractionResult( + data_matrix=data, + val_matrix=None, + metadata=meta, + datasets=[], + regions_dict=None, + ) + with pytest.raises(ValueError, match="min_read_length_bp"): + cluster.plot_multisite_read_raster( + r, + motif_index=0, + selection_mode="fixed_offsets", + window_offsets_bp=[0, 2500, 5000], + min_read_length_bp=100, + smoothing=None, + ) + + +def test_cluster_read_windows_kmeans(): + rng = np.random.default_rng(0) + feature_matrix = rng.normal(size=(20, 5)) + result = cluster.cluster_read_windows( + feature_matrix, method="kmeans", n_clusters=3, random_state=0 + ) + assert isinstance(result.labels_raw, np.ndarray) + assert result.labels_raw.shape[0] == 20 + assert result.labels_size_ordered.shape == result.labels_raw.shape + assert "silhouette" in result.metrics + + +def test_cluster_label_mapping_round_trips_labels(): + labels_raw = np.array([3, 3, 7, 7, 7]) + labels_size_ordered = np.array([1, 1, 0, 0, 0]) + + mapping = cluster.cluster_label_mapping(labels_raw, labels_size_ordered) + remapped = cluster.apply_cluster_label_mapping(np.array([7, 3, 9]), mapping) + + assert mapping == {3: 1, 7: 0} + np.testing.assert_array_equal(remapped, np.array([0, 1, -1])) + + +def test_extract_read_windows_infers_shortest_region_window(monkeypatch): + dataset_names = [ + "chromosome", + "mod_vector", + "motif", + "read_end", + "read_name", + "read_start", + "strand", + "val_vector", + "region_start", + "region_end", + "region_strand", + "read_length", + ] + records = [ + ( + "chr1", + np.arange(20, dtype=float), + "A,0", + 120, + "r1", + 100, + "+", + np.ones(20, dtype=float), + 104, + 110, # length 6 -> shortest + "+", + 20, + ), + ( + "chr1", + np.arange(20, dtype=float), + "A,0", + 120, + "r2", + 100, + "+", + np.ones(20, dtype=float), + 102, + 112, # length 10 + "+", + 20, + ), + ] + monkeypatch.setattr( + cluster.load_processed, + "read_vectors_from_hdf5", + lambda **kwargs: (records, dataset_names, None), + ) + result = cluster.extract_read_windows( + hdf5_file="reads.h5", + motifs=["A,0"], + config=cluster.ReadWindowExtractionConfig( + window_size=None, + orientation_aware=False, + enforce_thresholded_vectors=False, + ), + ) + assert result.data_matrix.shape == (2, 6) + + +def test_extract_read_windows_auto_thresholds_raw_vectors_by_default(monkeypatch): + dataset_names = [ + "chromosome", + "mod_vector", + "motif", + "read_end", + "read_name", + "read_start", + "strand", + "val_vector", + "region_start", + "region_end", + "region_strand", + "read_length", + ] + record = ( + "chr1", + np.array([0.0, 0.3, 0.8, 0.2, 0.9, 0.0], dtype=float), + "A,0", + 106, + "read1", + 100, + "+", + np.ones(6, dtype=float), + 101, + 105, + "+", + 6, + ) + + monkeypatch.setattr( + cluster.load_processed, + "read_vectors_from_hdf5", + lambda **kwargs: ([record], dataset_names, None), + ) + with pytest.warns(RuntimeWarning, match="applying automatic thresholding"): + result = cluster.extract_read_windows( + hdf5_file="reads.h5", + motifs=["A,0"], + config=cluster.ReadWindowExtractionConfig( + window_size=2, orientation_aware=False + ), + ) + + # default 190/255 ~= 0.745 -> [0.3, 0.8, 0.2, 0.9] => [0,1,0,1] + np.testing.assert_allclose(result.data_matrix, np.array([[0.0, 1.0, 0.0, 1.0]])) + + +def test_extract_read_windows_respects_adjustable_auto_threshold(monkeypatch): + dataset_names = [ + "chromosome", + "mod_vector", + "motif", + "read_end", + "read_name", + "read_start", + "strand", + "val_vector", + "region_start", + "region_end", + "region_strand", + "read_length", + ] + record = ( + "chr1", + np.array([0.0, 0.45, 0.8, 0.2, 0.9, 0.0], dtype=float), + "A,0", + 106, + "read1", + 100, + "+", + np.ones(6, dtype=float), + 101, + 105, + "+", + 6, + ) + + monkeypatch.setattr( + cluster.load_processed, + "read_vectors_from_hdf5", + lambda **kwargs: ([record], dataset_names, None), + ) + with pytest.warns(RuntimeWarning): + result = cluster.extract_read_windows( + hdf5_file="reads.h5", + motifs=["A,0"], + config=cluster.ReadWindowExtractionConfig( + window_size=2, + orientation_aware=False, + auto_threshold_if_raw=100, # 100/255 ~= 0.392 + ), + ) + + # threshold 100/255 -> [0.45,0.8,0.2,0.9] => [1,1,0,1] + np.testing.assert_allclose(result.data_matrix, np.array([[1.0, 1.0, 0.0, 1.0]])) + + +def test_extract_read_windows_errors_when_raw_vectors_disallowed(monkeypatch): + dataset_names = [ + "chromosome", + "mod_vector", + "motif", + "read_end", + "read_name", + "read_start", + "strand", + "val_vector", + "region_start", + "region_end", + "region_strand", + "read_length", + ] + record = ( + "chr1", + np.array([0.0, 0.45, 0.8, 0.2, 0.9, 0.0], dtype=float), + "A,0", + 106, + "read1", + 100, + "+", + np.ones(6, dtype=float), + 101, + 105, + "+", + 6, + ) + + monkeypatch.setattr( + cluster.load_processed, + "read_vectors_from_hdf5", + lambda **kwargs: ([record], dataset_names, None), + ) + with pytest.raises(ValueError, match="unthresholded"): + cluster.extract_read_windows( + hdf5_file="reads.h5", + motifs=["A,0"], + config=cluster.ReadWindowExtractionConfig( + window_size=2, + orientation_aware=False, + auto_threshold_if_raw=None, + enforce_thresholded_vectors=True, + ), + ) + + +def test_classify_read_features_binary_includes_row_index(): + X = np.vstack([np.zeros((8, 3)), np.ones((8, 3))]) + labels = np.array(["a"] * 8 + ["b"] * 8) + result = cluster.classify_read_features_binary( + X, + labels, + classifier="logreg", + random_state=0, + ) + preds = result["predictions"] + assert "row_index" in preds.columns + assert preds["row_index"].between(0, X.shape[0] - 1).all() + assert preds["row_index"].nunique() == X.shape[0] + + +def test_plot_classification_profiles_confusion_smoke(): + import matplotlib + + matplotlib.use("Agg") + data = np.array( + [ + [0, 1, 0, 1], + [1, 0, 1, 0], + [0, 0, 1, 1], + [1, 1, 0, 0], + ], + dtype=float, + ) + preds = pd.DataFrame( + { + "true_label": ["neg", "neg", "pos", "pos"], + "pred_label": ["neg", "pos", "neg", "pos"], + "proba": [0.1, 0.7, 0.4, 0.8], + "split": ["test"] * 4, + "sample_label": ["s"] * 4, + "row_index": [0, 1, 2, 3], + } + ) + fig = cluster.plot_classification_profiles( + data, + preds, + split="test", + group_by="confusion", + show_valid_profile=False, + ) + assert fig is not None + + +def test_plot_region_classification_profiles_smoke(): + import matplotlib + + matplotlib.use("Agg") + X = np.array( + [ + [0.1, 0.2, 0.1, 0.2], + [0.8, 0.7, 0.8, 0.7], + [0.2, 0.1, 0.2, 0.1], + [0.7, 0.8, 0.7, 0.8], + ], + dtype=float, + ) + preds = pd.DataFrame( + { + "true_label": ["neg", "pos", "neg", "pos"], + "pred_label": ["neg", "pos", "pos", "neg"], + "split": ["test"] * 4, + "row_index": [0, 1, 2, 3], + } + ) + fig = cluster.plot_region_classification_profiles( + X, + preds, + split="test", + group_by="confusion", + ) + assert fig is not None + + +def test_plot_classification_profiles_accepts_split_aligned_matrix_with_global_row_index(): + import matplotlib + + matplotlib.use("Agg") + full_matrix = np.array( + [ + [0.0, 0.1, 0.0, 0.1], + [0.9, 0.8, 0.9, 0.8], + [0.2, 0.1, 0.2, 0.1], + [0.8, 0.9, 0.8, 0.9], + [0.1, 0.2, 0.1, 0.2], + [0.7, 0.6, 0.7, 0.6], + ], + dtype=float, + ) + split_rows = np.array([5, 2, 4], dtype=int) + split_matrix = full_matrix[split_rows] + preds = pd.DataFrame( + { + "true_label": ["pos", "neg", "neg"], + "pred_label": ["pos", "neg", "pos"], + "proba": [0.9, 0.2, 0.7], + "split": ["test"] * 3, + "sample_label": ["s"] * 3, + # Global row indices do not fit split_matrix bounds. + "row_index": split_rows, + } + ) + fig = cluster.plot_classification_profiles( + split_matrix, + preds, + split="test", + group_by="pred_label", + ) + assert fig is not None + + +def test_plot_region_classification_profiles_accepts_split_aligned_matrix_with_global_row_index(): + import matplotlib + + matplotlib.use("Agg") + full_matrix = np.array( + [ + [0.1, 0.2, 0.1, 0.2], + [0.8, 0.7, 0.8, 0.7], + [0.2, 0.1, 0.2, 0.1], + [0.7, 0.8, 0.7, 0.8], + [0.3, 0.2, 0.3, 0.2], + [0.6, 0.5, 0.6, 0.5], + ], + dtype=float, + ) + split_rows = np.array([4, 5, 2], dtype=int) + split_matrix = full_matrix[split_rows] + preds = pd.DataFrame( + { + "true_label": ["neg", "pos", "neg"], + "pred_label": ["neg", "pos", "pos"], + "split": ["test"] * 3, + "row_index": split_rows, + } + ) + fig = cluster.plot_region_classification_profiles( + split_matrix, + preds, + split="test", + group_by="confusion", + ) + assert fig is not None + + +def test_plot_cluster_karyotype_handles_non_numeric_cluster_names(tmp_path): + import matplotlib + + matplotlib.use("Agg") + bed = tmp_path / "clusters.bed" + bed.write_text( + "chr1\t0\t10\talpha\t0\t+\nchr1\t20\t40\tbeta\t0\t+\nchr2\t5\t15\talpha\t0\t+\n" + ) + sizes = tmp_path / "chrom.sizes" + sizes.write_text("chr1\t100\nchr2\t80\n") + fig = cluster.plot_cluster_karyotype(bed, sizes) + assert fig is not None + + +def test_plot_cluster_karyotype_uses_top_bottom_coordinate_labels_and_draws_rare_cluster_on_top( + tmp_path, +): + import matplotlib + + matplotlib.use("Agg") + bed = tmp_path / "clusters_overlap.bed" + bed.write_text( + "chr1\t10\t20\t0\t0\t+\nchr1\t10\t20\t1\t0\t+\nchr2\t5\t15\t0\t0\t+\n" + ) + sizes = tmp_path / "chrom.sizes" + sizes.write_text("chr1\t100\nchr2\t80\n") + + fig = cluster.plot_cluster_karyotype( + bed, + sizes, + chromosome_order="natural", + min_visible_bp=1, + min_visible_fraction=0.0, + ) + assert fig is not None + ax = fig.axes[0] + + text_values = [t.get_text() for t in ax.texts] + # One "0" label per chromosome at the top. + assert text_values.count("0") >= 2 + # Bottom end-index labels should be present. + assert "100" in text_values + assert "80" in text_values + top_zero_labels = [text for text in ax.texts if text.get_text() == "0"] + assert len(top_zero_labels) >= 2 + # Top "0" labels should sit just above chromosome starts and centered on each line. + for text in top_zero_labels: + x_pos, y_pos = text.get_position() + assert x_pos == pytest.approx(round(x_pos)) + assert y_pos < 0 + assert y_pos > -5 + + end_labels = [text for text in ax.texts if text.get_text() in {"80", "100"}] + assert len(end_labels) == 2 + for text in end_labels: + x_pos, y_pos = text.get_position() + end_val = float(text.get_text()) + # End labels should be just below the chromosome end and centered. + assert x_pos == pytest.approx(round(x_pos)) + assert y_pos > end_val + assert y_pos < end_val + 6 + + legend = ax.get_legend() + assert legend is not None + label_to_color = { + text.get_text(): handle.get_color() + for text, handle in zip(legend.get_texts(), legend.legend_handles, strict=False) + } + assert "C0" in label_to_color + assert "C1" in label_to_color + + overlap_lines = [] + for line in ax.lines: + x_vals = np.asarray(line.get_xdata(), dtype=float) + y_vals = np.asarray(line.get_ydata(), dtype=float) + if x_vals.size != 2 or y_vals.size != 2: + continue + if np.allclose(x_vals, [0.0, 0.0]) and np.allclose(y_vals, [10.0, 20.0]): + overlap_lines.append(line) + + assert len(overlap_lines) == 2 + top_line = max(overlap_lines, key=lambda line: line.get_zorder()) + assert tuple(top_line.get_color()) == tuple(label_to_color["C1"]) + assert not ax.yaxis.get_visible() + + +def test_infer_shared_window_size(monkeypatch): + def fake_regions_dict_from_input(regions, window_size=None): + if regions == "a.bed": + return {"chr1": [(0, 100, "+"), (50, 120, "+")]} + if regions == "b.bed": + return {"chr1": [(0, 30, "+")], "chr2": [(10, 70, "-")]} + raise ValueError("unexpected input") + + monkeypatch.setattr( + cluster.utils, "regions_dict_from_input", fake_regions_dict_from_input + ) + shared = cluster._infer_shared_window_size(["a.bed", "b.bed"]) + assert shared == 15 + + +def test_merge_read_window_results_center_crop(): + r1 = cluster.ReadWindowExtractionResult( + data_matrix=np.arange(24, dtype=float).reshape(3, 8), + val_matrix=np.ones((3, 8), dtype=float), + metadata=[{"read_name": "r1"}, {"read_name": "r2"}, {"read_name": "r3"}], + datasets=["read_name"], + regions_dict=None, + ) + r2 = cluster.ReadWindowExtractionResult( + data_matrix=np.arange(10, dtype=float).reshape(2, 5), + val_matrix=np.ones((2, 5), dtype=float), + metadata=[{"read_name": "r4"}, {"read_name": "r5"}], + datasets=["read_name"], + regions_dict=None, + ) + merged = cluster.merge_read_window_results( + [r1, r2], + source_labels=["on", "off"], + align="center_crop", + ) + assert merged.data_matrix.shape == (5, 5) + assert merged.val_matrix is not None + assert merged.val_matrix.shape == (5, 5) + assert {m["source_label"] for m in merged.metadata} == {"on", "off"} + + +def test_merge_read_window_results_error_on_mismatch(): + r1 = cluster.ReadWindowExtractionResult( + data_matrix=np.zeros((2, 4), dtype=float), + val_matrix=None, + metadata=[], + datasets=[], + regions_dict=None, + ) + r2 = cluster.ReadWindowExtractionResult( + data_matrix=np.zeros((2, 6), dtype=float), + val_matrix=None, + metadata=[], + datasets=[], + regions_dict=None, + ) + with pytest.raises(ValueError, match="do not match"): + cluster.merge_read_window_results([r1, r2], align="error") + + +def test_extract_read_windows_rejects_nonpositive_window_size(monkeypatch): + dataset_names = [ + "chromosome", + "mod_vector", + "motif", + "read_end", + "read_name", + "read_start", + "strand", + "val_vector", + "region_start", + "region_end", + "region_strand", + "read_length", + ] + records = [ + ( + "chr1", + np.arange(20, dtype=float), + "A,0", + 120, + "r1", + 100, + "+", + np.ones(20, dtype=float), + 104, + 112, + "+", + 20, + ), + ] + monkeypatch.setattr( + cluster.load_processed, + "read_vectors_from_hdf5", + lambda **kwargs: (records, dataset_names, None), + ) + with pytest.raises(ValueError, match="window_size must be a positive integer"): + cluster.extract_read_windows( + hdf5_file="reads.h5", + motifs=["A,0"], + window_size=0, + ) diff --git a/tests/test_distribution.py b/tests/test_distribution.py new file mode 100644 index 0000000..fd1d8b4 --- /dev/null +++ b/tests/test_distribution.py @@ -0,0 +1,173 @@ +import pandas as pd + +from dimelo.distribution import ( + build_cluster_distribution, + build_condition_distribution, + build_distribution_change, +) +from dimelo.plotting import ( + prepare_cluster_distribution_bar_data, + prepare_cluster_distribution_heatmap_data, +) + + +def test_build_cluster_distribution_counts_and_fractions(): + assignments = pd.DataFrame( + { + "sample_id": ["s1", "s1", "s1", "s2"], + "condition": ["NS", "NS", "NS", "15min"], + "cluster": ["C0", "C0", "C1", "C1"], + } + ) + + result = build_cluster_distribution(assignments) + + assert list(result.columns) == [ + "sample_id", + "condition", + "cluster", + "count", + "fraction", + ] + s1 = ( + result[result["sample_id"] == "s1"] + .sort_values("cluster") + .reset_index(drop=True) + ) + assert list(s1["count"]) == [2, 1] + assert list(s1["fraction"]) == [2 / 3, 1 / 3] + + +def test_build_condition_distribution_aggregates_sample_distributions(): + cluster_distribution = pd.DataFrame( + { + "sample_id": ["s1", "s1", "s2", "s2"], + "condition": ["NS", "NS", "NS", "NS"], + "cluster": ["C0", "C1", "C0", "C1"], + "count": [2, 1, 1, 2], + "fraction": [2 / 3, 1 / 3, 1 / 3, 2 / 3], + } + ) + + result = build_condition_distribution(cluster_distribution) + + assert list(result.columns) == [ + "condition", + "cluster", + "count", + "fraction", + "replicate_n", + ] + assert set(result["condition"]) == {"NS"} + assert list(result.sort_values("cluster")["count"]) == [3, 3] + assert list(result.sort_values("cluster")["fraction"]) == [0.5, 0.5] + assert set(result["replicate_n"]) == {2} + + +def test_build_condition_distribution_tracks_condition_level_replicate_count(): + cluster_distribution = pd.DataFrame( + { + "sample_id": ["s1", "s1", "s2"], + "condition": ["NS", "NS", "NS"], + "cluster": ["C0", "C1", "C0"], + "count": [2, 1, 1], + "fraction": [2 / 3, 1 / 3, 1.0], + } + ) + + result = build_condition_distribution(cluster_distribution) + + assert set(result["replicate_n"]) == {2} + + +def test_build_distribution_change_against_reference_condition(): + condition_distribution = pd.DataFrame( + { + "condition": ["NS", "NS", "15min", "15min"], + "cluster": ["C0", "C1", "C0", "C1"], + "count": [3, 1, 1, 3], + "fraction": [0.75, 0.25, 0.25, 0.75], + "replicate_n": [2, 2, 2, 2], + } + ) + + result = build_distribution_change( + condition_distribution, + reference_condition="NS", + ) + + row = result[(result["condition"] == "15min") & (result["cluster"] == "C1")].iloc[0] + assert row["reference_fraction"] == 0.25 + assert row["delta_fraction"] == 0.5 + assert row["log2_fc"] > 0 + + +def test_build_distribution_change_accepts_minimal_condition_distribution_shape(): + condition_distribution = pd.DataFrame( + { + "condition": ["NS", "NS", "15min", "15min"], + "cluster": ["C0", "C1", "C0", "C1"], + "fraction": [0.75, 0.25, 0.25, 0.75], + } + ) + + result = build_distribution_change( + condition_distribution, + reference_condition="NS", + ) + + assert list(result.columns) == [ + "condition", + "cluster", + "count", + "fraction", + "replicate_n", + "reference_fraction", + "delta_fraction", + "log2_fc", + ] + assert result["count"].isna().all() + assert result["replicate_n"].isna().all() + + +def test_prepare_cluster_distribution_bar_data_returns_sorted_frame(): + cluster_distribution = pd.DataFrame( + { + "sample_id": ["s2", "s1"], + "condition": ["15min", "NS"], + "cluster": ["C1", "C0"], + "count": [1, 2], + "fraction": [1 / 3, 2 / 3], + } + ) + + result = prepare_cluster_distribution_bar_data(cluster_distribution) + + assert list(result.columns) == [ + "sample_id", + "condition", + "cluster", + "count", + "fraction", + ] + assert list(result["sample_id"]) == ["s1", "s2"] + assert list(result["cluster"]) == ["C0", "C1"] + + +def test_prepare_cluster_distribution_heatmap_data_pivots_conditions_and_clusters(): + condition_distribution = pd.DataFrame( + { + "condition": ["NS", "NS", "15min", "15min"], + "cluster": ["C0", "C1", "C0", "C1"], + "count": [3, 1, 1, 3], + "fraction": [0.75, 0.25, 0.25, 0.75], + "replicate_n": [2, 2, 2, 2], + } + ) + + result = prepare_cluster_distribution_heatmap_data(condition_distribution) + + assert list(result.columns) == ["condition", "C0", "C1"] + ns_row = result[result["condition"] == "NS"].iloc[0] + assert ns_row["C0"] == 0.75 + assert ns_row["C1"] == 0.25 diff --git a/tests/test_dmr.py b/tests/test_dmr.py new file mode 100644 index 0000000..da4dbe5 --- /dev/null +++ b/tests/test_dmr.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +import pandas as pd + +from dimelo import dmr, run_modkit + + +def _fake_capabilities() -> run_modkit.ModkitCapabilities: + return run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.6.1", + version="0.6.1", + version_tuple=(0, 6, 1), + supports_mod_threshold=True, + supports_mod_thresholds=False, + supports_modified_bases=True, + supports_force_allow_implicit=False, + supports_extract_subcommands=True, + extract_supports_reference_long=True, + extract_supports_reference_short=True, + ) + + +def test_run_dmr_pair_builds_command_and_parses_outputs(tmp_path, monkeypatch): + a = tmp_path / "a.bed.gz" + b = tmp_path / "b.bed.gz" + ref = tmp_path / "ref.fa" + out = tmp_path / "pair.bed" + seg = tmp_path / "pair.segment.bed" + for path in (a, b, ref): + path.write_text("x") + + captured: dict[str, object] = {} + + def fake_run(cmd, check): + captured["cmd"] = list(cmd) + out.write_text( + "#chrom\tstart\tend\tscore\tmap_pvalue\teffect_size\ta_total\n" + "chr1\t1\t2\t0.3\t0.001\t0.4\t10\n" + "chr1\t3\t4\t0.01\t0.6\t0.01\t3\n" + ) + seg.write_text( + "#chrom\tchrom_start\tchrom_end\tname\tscore\nchr1\t1\t10\tdiff\t0.2\n" + ) + return None + + monkeypatch.setattr(dmr.subprocess, "run", fake_run) + monkeypatch.setattr( + run_modkit, "_ensure_modkit_available", lambda **_: _fake_capabilities() + ) + + result = dmr.run_dmr_pair( + control_bed_methyl=a, + experiment_bed_methyl=b, + ref_genome=ref, + out_path=out, + segment_path=seg, + bases=["A"], + threads=2, + io_threads=1, + min_total_coverage=5, + ) + + assert isinstance(result.sites, pd.DataFrame) + assert isinstance(result.segments, pd.DataFrame) + assert result.high_confidence_sites.shape[0] == 1 + command = captured["cmd"] + assert "--segment" in command + assert "--base" in command + assert "--header" in command + + +def test_run_dmr_pair_rejects_regions_and_segment_together(tmp_path): + a = tmp_path / "a.bed.gz" + b = tmp_path / "b.bed.gz" + ref = tmp_path / "ref.fa" + regions = tmp_path / "regions.bed" + for path in (a, b, ref, regions): + path.write_text("x") + + try: + dmr.run_dmr_pair( + control_bed_methyl=a, + experiment_bed_methyl=b, + ref_genome=ref, + out_path=tmp_path / "out.bed", + regions_bed=regions, + segment_path=tmp_path / "seg.bed", + ) + except ValueError as exc: + assert "does not allow --regions-bed with --segment" in str(exc) + else: # pragma: no cover + raise AssertionError("Expected ValueError for regions_bed + segment_path") + + +def test_run_dmr_multi_collects_pair_files(tmp_path, monkeypatch): + sample_a = tmp_path / "a.bed.gz" + sample_b = tmp_path / "b.bed.gz" + regions = tmp_path / "regions.bed" + ref = tmp_path / "ref.fa" + out_dir = tmp_path / "dmr_multi" + for path in (sample_a, sample_b, regions, ref): + path.write_text("x") + + captured: dict[str, object] = {} + + def fake_run(cmd, check): + captured["cmd"] = list(cmd) + out_dir.mkdir(parents=True, exist_ok=True) + (out_dir / "s1_vs_s2.bed").write_text("chr1\t1\t2\n") + return None + + monkeypatch.setattr(dmr.subprocess, "run", fake_run) + monkeypatch.setattr( + run_modkit, "_ensure_modkit_available", lambda **_: _fake_capabilities() + ) + + result = dmr.run_dmr_multi( + samples={"s1": sample_a, "s2": sample_b}, + regions_bed=regions, + ref_genome=ref, + out_dir=out_dir, + bases=["A"], + threads=2, + ) + + assert result.pair_files.shape[0] == 1 + assert result.pair_files.iloc[0]["pair_name"] == "s1_vs_s2" + command = captured["cmd"] + assert "--sample" in command + assert "--regions-bed" in command diff --git a/tests/test_export.py b/tests/test_export.py new file mode 100644 index 0000000..864c6ef --- /dev/null +++ b/tests/test_export.py @@ -0,0 +1,337 @@ +from pathlib import Path + +import pytest + +from dimelo import export + + +def _make_bedmethyl_row( + *, + chrom: str, + start: int, + strand: str, + modified: int, + valid: int, +) -> str: + end = start + 1 + other = max(valid - modified, 0) + # modkit-compatible 18-column row + return ( + f"{chrom}\t{start}\t{end}\tY\t{valid}\t{strand}\t{start}\t{end}\t255,0,0\t" + f"{valid}\t0.00\t{modified}\t{other}\t0\t0\t0\t0\t0" + ) + + +def test_pileup_to_bigwig_sorts_and_collapses_duplicate_positions( + monkeypatch, tmp_path +): + rows_chr1 = [ + _make_bedmethyl_row(chrom="chr1", start=100, strand="+", modified=1, valid=2), + _make_bedmethyl_row(chrom="chr1", start=99, strand="+", modified=1, valid=1), + _make_bedmethyl_row(chrom="chr1", start=100, strand="-", modified=2, valid=2), + _make_bedmethyl_row(chrom="chr1", start=101, strand="+", modified=1, valid=2), + ] + + class _FakeTabixFile: + def __init__(self, *_args, **_kwargs): + self.contigs = ["chr1"] + + def fetch(self, contig): + assert contig == "chr1" + return iter(rows_chr1) + + class _FakeFastaFile: + def __init__(self, *_args, **_kwargs): + pass + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def get_reference_length(self, contig): + assert contig == "chr1" + return 1000 + + class _FakeBigWig: + def __init__(self): + self.header = None + self.entries = [] + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def addHeader(self, header): + self.header = header + + def addEntries(self, contigs, starts, *, ends, values): + self.entries.extend(zip(contigs, starts, ends, values, strict=False)) + + fake_bw = _FakeBigWig() + + monkeypatch.setattr( + export.utils, + "sanitize_path_args", + lambda bedmethyl_file, bigwig_file, ref_genome: ( + Path(bedmethyl_file), + Path(bigwig_file) if bigwig_file is not None else None, + Path(ref_genome) if ref_genome is not None else None, + ), + ) + monkeypatch.setattr(export.pysam, "TabixFile", _FakeTabixFile) + monkeypatch.setattr(export.pysam, "FastaFile", _FakeFastaFile) + monkeypatch.setattr(export.pyBigWig, "open", lambda *_args, **_kwargs: fake_bw) + + output_bigwig = tmp_path / "out.bw" + export.pileup_to_bigwig( + bedmethyl_file=tmp_path / "in.bed.gz", + motif="A,0", + bigwig_file=output_bigwig, + ref_genome=tmp_path / "ref.fa", + strand=".", + chunk_size=2, + ) + + assert fake_bw.header == [("chr1", 1000)] + starts = [entry[1] for entry in fake_bw.entries] + assert starts == [99, 100, 101] + values_by_start = {entry[1]: entry[3] for entry in fake_bw.entries} + assert values_by_start[99] == pytest.approx(1.0) + # 100 has two rows merged: (1+2)/(2+2) = 0.75 + assert values_by_start[100] == pytest.approx(0.75) + assert values_by_start[101] == pytest.approx(0.5) + + +def test_pileup_to_bigwig_sorts_rows_buffered_before_late_inversion( + monkeypatch, tmp_path +): + rows_chr1 = [ + _make_bedmethyl_row(chrom="chr1", start=100, strand="+", modified=1, valid=2), + _make_bedmethyl_row(chrom="chr1", start=101, strand="+", modified=1, valid=4), + _make_bedmethyl_row(chrom="chr1", start=99, strand="+", modified=1, valid=1), + _make_bedmethyl_row(chrom="chr1", start=100, strand="-", modified=2, valid=2), + ] + + class _FakeTabixFile: + def __init__(self, *_args, **_kwargs): + self.contigs = ["chr1"] + + def fetch(self, contig): + assert contig == "chr1" + return iter(rows_chr1) + + class _FakeFastaFile: + def __init__(self, *_args, **_kwargs): + pass + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def get_reference_length(self, contig): + assert contig == "chr1" + return 1000 + + class _FakeBigWig: + def __init__(self): + self.header = None + self.entries = [] + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def addHeader(self, header): + self.header = header + + def addEntries(self, contigs, starts, *, ends, values): + self.entries.extend(zip(contigs, starts, ends, values, strict=False)) + + fake_bw = _FakeBigWig() + + monkeypatch.setattr( + export.utils, + "sanitize_path_args", + lambda bedmethyl_file, bigwig_file, ref_genome: ( + Path(bedmethyl_file), + Path(bigwig_file) if bigwig_file is not None else None, + Path(ref_genome) if ref_genome is not None else None, + ), + ) + monkeypatch.setattr(export.pysam, "TabixFile", _FakeTabixFile) + monkeypatch.setattr(export.pysam, "FastaFile", _FakeFastaFile) + monkeypatch.setattr(export.pyBigWig, "open", lambda *_args, **_kwargs: fake_bw) + + output_bigwig = tmp_path / "out.bw" + export.pileup_to_bigwig( + bedmethyl_file=tmp_path / "in.bed.gz", + motif="A,0", + bigwig_file=output_bigwig, + ref_genome=tmp_path / "ref.fa", + strand=".", + chunk_size=1, + ) + + assert fake_bw.header == [("chr1", 1000)] + starts = [entry[1] for entry in fake_bw.entries] + assert starts == [99, 100, 101] + values_by_start = {entry[1]: entry[3] for entry in fake_bw.entries} + assert values_by_start[99] == pytest.approx(1.0) + # 100 has one row written before inversion plus one later row: (1+2)/(2+2) = 0.75 + assert values_by_start[100] == pytest.approx(0.75) + assert values_by_start[101] == pytest.approx(0.25) + + +def test_pileup_to_bigwig_streams_sorted_rows_before_iterator_is_exhausted( + monkeypatch, tmp_path +): + rows_chr1 = [ + _make_bedmethyl_row(chrom="chr1", start=100, strand="+", modified=1, valid=2), + _make_bedmethyl_row(chrom="chr1", start=101, strand="+", modified=1, valid=4), + _make_bedmethyl_row(chrom="chr1", start=102, strand="+", modified=3, valid=4), + ] + + class _FakeTabixFile: + def __init__(self, *_args, **_kwargs): + self.contigs = ["chr1"] + self.write_fetch_exhausted = True + + def fetch(self, contig): + assert contig == "chr1" + self.write_fetch_exhausted = False + try: + yield from rows_chr1 + finally: + self.write_fetch_exhausted = True + + class _FakeFastaFile: + def __init__(self, *_args, **_kwargs): + pass + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def get_reference_length(self, contig): + assert contig == "chr1" + return 1000 + + class _FakeBigWig: + def __init__(self, tabix): + self.header = None + self.entries = [] + self.add_entries_exhausted_states = [] + self.tabix = tabix + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def addHeader(self, header): + self.header = header + + def addEntries(self, contigs, starts, *, ends, values): + self.add_entries_exhausted_states.append(self.tabix.write_fetch_exhausted) + self.entries.extend(zip(contigs, starts, ends, values, strict=False)) + + fake_tabix = _FakeTabixFile() + fake_bw = _FakeBigWig(fake_tabix) + + monkeypatch.setattr( + export.utils, + "sanitize_path_args", + lambda bedmethyl_file, bigwig_file, ref_genome: ( + Path(bedmethyl_file), + Path(bigwig_file) if bigwig_file is not None else None, + Path(ref_genome) if ref_genome is not None else None, + ), + ) + monkeypatch.setattr(export.pysam, "TabixFile", lambda *_args, **_kwargs: fake_tabix) + monkeypatch.setattr(export.pysam, "FastaFile", _FakeFastaFile) + monkeypatch.setattr(export.pyBigWig, "open", lambda *_args, **_kwargs: fake_bw) + + export.pileup_to_bigwig( + bedmethyl_file=tmp_path / "in.bed.gz", + motif="A,0", + bigwig_file=tmp_path / "out.bw", + ref_genome=tmp_path / "ref.fa", + strand=".", + chunk_size=1, + ) + + assert fake_bw.add_entries_exhausted_states + assert fake_bw.add_entries_exhausted_states[0] is False + assert [entry[1] for entry in fake_bw.entries] == [100, 101, 102] + + +def test_pileup_to_bigwig_uses_max_end_for_header_without_reference( + monkeypatch, tmp_path +): + rows_chr1 = [ + _make_bedmethyl_row(chrom="chr1", start=100, strand="+", modified=1, valid=2), + _make_bedmethyl_row(chrom="chr1", start=105, strand="+", modified=1, valid=4), + _make_bedmethyl_row(chrom="chr1", start=99, strand="+", modified=1, valid=1), + ] + + class _FakeTabixFile: + def __init__(self, *_args, **_kwargs): + self.contigs = ["chr1"] + + def fetch(self, contig): + assert contig == "chr1" + return iter(rows_chr1) + + class _FakeBigWig: + def __init__(self): + self.header = None + self.entries = [] + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def addHeader(self, header): + self.header = header + + def addEntries(self, contigs, starts, *, ends, values): + self.entries.extend(zip(contigs, starts, ends, values, strict=False)) + + fake_bw = _FakeBigWig() + + monkeypatch.setattr( + export.utils, + "sanitize_path_args", + lambda bedmethyl_file, bigwig_file, ref_genome: ( + Path(bedmethyl_file), + Path(bigwig_file) if bigwig_file is not None else None, + Path(ref_genome) if ref_genome is not None else None, + ), + ) + monkeypatch.setattr(export.pysam, "TabixFile", _FakeTabixFile) + monkeypatch.setattr(export.pyBigWig, "open", lambda *_args, **_kwargs: fake_bw) + + export.pileup_to_bigwig( + bedmethyl_file=tmp_path / "in.bed.gz", + motif="A,0", + bigwig_file=tmp_path / "out.bw", + ref_genome=None, + strand=".", + chunk_size=2, + ) + + assert fake_bw.header == [("chr1", 106)] + assert [entry[1] for entry in fake_bw.entries] == [99, 100, 105] diff --git a/tests/test_global_analysis.py b/tests/test_global_analysis.py new file mode 100644 index 0000000..1c66fb5 --- /dev/null +++ b/tests/test_global_analysis.py @@ -0,0 +1,496 @@ +import pandas as pd +import pytest + +from dimelo import global_analysis +from dimelo.models import SampleSpec + + +def test_summarize_global_samples_from_pileup(monkeypatch): + samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="15min", + extract_h5="s2.h5", + replicate=2, + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + + counts = { + ("s1.bed.gz", "A,0"): (2, 10), + ("s1.bed.gz", "CG,0"): (1, 4), + ("s2.bed.gz", "A,0"): (3, 6), + ("s2.bed.gz", "CG,0"): (0, 0), + } + + observed_calls = [] + + def fake_global_counts_for_motifs_from_bedmethyl( + bedmethyl_file, motifs, quiet=True + ): + observed_calls.append((bedmethyl_file, tuple(motifs), quiet)) + return {motif: counts[(bedmethyl_file, motif)] for motif in motifs} + + monkeypatch.setattr( + global_analysis, + "_global_counts_for_motifs_from_bedmethyl", + fake_global_counts_for_motifs_from_bedmethyl, + ) + monkeypatch.setattr( + global_analysis, + "_global_counts_from_bedmethyl", + lambda *args, **kwargs: (_ for _ in ()).throw( + AssertionError("legacy single-motif counter should not be used here") + ), + ) + + result = global_analysis.summarize_global_samples( + samples=samples, + motifs=["A,0", "CG,0"], + ) + + expected = pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": None, + "motif": "A,0", + "modified_count": 2, + "valid_count": 10, + "global_fraction": 0.2, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": None, + "motif": "CG,0", + "modified_count": 1, + "valid_count": 4, + "global_fraction": 0.25, + }, + { + "sample_id": "s2", + "condition": "15min", + "replicate": 2, + "motif": "A,0", + "modified_count": 3, + "valid_count": 6, + "global_fraction": 0.5, + }, + { + "sample_id": "s2", + "condition": "15min", + "replicate": 2, + "motif": "CG,0", + "modified_count": 0, + "valid_count": 0, + "global_fraction": float("nan"), + }, + ] + ) + + pd.testing.assert_frame_equal(result, expected) + assert observed_calls == [ + ("s1.bed.gz", ("A,0", "CG,0"), True), + ("s2.bed.gz", ("A,0", "CG,0"), True), + ] + + +def test_global_counts_for_motifs_from_bedmethyl_single_pass(monkeypatch): + rows_by_contig = { + "chr1": ["row-1", "row-2"], + "chr2": ["row-3"], + } + + class FakeTabixFile: + opened_paths = [] + fetch_calls = [] + + def __init__(self, path): + self.opened_paths.append(path) + self.contigs = tuple(rows_by_contig) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + return False + + def fetch(self, contig): + self.fetch_calls.append(contig) + return iter(rows_by_contig[contig]) + + parsed_motifs = [] + + def fake_parsed_motif(motif): + parsed_motifs.append(motif) + return {"motif": motif} + + processed_rows = [] + process_results = { + ("row-1", "A,0"): (True, 10, 3, 8), + ("row-1", "CG,0"): (False, 10, 99, 99), + ("row-2", "A,0"): (False, 11, 99, 99), + ("row-2", "CG,0"): (True, 11, 4, 9), + ("row-3", "A,0"): (True, 12, 5, 10), + ("row-3", "CG,0"): (True, 12, 6, 12), + } + + def fake_process_pileup_row(row, parsed_motif, region_strand, single_strand=False): + motif = parsed_motif["motif"] + processed_rows.append((row, motif, region_strand, single_strand)) + return process_results[(row, motif)] + + monkeypatch.setattr(global_analysis.pysam, "TabixFile", FakeTabixFile) + monkeypatch.setattr(global_analysis.utils, "ParsedMotif", fake_parsed_motif) + monkeypatch.setattr( + global_analysis.load_processed, + "process_pileup_row", + fake_process_pileup_row, + ) + + counts_by_motif = global_analysis._global_counts_for_motifs_from_bedmethyl( + bedmethyl_file="pileup.bed.gz", + motifs=["A,0", "CG,0"], + ) + + assert counts_by_motif == { + "A,0": (8, 18), + "CG,0": (10, 21), + } + assert parsed_motifs == ["A,0", "CG,0"] + assert FakeTabixFile.opened_paths == ["pileup.bed.gz"] + assert FakeTabixFile.fetch_calls == ["chr1", "chr2"] + assert processed_rows == [ + ("row-1", "A,0", ".", False), + ("row-1", "CG,0", ".", False), + ("row-2", "A,0", ".", False), + ("row-2", "CG,0", ".", False), + ("row-3", "A,0", ".", False), + ("row-3", "CG,0", ".", False), + ] + + +def test_global_counts_from_bedmethyl_sums_only_matching_rows(monkeypatch): + rows_by_contig = { + "chr1": ["row-1", "row-2"], + "chr2": ["row-3"], + } + + class FakeTabixFile: + def __init__(self, path): + assert path == "pileup.bed.gz" + self.contigs = tuple(rows_by_contig) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + return False + + def fetch(self, contig): + return iter(rows_by_contig[contig]) + + parsed_motifs = [] + + def fake_parsed_motif(motif): + parsed_motifs.append(motif) + return {"motif": motif} + + processed_rows = [] + process_results = { + "row-1": (True, 10, 3, 8), + "row-2": (False, 11, 20, 30), + "row-3": (True, 12, 5, 10), + } + + def fake_process_pileup_row(row, parsed_motif, region_strand, single_strand=False): + processed_rows.append((row, parsed_motif, region_strand, single_strand)) + return process_results[row] + + monkeypatch.setattr(global_analysis.pysam, "TabixFile", FakeTabixFile) + monkeypatch.setattr(global_analysis.utils, "ParsedMotif", fake_parsed_motif) + monkeypatch.setattr( + global_analysis.load_processed, + "process_pileup_row", + fake_process_pileup_row, + ) + + modified_count, valid_count = global_analysis._global_counts_from_bedmethyl( + bedmethyl_file="pileup.bed.gz", + motif="A,0", + ) + + assert (modified_count, valid_count) == (8, 18) + assert parsed_motifs == ["A,0"] + assert processed_rows == [ + ("row-1", {"motif": "A,0"}, ".", False), + ("row-2", {"motif": "A,0"}, ".", False), + ("row-3", {"motif": "A,0"}, ".", False), + ] + + +def test_summarize_global_samples_requires_pileup_path(): + samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + ) + ] + + with pytest.raises(ValueError, match="missing metadata\\['pileup_path'\\]"): + global_analysis.summarize_global_samples(samples=samples, motifs=["A,0"]) + + +def test_tile_windows_from_genome_sizes_dict(): + windows = global_analysis.tile_genome_windows( + genome_sizes={"chr1": 2500}, + window_size=1000, + step_size=500, + ) + + assert windows["window_id"].tolist() == [ + "chr1:0-1000", + "chr1:500-1500", + "chr1:1000-2000", + "chr1:1500-2500", + ] + + +def test_build_window_summary_from_regions_to_list(monkeypatch): + samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + ] + windows = pd.DataFrame( + { + "window_id": ["chr1:0-1000", "chr1:500-1500"], + "chromosome": ["chr1", "chr1"], + "start": [0, 500], + "end": [1000, 1500], + "strand": [".", "."], + } + ) + + captured = {} + + def fake_tile_genome_windows( + genome_sizes, + window_size, + step_size, + include_contigs=None, + exclude_contigs=None, + ): + captured["genome_sizes"] = genome_sizes + captured["window_size"] = window_size + captured["step_size"] = step_size + captured["include_contigs"] = include_contigs + captured["exclude_contigs"] = exclude_contigs + return windows + + def fake_regions_to_list( + function_handle, + regions, + window_size=None, + quiet=True, + cores=None, + split_large_regions=False, + ): + captured["function_handle"] = function_handle + captured["regions"] = list(regions) + captured["window_size_arg"] = window_size + captured["quiet"] = quiet + captured["cores"] = cores + captured["split_large_regions"] = split_large_regions + return [(5, 10), (0, 0)] + + monkeypatch.setattr( + global_analysis, "tile_genome_windows", fake_tile_genome_windows + ) + monkeypatch.setattr( + global_analysis.load_processed, + "regions_to_list", + fake_regions_to_list, + ) + + summary = global_analysis.build_window_summary( + samples=samples, + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + window_size=1000, + step_size=500, + ) + + assert captured["genome_sizes"] == {"chr1": 1500} + assert captured["window_size"] == 1000 + assert captured["step_size"] == 500 + assert captured["regions"] == ["chr1:0-1000,.", "chr1:500-1500,."] + assert captured["function_handle"].keywords["bedmethyl_file"] == "s1.bed.gz" + assert captured["function_handle"].keywords["motif"] == "A,0" + assert summary["window_fraction"].tolist() == [0.5, 0.0] + + +def test_compute_global_normalization_factors_from_summary(): + summary = pd.DataFrame( + { + "sample_id": ["s1", "s2"], + "condition": ["NS", "15min"], + "replicate": [None, None], + "motif": ["A,0", "A,0"], + "modified_count": [10, 30], + "valid_count": [100, 100], + "global_fraction": [0.1, 0.3], + } + ) + + factors = global_analysis.compute_global_normalization_factors(summary) + + assert set(factors.columns) >= { + "sample_id", + "motif", + "global_fraction", + "reference_fraction", + "global_offset", + } + assert factors.loc[factors["sample_id"] == "s1", "global_offset"].iloc[ + 0 + ] == pytest.approx(-0.1) + assert factors.loc[factors["sample_id"] == "s2", "global_offset"].iloc[ + 0 + ] == pytest.approx(0.1) + + +def test_run_global_analysis_returns_result(monkeypatch): + samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + ] + + monkeypatch.setattr( + global_analysis, + "summarize_global_samples", + lambda *, samples, motifs, quiet=True: pd.DataFrame( + { + "sample_id": ["s1"], + "condition": ["NS"], + "replicate": [None], + "motif": ["A,0"], + "modified_count": [10], + "valid_count": [100], + "global_fraction": [0.1], + } + ), + ) + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **kwargs: pd.DataFrame({"window_id": ["chr1:0-1000"]}), + ) + monkeypatch.setattr( + global_analysis, + "compute_global_normalization_factors", + lambda summary: pd.DataFrame({"sample_id": ["s1"], "global_offset": [0.0]}), + ) + + result = global_analysis.run_global_analysis( + samples=samples, + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=1000, + step_size=1000, + ) + + assert list(result.summary["sample_id"]) == ["s1"] + assert list(result.windows["window_id"]) == ["chr1:0-1000"] + assert "global_fraction_bar" in result.plot_data + + +def test_run_global_analysis_supports_multiple_motifs(monkeypatch): + samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + ] + observed_build_calls = [] + + monkeypatch.setattr( + global_analysis, + "summarize_global_samples", + lambda *, samples, motifs, quiet=True: pd.DataFrame( + { + "sample_id": ["s1", "s1"], + "condition": ["NS", "NS"], + "replicate": [None, None], + "motif": list(motifs), + "modified_count": [10, 20], + "valid_count": [100, 200], + "global_fraction": [0.1, 0.1], + } + ), + ) + + def fake_build_window_summary(**kwargs): + motifs = list(kwargs["motifs"]) + observed_build_calls.append(motifs) + return pd.DataFrame( + { + "window_id": ["chr1:0-1000"], + "motif": motifs, + } + ) + + monkeypatch.setattr( + global_analysis, "build_window_summary", fake_build_window_summary + ) + monkeypatch.setattr( + global_analysis, + "compute_global_normalization_factors", + lambda summary: pd.DataFrame( + { + "sample_id": ["s1", "s1"], + "motif": ["A,0", "CG,0"], + "global_offset": [0.0, 0.0], + } + ), + ) + + result = global_analysis.run_global_analysis( + samples=samples, + motifs=["A,0", "CG,0"], + genome_sizes={"chr1": 1000}, + window_size=1000, + step_size=1000, + ) + + assert observed_build_calls == [["A,0"], ["CG,0"]] + assert result.windows["motif"].tolist() == ["A,0", "CG,0"] + + +def test_run_global_analysis_requires_at_least_one_motif(): + with pytest.raises(ValueError, match="at least one motif"): + global_analysis.run_global_analysis( + samples=[], + motifs=[], + genome_sizes={"chr1": 1000}, + window_size=1000, + step_size=1000, + ) diff --git a/tests/test_load_processed.py b/tests/test_load_processed.py new file mode 100644 index 0000000..521f7e8 --- /dev/null +++ b/tests/test_load_processed.py @@ -0,0 +1,492 @@ +import h5py +import numpy as np +import pytest + +from dimelo import load_processed +from dimelo import utils as dimelo_utils + + +def _single_region_chunks(): + return [ + { + "chromosome": "chr1", + "region_start": 100, + "region_end": 104, + "subregion_start": 100, + "subregion_end": 104, + "strand": "+", + } + ] + + +class _FakeTabix: + def __init__(self, *_args, **_kwargs): + self.contigs = {"chr1"} + + def fetch(self, _chromosome, _start, _end): + return ["row_a", "row_b"] + + +def test_process_pileup_row_parses_modkit_18_column_format(): + parsed_motif = dimelo_utils.ParsedMotif("A,0") + row = "chr1\t9167144\t9167145\tY\t12\t+\t9167144\t9167145\t255,0,0\t12\t0.00\t3\t9\t0\t0\t0\t0\t0" + keep, coord, modified, valid = load_processed.process_pileup_row( + row=row, + parsed_motif=parsed_motif, + region_strand="+", + single_strand=False, + ) + assert keep is True + assert coord == 9167144 + assert modified == 3 + assert valid == 12 + + +def test_process_pileup_row_skips_malformed_rows_without_raising(): + parsed_motif = dimelo_utils.ParsedMotif("A,0") + malformed_row = "chr1\t1\t2\tY\tbad\t+\t1\t2\t255,0,0\tbad\t0.00\tbad" + with pytest.warns(RuntimeWarning, match="Skipping malformed bedMethyl row"): + keep, coord, modified, valid = load_processed.process_pileup_row( + row=malformed_row, + parsed_motif=parsed_motif, + region_strand="+", + single_strand=False, + ) + assert (keep, coord, modified, valid) == (False, 0, 0, 0) + + +def test_resolve_cores_for_task_count_auto_small_batch_is_single_core(monkeypatch): + monkeypatch.setattr(load_processed.utils, "cores_to_run", lambda _cores: 8) + resolved = load_processed._resolve_cores_for_task_count( + requested_cores=None, + task_count=20, + ) + assert resolved == 1 + + +def test_resolve_cores_for_task_count_explicit_cores_are_preserved(monkeypatch): + monkeypatch.setattr( + load_processed.utils, "cores_to_run", lambda _cores: int(_cores) + ) + resolved = load_processed._resolve_cores_for_task_count( + requested_cores=4, + task_count=2, + ) + assert resolved == 4 + + +def test_memory_limited_cores_caps_auto_parallelism(monkeypatch): + monkeypatch.setattr(load_processed.utils, "cores_to_run", lambda _cores: 8) + monkeypatch.setattr( + load_processed, "_available_memory_bytes", lambda: 1024 * 1024 * 1024 + ) + monkeypatch.setattr(load_processed, "AUTO_PARALLEL_MEMORY_FRACTION", 0.5) + monkeypatch.setattr( + load_processed, "AUTO_PARALLEL_PROCESS_OVERHEAD_BYTES", 64 * 1024 * 1024 + ) + + resolved = load_processed._memory_limited_cores( + requested_cores=None, + task_count=1024, + bytes_per_task=128 * 1024 * 1024, + extra_shared_bytes=0, + ) + assert resolved == 2 + + +def test_memory_limited_cores_keeps_explicit_override(monkeypatch): + monkeypatch.setattr( + load_processed.utils, "cores_to_run", lambda _cores: int(_cores) + ) + monkeypatch.setattr( + load_processed, "_available_memory_bytes", lambda: 128 * 1024 * 1024 + ) + + resolved = load_processed._memory_limited_cores( + requested_cores=6, + task_count=1, + bytes_per_task=1024 * 1024 * 1024, + extra_shared_bytes=0, + ) + assert resolved == 6 + + +def test_memory_limited_cores_falls_back_when_memory_unknown(monkeypatch): + monkeypatch.setattr(load_processed.utils, "cores_to_run", lambda _cores: 8) + monkeypatch.setattr(load_processed, "_available_memory_bytes", lambda: None) + + resolved = load_processed._memory_limited_cores( + requested_cores=None, + task_count=160, + bytes_per_task=256 * 1024 * 1024, + extra_shared_bytes=0, + ) + assert resolved == 8 + + +def test_slurm_memory_limit_bytes_uses_mem_per_cpu(monkeypatch): + monkeypatch.setenv("SLURM_MEM_PER_CPU", "2048") + monkeypatch.setenv("SLURM_CPUS_PER_TASK", "4") + monkeypatch.delenv("SLURM_MEM_PER_NODE", raising=False) + assert load_processed._slurm_memory_limit_bytes() == 2048 * 4 * 1024 * 1024 + + +def test_slurm_memory_limit_bytes_uses_mem_per_node(monkeypatch): + monkeypatch.setenv("SLURM_MEM_PER_NODE", "8192") + monkeypatch.delenv("SLURM_MEM_PER_CPU", raising=False) + monkeypatch.delenv("SLURM_CPUS_PER_TASK", raising=False) + assert load_processed._slurm_memory_limit_bytes() == 8192 * 1024 * 1024 + + +def test_available_memory_bytes_respects_override(monkeypatch): + monkeypatch.setenv("DIMELO_AVAILABLE_MEMORY_BYTES", "12345") + monkeypatch.setattr(load_processed, "_cgroup_memory_limit_bytes", lambda: 999) + monkeypatch.setattr(load_processed, "_slurm_memory_limit_bytes", lambda: 999) + assert load_processed._available_memory_bytes() == 12345 + + +def test_cores_to_run_respects_slurm_and_affinity(monkeypatch): + monkeypatch.setattr(dimelo_utils.multiprocessing, "cpu_count", lambda: 64) + monkeypatch.setattr( + dimelo_utils.os, + "sched_getaffinity", + lambda _pid: set(range(8)), + raising=False, + ) + monkeypatch.setenv("SLURM_CPUS_PER_TASK", "4") + assert dimelo_utils.cores_to_run(None) == 4 + assert dimelo_utils.cores_to_run(12) == 4 + + +def test_get_tabix_file_caches_by_path(monkeypatch): + class _FakeTabixFile: + calls = 0 + + def __init__(self, _path): + _FakeTabixFile.calls += 1 + self.contigs = set() + + load_processed._clear_tabix_cache() + monkeypatch.setattr(load_processed.pysam, "TabixFile", _FakeTabixFile) + + first = load_processed._get_tabix_file("same.bed.gz") + second = load_processed._get_tabix_file("same.bed.gz") + third = load_processed._get_tabix_file("other.bed.gz") + + assert first is second + assert first is not third + assert _FakeTabixFile.calls == 2 + + +def test_pileup_vectors_from_bedmethyl_single_core_bypasses_process_pool(monkeypatch): + def _raise_if_called(*_args, **_kwargs): + raise AssertionError( + "ProcessPoolExecutor should not be created for single-core execution." + ) + + def fake_process_pileup_row(row, **_kwargs): + if row == "row_a": + return True, 100, 2, 5 + if row == "row_b": + return True, 102, 1, 3 + raise AssertionError(f"Unexpected row payload: {row}") + + monkeypatch.setattr( + load_processed.utils, + "regions_dict_from_input", + lambda *_args, **_kwargs: {"chr1": [(100, 104, "+")]}, + ) + monkeypatch.setattr( + load_processed.utils, + "process_chunks_from_regions_dict", + lambda *_args, **_kwargs: _single_region_chunks(), + ) + monkeypatch.setattr(load_processed.utils, "cores_to_run", lambda _cores: 1) + monkeypatch.setattr( + load_processed.concurrent.futures, "ProcessPoolExecutor", _raise_if_called + ) + monkeypatch.setattr(load_processed.pysam, "TabixFile", _FakeTabix) + monkeypatch.setattr(load_processed, "process_pileup_row", fake_process_pileup_row) + + modified, valid = load_processed.pileup_vectors_from_bedmethyl( + bedmethyl_file="fake.bed.gz", + motif="A,0", + regions="chr1:100-104,+", + quiet=True, + ) + + np.testing.assert_array_equal(modified, np.array([2, 0, 1, 0], dtype=np.int32)) + np.testing.assert_array_equal(valid, np.array([5, 0, 3, 0], dtype=np.int32)) + + +def test_regions_to_list_single_core_bypasses_process_pool(monkeypatch): + def _raise_if_called(*_args, **_kwargs): + raise AssertionError( + "ProcessPoolExecutor should not be created for single-core execution." + ) + + monkeypatch.setattr( + load_processed.utils, + "regions_dict_from_input", + lambda *_args, **_kwargs: {"chr1": [(100, 110, "+"), (120, 130, "-")]}, + ) + monkeypatch.setattr(load_processed.utils, "cores_to_run", lambda _cores: 1) + monkeypatch.setattr( + load_processed.concurrent.futures, "ProcessPoolExecutor", _raise_if_called + ) + + result = load_processed.regions_to_list( + function_handle=lambda regions, **_kwargs: f"ok:{regions}", + regions="ignored", + quiet=True, + ) + + assert result == ["ok:chr1:100-110,+", "ok:chr1:120-130,-"] + + +def test_regions_to_list_auto_cores_prefers_single_core_for_small_batches(monkeypatch): + def _raise_if_called(*_args, **_kwargs): + raise AssertionError( + "ProcessPoolExecutor should not be created for small auto-core batches." + ) + + monkeypatch.setattr( + load_processed.utils, + "regions_dict_from_input", + lambda *_args, **_kwargs: {"chr1": [(100, 110, "+"), (120, 130, "-")]}, + ) + monkeypatch.setattr(load_processed.utils, "cores_to_run", lambda _cores: 8) + monkeypatch.setattr( + load_processed.concurrent.futures, "ProcessPoolExecutor", _raise_if_called + ) + + result = load_processed.regions_to_list( + function_handle=lambda regions, **_kwargs: f"ok:{regions}", + regions="ignored", + quiet=True, + cores=None, + ) + + assert result == ["ok:chr1:100-110,+", "ok:chr1:120-130,-"] + + +def test_regions_to_list_explicit_cores_still_uses_process_pool(monkeypatch): + class _FakeExecutor: + last_max_workers = None + + def __init__(self, *, max_workers): + _FakeExecutor.last_max_workers = max_workers + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def map(self, fn, iterable): + return map(fn, iterable) + + monkeypatch.setattr( + load_processed.utils, + "regions_dict_from_input", + lambda *_args, **_kwargs: {"chr1": [(100, 110, "+"), (120, 130, "-")]}, + ) + monkeypatch.setattr( + load_processed.utils, "cores_to_run", lambda _cores: int(_cores) + ) + monkeypatch.setattr( + load_processed.concurrent.futures, "ProcessPoolExecutor", _FakeExecutor + ) + + result = load_processed.regions_to_list( + function_handle=lambda regions, **_kwargs: f"ok:{regions}", + regions="ignored", + quiet=True, + cores=2, + ) + + assert result == ["ok:chr1:100-110,+", "ok:chr1:120-130,-"] + assert _FakeExecutor.last_max_workers == 2 + + +def test_regions_to_list_parallel_batches_preserve_order(monkeypatch): + class _FakeExecutor: + observed_batch_count = None + + def __init__(self, *, max_workers): + self.max_workers = max_workers + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def map(self, fn, iterable): + batches = list(iterable) + _FakeExecutor.observed_batch_count = len(batches) + return map(fn, batches) + + regions = [(idx * 10, idx * 10 + 5, "+") for idx in range(100)] + monkeypatch.setattr( + load_processed.utils, + "regions_dict_from_input", + lambda *_args, **_kwargs: {"chr1": regions}, + ) + monkeypatch.setattr( + load_processed.utils, "cores_to_run", lambda _cores: int(_cores) + ) + monkeypatch.setattr( + load_processed.concurrent.futures, "ProcessPoolExecutor", _FakeExecutor + ) + + result = load_processed.regions_to_list( + function_handle=lambda regions, **_kwargs: f"ok:{regions}", + regions="ignored", + quiet=True, + cores=2, + ) + + expected = [f"ok:chr1:{start}-{end},+" for start, end, _strand in regions] + assert result == expected + assert _FakeExecutor.observed_batch_count is not None + assert _FakeExecutor.observed_batch_count < len(regions) + + +def test_regions_to_list_uses_external_executor_when_provided(monkeypatch): + class _ExternalExecutor: + called = False + + def map(self, fn, iterable): + _ExternalExecutor.called = True + return map(fn, iterable) + + def _raise_if_called(*_args, **_kwargs): + raise AssertionError( + "ProcessPoolExecutor should not be created when external executor is provided." + ) + + monkeypatch.setattr( + load_processed.utils, + "regions_dict_from_input", + lambda *_args, **_kwargs: {"chr1": [(100, 110, "+"), (120, 130, "-")]}, + ) + monkeypatch.setattr( + load_processed.utils, "cores_to_run", lambda _cores: int(_cores) + ) + monkeypatch.setattr( + load_processed.concurrent.futures, "ProcessPoolExecutor", _raise_if_called + ) + + executor = _ExternalExecutor() + result = load_processed.regions_to_list( + function_handle=lambda regions, **_kwargs: f"ok:{regions}", + regions="ignored", + quiet=True, + cores=4, + executor=executor, + ) + + assert result == ["ok:chr1:100-110,+", "ok:chr1:120-130,-"] + assert _ExternalExecutor.called + + +def test_pileup_counts_from_bedmethyl_single_core_bypasses_process_pool(monkeypatch): + def _raise_if_called(*_args, **_kwargs): + raise AssertionError( + "ProcessPoolExecutor should not be created for single-core execution." + ) + + def fake_process_pileup_row(row, **_kwargs): + if row == "row_a": + return True, 0, 2, 5 + if row == "row_b": + return True, 0, 1, 3 + raise AssertionError(f"Unexpected row payload: {row}") + + monkeypatch.setattr( + load_processed.utils, + "regions_dict_from_input", + lambda *_args, **_kwargs: {"chr1": [(100, 104, "+")]}, + ) + monkeypatch.setattr( + load_processed.utils, + "process_chunks_from_regions_dict", + lambda *_args, **_kwargs: _single_region_chunks(), + ) + monkeypatch.setattr(load_processed.utils, "cores_to_run", lambda _cores: 1) + monkeypatch.setattr( + load_processed.concurrent.futures, "ProcessPoolExecutor", _raise_if_called + ) + monkeypatch.setattr(load_processed.pysam, "TabixFile", _FakeTabix) + monkeypatch.setattr(load_processed, "process_pileup_row", fake_process_pileup_row) + + modified, valid = load_processed.pileup_counts_from_bedmethyl( + bedmethyl_file="fake.bed.gz", + motif="A,0", + regions="chr1:100-104,+", + quiet=True, + ) + + assert modified == 3 + assert valid == 8 + + +def test_read_vectors_from_hdf5_mod_fraction_order_and_missing_defaults(tmp_path): + h5_path = tmp_path / "reads.h5" + with h5py.File(h5_path, "w") as h5: + h5.create_dataset("read_name", data=np.array(["r1", "r1", "r2"], dtype="S2")) + h5.create_dataset( + "chromosome", data=np.array(["chr1", "chr1", "chr1"], dtype="S4") + ) + h5.create_dataset("read_start", data=np.array([0, 0, 10], dtype=np.int32)) + h5.create_dataset("read_end", data=np.array([4, 4, 14], dtype=np.int32)) + h5.create_dataset("motif", data=np.array(["A,0", "CG,0", "A,0"], dtype="S4")) + h5.create_dataset("strand", data=np.array(["+", "+", "+"], dtype="S1")) + h5.create_dataset( + "mod_vector", + data=np.array( + [ + [1, 0], + [1, 1], + [0, 1], + ], + dtype=np.uint8, + ), + ) + h5.create_dataset( + "val_vector", + data=np.array( + [ + [1, 1], + [1, 1], + [1, 1], + ], + dtype=np.uint8, + ), + ) + + reads, datasets, _regions_dict = load_processed.read_vectors_from_hdf5( + file=h5_path, + motifs=["CG,0", "A,0"], + regions=None, + sort_by=["read_name", "motif"], + calculate_mod_fractions=True, + ) + + assert datasets[-2:] == ["CG,0_mod_fraction", "A,0_mod_fraction"] + + dataset_idx = {name: i for i, name in enumerate(datasets)} + r1_rows = [row for row in reads if row[dataset_idx["read_name"]] == "r1"] + r2_rows = [row for row in reads if row[dataset_idx["read_name"]] == "r2"] + + assert len(r1_rows) == 2 + assert len(r2_rows) == 1 + + for row in r1_rows: + assert row[dataset_idx["CG,0_mod_fraction"]] == 1.0 + assert row[dataset_idx["A,0_mod_fraction"]] == 0.5 + + r2_row = r2_rows[0] + assert r2_row[dataset_idx["CG,0_mod_fraction"]] == 0.0 + assert r2_row[dataset_idx["A,0_mod_fraction"]] == 0.5 diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..d320e3e --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,1010 @@ +from pathlib import Path + +import pandas as pd +import pytest + +from dimelo import models +from dimelo.models import ( + BatchJob, + ChipAtlasEnrichmentResult, + CohortSpec, + ContrastSpec, + DatasetArtifact, + GlobalAnalysisResult, + ModkitDMRMultiResult, + ModkitDMRPairResult, + RegionContrastResult, + RegionDiscoveryClusterContrastResult, + RegionDiscoveryClusterResult, + RegionDiscoveryResult, + SampleSpec, + SharedClusterContrastResult, + SharedClusterModel, + SharedClusterResult, +) + + +def test_dimelo_package_exports_models(): + assert models.SampleSpec is SampleSpec + + +def test_dimelo_package_exports_dmr_module(): + from dimelo import dmr as root_dmr + + assert root_dmr is not None + + +def test_dimelo_package_root_exports_region_discovery_cluster_result(): + from dimelo import RegionDiscoveryClusterResult as RootRegionDiscoveryClusterResult + + assert RootRegionDiscoveryClusterResult is RegionDiscoveryClusterResult + + +def test_dimelo_package_root_exports_region_discovery_cluster_contrast_result(): + from dimelo import ( + RegionDiscoveryClusterContrastResult as RootRegionDiscoveryClusterContrastResult, + ) + + assert ( + RootRegionDiscoveryClusterContrastResult is RegionDiscoveryClusterContrastResult + ) + + +def test_dimelo_package_root_exports_chip_atlas_enrichment_result(): + from dimelo import ChipAtlasEnrichmentResult as RootChipAtlasEnrichmentResult + + assert RootChipAtlasEnrichmentResult is ChipAtlasEnrichmentResult + + +def test_dimelo_package_root_exports_modkit_dmr_pair_result(): + from dimelo import ModkitDMRPairResult as RootModkitDMRPairResult + + assert RootModkitDMRPairResult is ModkitDMRPairResult + + +def test_sample_spec_fields(): + sample = SampleSpec( + sample_id="sample-1", + condition="treated", + extract_h5=Path("sample-1.h5"), + regions_bed=Path("sample-1.bed"), + replicate=2, + metadata={"batch": "A"}, + ) + + assert sample.sample_id == "sample-1" + assert sample.condition == "treated" + assert sample.extract_h5 == Path("sample-1.h5") + assert sample.regions_bed == Path("sample-1.bed") + assert sample.replicate == 2 + assert sample.metadata == {"batch": "A"} + + +def test_chip_atlas_enrichment_result_requires_request_id(): + with pytest.raises(ValueError, match="request_id"): + ChipAtlasEnrichmentResult( + request_id="", + status="finished", + results=pd.DataFrame(), + ) + + +def test_modkit_dmr_pair_result_requires_core_outputs(): + with pytest.raises(ValueError, match="output_path"): + ModkitDMRPairResult( + output_path=None, + segment_path=None, + command=["modkit", "dmr", "pair"], + sites=pd.DataFrame(), + segments=None, + high_confidence_sites=pd.DataFrame(), + ) + + +def test_modkit_dmr_multi_result_requires_pair_files(): + with pytest.raises(ValueError, match="pair_files"): + ModkitDMRMultiResult( + out_dir=Path("/tmp"), + command=["modkit", "dmr", "multi"], + pair_files=None, + ) + + +def test_dataset_artifact_stores_metadata(): + artifact = DatasetArtifact( + sample_id="sample-1", + artifact_type="extract", + path=Path("sample-1.h5"), + format="hdf5", + params={"window_size": 200}, + provenance={"pipeline": "parse_bam"}, + metadata={"source": "parse_bam"}, + ) + + assert artifact.metadata == {"source": "parse_bam"} + assert artifact.params == {"window_size": 200} + assert artifact.provenance == {"pipeline": "parse_bam"} + + +def test_dataset_artifact_rejects_none_metadata(): + with pytest.raises(ValueError, match="metadata"): + DatasetArtifact( + sample_id="sample-1", + artifact_type="extract", + path=Path("sample-1.h5"), + format="hdf5", + params={"window_size": 200}, + provenance={"pipeline": "parse_bam"}, + metadata=None, + ) + + +def test_dataset_artifact_rejects_none_provenance(): + with pytest.raises(ValueError): + DatasetArtifact( + sample_id="sample-1", + artifact_type="extract", + path=Path("sample-1.h5"), + format="hdf5", + params={"window_size": 200}, + provenance=None, + ) + + +def test_dataset_artifact_rejects_none_params(): + with pytest.raises(ValueError, match="params"): + DatasetArtifact( + sample_id="sample-1", + artifact_type="extract", + path=Path("sample-1.h5"), + format="hdf5", + params=None, + provenance={"pipeline": "parse_bam"}, + ) + + +def test_contrast_spec_accepts_pairwise_mode(): + contrast = ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ) + + assert contrast.mode == "pairwise" + assert contrast.numerator == ["15min"] + assert contrast.denominator == ["NS"] + assert contrast.reference_condition == "NS" + + +def test_contrast_spec_accepts_group_vs_group_mode(): + contrast = ContrastSpec( + mode="group_vs_group", + numerator=["15min", "30min"], + denominator=["NS", "0min"], + ) + + assert contrast.mode == "group_vs_group" + assert contrast.numerator == ["15min", "30min"] + assert contrast.denominator == ["NS", "0min"] + + +def test_contrast_spec_accepts_background_adjusted_mode(): + contrast = ContrastSpec( + mode="background_adjusted", + numerator=["15min"], + denominator=["NS"], + background=["bg"], + ) + + assert contrast.mode == "background_adjusted" + assert contrast.background == ["bg"] + + +def test_contrast_spec_accepts_time_course_mode(): + contrast = ContrastSpec( + mode="time_course", + time_order=["NS", "15min", "30min"], + ) + + assert contrast.mode == "time_course" + assert contrast.time_order == ["NS", "15min", "30min"] + + +def test_contrast_spec_rejects_missing_groups_for_pairwise(): + with pytest.raises(ValueError, match="numerator and denominator"): + ContrastSpec(mode="pairwise") + + +def test_contrast_spec_rejects_missing_groups_for_group_vs_group(): + with pytest.raises(ValueError, match="numerator and denominator"): + ContrastSpec(mode="group_vs_group", numerator=["15min"]) + + +def test_contrast_spec_rejects_missing_pairing_key_for_matched_pairwise(): + with pytest.raises(ValueError, match="pairing_key"): + ContrastSpec( + mode="matched_pairwise", + numerator=["15min"], + denominator=["NS"], + ) + + +def test_contrast_spec_rejects_missing_groups_for_matched_pairwise(): + with pytest.raises(ValueError, match="matched_pairwise mode requires"): + ContrastSpec( + mode="matched_pairwise", + pairing_key="pair-1", + ) + + +def test_contrast_spec_rejects_missing_background_for_background_adjusted(): + with pytest.raises(ValueError, match="background"): + ContrastSpec( + mode="background_adjusted", + numerator=["15min"], + denominator=["NS"], + ) + + +def test_contrast_spec_rejects_missing_groups_for_background_adjusted(): + with pytest.raises(ValueError, match="numerator and denominator"): + ContrastSpec( + mode="background_adjusted", + background=["bg"], + ) + + +def test_contrast_spec_rejects_missing_time_order_for_time_course(): + with pytest.raises(ValueError, match="time_order"): + ContrastSpec(mode="time_course") + + +def test_shared_cluster_result_supports_plot_data(): + model = SharedClusterModel( + mode="shared", + motifs=["A,0"], + feature_names=["A,0_mod_fraction"], + preprocessing={"scale": "standard"}, + estimator=object(), + cluster_labels=["cluster-1"], + fit_metadata={"random_state": 7}, + ) + result = SharedClusterResult( + model=model, + assignments=pd.DataFrame({"cluster": ["cluster-1"]}), + cluster_distribution=pd.DataFrame({"cluster": ["cluster-1"]}), + condition_distribution=pd.DataFrame({"condition": ["treated"]}), + distribution_change=None, + cluster_profiles=pd.DataFrame({"profile": [1.0]}), + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={"notes": "ok"}, + ) + + assert result.plot_data["cluster_distribution_bar"] == {"kind": "bar"} + assert result.model is model + + +def test_shared_cluster_result_rejects_none_model(): + with pytest.raises(ValueError, match="model"): + SharedClusterResult( + model=None, + assignments=pd.DataFrame({"cluster": ["cluster-1"]}), + cluster_distribution=pd.DataFrame({"cluster": ["cluster-1"]}), + condition_distribution=pd.DataFrame({"condition": ["treated"]}), + distribution_change=None, + cluster_profiles=pd.DataFrame({"profile": [1.0]}), + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={"notes": "ok"}, + ) + + +@pytest.mark.parametrize( + "field_name", + [ + "assignments", + "cluster_distribution", + "condition_distribution", + "cluster_profiles", + "plot_data", + ], +) +def test_shared_cluster_result_rejects_none_core_outputs(field_name): + model = SharedClusterModel( + mode="shared", + motifs=["A,0"], + feature_names=["A,0_mod_fraction"], + preprocessing={"scale": "standard"}, + estimator=object(), + cluster_labels=["cluster-1"], + fit_metadata={"random_state": 7}, + ) + kwargs = { + "model": model, + "assignments": pd.DataFrame({"cluster": ["cluster-1"]}), + "cluster_distribution": pd.DataFrame({"cluster": ["cluster-1"]}), + "condition_distribution": pd.DataFrame({"condition": ["treated"]}), + "distribution_change": None, + "cluster_profiles": pd.DataFrame({"profile": [1.0]}), + "region_summaries": None, + "plot_data": {"cluster_distribution_bar": {"kind": "bar"}}, + "figures": {}, + "metadata": {"notes": "ok"}, + } + kwargs[field_name] = None + + with pytest.raises(ValueError): + SharedClusterResult(**kwargs) + + +def test_shared_cluster_contrast_result_requires_summary_details_and_plot_data(): + with pytest.raises( + ValueError, + match="SharedClusterContrastResult requires non-None values", + ): + SharedClusterContrastResult( + summary=None, + details=pd.DataFrame(), + plot_data={}, + figures={}, + ) + + +def test_global_analysis_result_supports_summary_windows_and_normalization(): + result = GlobalAnalysisResult( + summary=pd.DataFrame({"sample_id": ["s1"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + normalization_factors=pd.DataFrame( + {"sample_id": ["s1"], "global_offset": [0.1]} + ), + plot_data={"global_fraction_bar": pd.DataFrame({"sample_id": ["s1"]})}, + metadata={"normalization_mode": "per_sample_global"}, + figures={}, + ) + + assert list(result.summary["sample_id"]) == ["s1"] + assert list(result.windows["window_id"]) == ["chr1:0-1000"] + assert list(result.normalization_factors["sample_id"]) == ["s1"] + + +@pytest.mark.parametrize( + "field_name", + ["summary", "normalization_factors", "plot_data"], +) +def test_global_analysis_result_rejects_none_core_outputs(field_name): + kwargs = { + "summary": pd.DataFrame({"sample_id": ["s1"]}), + "windows": pd.DataFrame({"window_id": ["chr1:0-1000"]}), + "normalization_factors": pd.DataFrame( + {"sample_id": ["s1"], "global_offset": [0.1]} + ), + "plot_data": {"global_fraction_bar": pd.DataFrame({"sample_id": ["s1"]})}, + "metadata": {"normalization_mode": "per_sample_global"}, + "figures": {}, + } + kwargs[field_name] = None + + with pytest.raises(ValueError, match=field_name): + GlobalAnalysisResult(**kwargs) + + +@pytest.mark.parametrize("field_name", ["metadata", "figures"]) +def test_global_analysis_result_rejects_none_optional_outputs(field_name): + kwargs = { + "summary": pd.DataFrame({"sample_id": ["s1"]}), + "windows": pd.DataFrame({"window_id": ["chr1:0-1000"]}), + "normalization_factors": pd.DataFrame( + {"sample_id": ["s1"], "global_offset": [0.1]} + ), + "plot_data": {"global_fraction_bar": pd.DataFrame({"sample_id": ["s1"]})}, + "metadata": {"normalization_mode": "per_sample_global"}, + "figures": {}, + } + kwargs[field_name] = None + + with pytest.raises(ValueError, match=field_name): + GlobalAnalysisResult(**kwargs) + + +def test_region_contrast_result_rejects_none_core_tables(): + contrast = ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ) + + with pytest.raises(ValueError, match="regions, summary, plot_data"): + RegionContrastResult( + regions=None, + summary=None, + contrast=contrast, + plot_data=None, + ) + + +def test_region_contrast_result_rejects_none_contrast(): + with pytest.raises(ValueError, match="contrast"): + RegionContrastResult( + regions=pd.DataFrame({"region": ["r1"]}), + summary=pd.DataFrame({"metric": [1.0]}), + contrast=None, + plot_data={"volcano": pd.DataFrame({"x": [1.0]})}, + ) + + +def test_region_discovery_result_accepts_tables_and_plot_data(): + result = RegionDiscoveryResult( + hits=pd.DataFrame({"region": ["chr1:0-1000"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + contrast=None, + plot_data={ + "ranked_hits": pd.DataFrame({"region": ["chr1:0-1000"]}), + "summary": {"kind": "table"}, + }, + metadata={"analysis_unit": "genome"}, + figures={"hit_track": object()}, + ) + + assert list(result.hits["region"]) == ["chr1:0-1000"] + assert list(result.windows["window_id"]) == ["chr1:0-1000"] + assert result.plot_data["summary"] == {"kind": "table"} + assert result.metadata == {"analysis_unit": "genome"} + + +@pytest.mark.parametrize("field_name", ["hits", "windows", "plot_data"]) +def test_region_discovery_result_rejects_missing_required_fields(field_name): + kwargs = { + "hits": pd.DataFrame({"region": ["chr1:0-1000"]}), + "windows": pd.DataFrame({"window_id": ["chr1:0-1000"]}), + "contrast": None, + "plot_data": { + "ranked_hits": pd.DataFrame({"region": ["chr1:0-1000"]}), + "summary": {"kind": "table"}, + }, + "metadata": {"analysis_unit": "genome"}, + "figures": {}, + } + kwargs[field_name] = None + + with pytest.raises(ValueError, match=field_name): + RegionDiscoveryResult(**kwargs) + + +def test_region_discovery_cluster_result_supports_wrapped_outputs(): + discovery = RegionDiscoveryResult( + hits=pd.DataFrame({"region": ["chr1:0-1000"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + contrast=None, + plot_data={"ranked_hits": pd.DataFrame({"region": ["chr1:0-1000"]})}, + metadata={"analysis_unit": "genome"}, + figures={"hit_track": object()}, + ) + clustering = SharedClusterResult( + model=SharedClusterModel( + mode="shared", + motifs=["A,0"], + feature_names=["A,0_mod_fraction"], + preprocessing={"scale": "standard"}, + estimator=object(), + cluster_labels=["cluster-1"], + fit_metadata={"random_state": 7}, + ), + assignments=pd.DataFrame({"cluster": ["cluster-1"]}), + cluster_distribution=pd.DataFrame({"cluster": ["cluster-1"]}), + condition_distribution=pd.DataFrame({"condition": ["treated"]}), + distribution_change=None, + cluster_profiles=pd.DataFrame({"profile": [1.0]}), + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={"notes": "ok"}, + ) + selected_regions = pd.DataFrame([{"chromosome": "chr1", "start": 0, "end": 1000}]) + + result = RegionDiscoveryClusterResult( + discovery=discovery, + clustering=clustering, + selected_regions=selected_regions, + metadata={"selection_mode": "top_n"}, + figures={"workflow": object()}, + ) + + assert result.discovery is discovery + assert result.clustering is clustering + assert result.selected_regions.equals(selected_regions) + assert result.metadata == {"selection_mode": "top_n"} + + +@pytest.mark.parametrize( + "field_name", + ["discovery", "clustering", "selected_regions", "metadata", "figures"], +) +def test_region_discovery_cluster_result_rejects_missing_required_fields( + field_name, +): + kwargs = { + "discovery": RegionDiscoveryResult( + hits=pd.DataFrame({"region": ["chr1:0-1000"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + contrast=None, + plot_data={"ranked_hits": pd.DataFrame({"region": ["chr1:0-1000"]})}, + metadata={"analysis_unit": "genome"}, + figures={"hit_track": object()}, + ), + "clustering": SharedClusterResult( + model=SharedClusterModel( + mode="shared", + motifs=["A,0"], + feature_names=["A,0_mod_fraction"], + preprocessing={"scale": "standard"}, + estimator=object(), + cluster_labels=["cluster-1"], + fit_metadata={"random_state": 7}, + ), + assignments=pd.DataFrame({"cluster": ["cluster-1"]}), + cluster_distribution=pd.DataFrame({"cluster": ["cluster-1"]}), + condition_distribution=pd.DataFrame({"condition": ["treated"]}), + distribution_change=None, + cluster_profiles=pd.DataFrame({"profile": [1.0]}), + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={"notes": "ok"}, + ), + "selected_regions": pd.DataFrame( + [{"chromosome": "chr1", "start": 0, "end": 1000}] + ), + "metadata": {"selection_mode": "top_n"}, + "figures": {}, + } + kwargs[field_name] = None + + with pytest.raises(ValueError, match=field_name): + RegionDiscoveryClusterResult(**kwargs) + + +@pytest.mark.parametrize( + ("field_name", "value", "expected_type"), + [ + ("discovery", object(), "RegionDiscoveryResult"), + ("clustering", object(), "SharedClusterResult"), + ], +) +def test_region_discovery_cluster_result_rejects_invalid_wrapper_types( + field_name, + value, + expected_type, +): + kwargs = { + "discovery": RegionDiscoveryResult( + hits=pd.DataFrame({"region": ["chr1:0-1000"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + contrast=None, + plot_data={"ranked_hits": pd.DataFrame({"region": ["chr1:0-1000"]})}, + metadata={"analysis_unit": "genome"}, + figures={"hit_track": object()}, + ), + "clustering": SharedClusterResult( + model=SharedClusterModel( + mode="shared", + motifs=["A,0"], + feature_names=["A,0_mod_fraction"], + preprocessing={"scale": "standard"}, + estimator=object(), + cluster_labels=["cluster-1"], + fit_metadata={"random_state": 7}, + ), + assignments=pd.DataFrame({"cluster": ["cluster-1"]}), + cluster_distribution=pd.DataFrame({"cluster": ["cluster-1"]}), + condition_distribution=pd.DataFrame({"condition": ["treated"]}), + distribution_change=None, + cluster_profiles=pd.DataFrame({"profile": [1.0]}), + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={"notes": "ok"}, + ), + "selected_regions": pd.DataFrame( + [{"chromosome": "chr1", "start": 0, "end": 1000}] + ), + "metadata": {"selection_mode": "top_n"}, + "figures": {}, + } + kwargs[field_name] = value + + with pytest.raises(TypeError, match=expected_type): + RegionDiscoveryClusterResult(**kwargs) + + +def test_region_discovery_cluster_contrast_result_supports_wrapped_outputs(): + discovery = RegionDiscoveryResult( + hits=pd.DataFrame({"region": ["chr1:0-1000"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + contrast=None, + plot_data={"ranked_hits": pd.DataFrame({"region": ["chr1:0-1000"]})}, + metadata={"analysis_unit": "genome"}, + figures={"hit_track": object()}, + ) + clustering = SharedClusterResult( + model=SharedClusterModel( + mode="shared", + motifs=["A,0"], + feature_names=["A,0_mod_fraction"], + preprocessing={"scale": "standard"}, + estimator=object(), + cluster_labels=["cluster-1"], + fit_metadata={"random_state": 7}, + ), + assignments=pd.DataFrame({"cluster": ["cluster-1"]}), + cluster_distribution=pd.DataFrame({"cluster": ["cluster-1"]}), + condition_distribution=pd.DataFrame({"condition": ["treated"]}), + distribution_change=None, + cluster_profiles=pd.DataFrame({"profile": [1.0]}), + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={"notes": "ok"}, + ) + contrasts = RegionContrastResult( + regions=pd.DataFrame({"region": ["chr1:0-1000"]}), + summary=pd.DataFrame({"metric": [1.0]}), + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + plot_data={"volcano": pd.DataFrame({"x": [1.0]})}, + metadata={"notes": "ok"}, + figures={}, + ) + selected_regions = pd.DataFrame([{"chromosome": "chr1", "start": 0, "end": 1000}]) + + result = RegionDiscoveryClusterContrastResult( + discovery=discovery, + clustering=clustering, + contrasts=contrasts, + selected_regions=selected_regions, + metadata={"contrast_scope": "selected"}, + figures={"workflow": object()}, + ) + + assert result.discovery is discovery + assert result.clustering is clustering + assert result.contrasts is contrasts + assert result.selected_regions.equals(selected_regions) + assert result.metadata == {"contrast_scope": "selected"} + + +def test_region_discovery_cluster_contrast_result_accepts_selected_regions_chrom_alias(): + discovery = RegionDiscoveryResult( + hits=pd.DataFrame({"region": ["chr1:0-1000"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + contrast=None, + plot_data={"ranked_hits": pd.DataFrame({"region": ["chr1:0-1000"]})}, + metadata={"analysis_unit": "genome"}, + figures={"hit_track": object()}, + ) + clustering = SharedClusterResult( + model=SharedClusterModel( + mode="shared", + motifs=["A,0"], + feature_names=["A,0_mod_fraction"], + preprocessing={"scale": "standard"}, + estimator=object(), + cluster_labels=["cluster-1"], + fit_metadata={"random_state": 7}, + ), + assignments=pd.DataFrame({"cluster": ["cluster-1"]}), + cluster_distribution=pd.DataFrame({"cluster": ["cluster-1"]}), + condition_distribution=pd.DataFrame({"condition": ["treated"]}), + distribution_change=None, + cluster_profiles=pd.DataFrame({"profile": [1.0]}), + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={"notes": "ok"}, + ) + contrasts = RegionContrastResult( + regions=pd.DataFrame({"region": ["chr1:0-1000"]}), + summary=pd.DataFrame({"metric": [1.0]}), + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + plot_data={"volcano": pd.DataFrame({"x": [1.0]})}, + metadata={"notes": "ok"}, + figures={}, + ) + selected_regions = pd.DataFrame([{"chrom": "chr1", "start": 0, "end": 1000}]) + + result = RegionDiscoveryClusterContrastResult( + discovery=discovery, + clustering=clustering, + contrasts=contrasts, + selected_regions=selected_regions, + metadata={"contrast_scope": "selected"}, + figures={"workflow": object()}, + ) + + assert result.selected_regions.equals(selected_regions) + + +@pytest.mark.parametrize( + "field_name", + ["discovery", "clustering", "contrasts", "selected_regions", "metadata", "figures"], +) +def test_region_discovery_cluster_contrast_result_rejects_missing_required_fields( + field_name, +): + kwargs = { + "discovery": RegionDiscoveryResult( + hits=pd.DataFrame({"region": ["chr1:0-1000"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + contrast=None, + plot_data={"ranked_hits": pd.DataFrame({"region": ["chr1:0-1000"]})}, + metadata={"analysis_unit": "genome"}, + figures={"hit_track": object()}, + ), + "clustering": SharedClusterResult( + model=SharedClusterModel( + mode="shared", + motifs=["A,0"], + feature_names=["A,0_mod_fraction"], + preprocessing={"scale": "standard"}, + estimator=object(), + cluster_labels=["cluster-1"], + fit_metadata={"random_state": 7}, + ), + assignments=pd.DataFrame({"cluster": ["cluster-1"]}), + cluster_distribution=pd.DataFrame({"cluster": ["cluster-1"]}), + condition_distribution=pd.DataFrame({"condition": ["treated"]}), + distribution_change=None, + cluster_profiles=pd.DataFrame({"profile": [1.0]}), + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={"notes": "ok"}, + ), + "contrasts": RegionContrastResult( + regions=pd.DataFrame({"region": ["chr1:0-1000"]}), + summary=pd.DataFrame({"metric": [1.0]}), + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + plot_data={"volcano": pd.DataFrame({"x": [1.0]})}, + metadata={"notes": "ok"}, + figures={}, + ), + "selected_regions": pd.DataFrame( + [{"chromosome": "chr1", "start": 0, "end": 1000}] + ), + "metadata": {"contrast_scope": "selected"}, + "figures": {}, + } + kwargs[field_name] = None + + with pytest.raises(ValueError, match=field_name): + RegionDiscoveryClusterContrastResult(**kwargs) + + +@pytest.mark.parametrize( + ("field_name", "value", "expected_type"), + [ + ("discovery", object(), "RegionDiscoveryResult"), + ("clustering", object(), "SharedClusterResult"), + ("contrasts", object(), "RegionContrastResult"), + ], +) +def test_region_discovery_cluster_contrast_result_rejects_invalid_wrapper_types( + field_name, + value, + expected_type, +): + kwargs = { + "discovery": RegionDiscoveryResult( + hits=pd.DataFrame({"region": ["chr1:0-1000"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + contrast=None, + plot_data={"ranked_hits": pd.DataFrame({"region": ["chr1:0-1000"]})}, + metadata={"analysis_unit": "genome"}, + figures={"hit_track": object()}, + ), + "clustering": SharedClusterResult( + model=SharedClusterModel( + mode="shared", + motifs=["A,0"], + feature_names=["A,0_mod_fraction"], + preprocessing={"scale": "standard"}, + estimator=object(), + cluster_labels=["cluster-1"], + fit_metadata={"random_state": 7}, + ), + assignments=pd.DataFrame({"cluster": ["cluster-1"]}), + cluster_distribution=pd.DataFrame({"cluster": ["cluster-1"]}), + condition_distribution=pd.DataFrame({"condition": ["treated"]}), + distribution_change=None, + cluster_profiles=pd.DataFrame({"profile": [1.0]}), + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={"notes": "ok"}, + ), + "contrasts": RegionContrastResult( + regions=pd.DataFrame({"region": ["chr1:0-1000"]}), + summary=pd.DataFrame({"metric": [1.0]}), + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + plot_data={"volcano": pd.DataFrame({"x": [1.0]})}, + metadata={"notes": "ok"}, + figures={}, + ), + "selected_regions": pd.DataFrame( + [{"chromosome": "chr1", "start": 0, "end": 1000}] + ), + "metadata": {"contrast_scope": "selected"}, + "figures": {}, + } + kwargs[field_name] = value + + with pytest.raises(TypeError, match=expected_type): + RegionDiscoveryClusterContrastResult(**kwargs) + + +def test_region_discovery_cluster_contrast_result_rejects_non_dataframe_selected_regions(): + with pytest.raises(TypeError, match="selected_regions must be a pandas DataFrame"): + RegionDiscoveryClusterContrastResult( + discovery=RegionDiscoveryResult( + hits=pd.DataFrame({"region": ["chr1:0-1000"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + contrast=None, + plot_data={"ranked_hits": pd.DataFrame({"region": ["chr1:0-1000"]})}, + metadata={"analysis_unit": "genome"}, + figures={"hit_track": object()}, + ), + clustering=SharedClusterResult( + model=SharedClusterModel( + mode="shared", + motifs=["A,0"], + feature_names=["A,0_mod_fraction"], + preprocessing={"scale": "standard"}, + estimator=object(), + cluster_labels=["cluster-1"], + fit_metadata={"random_state": 7}, + ), + assignments=pd.DataFrame({"cluster": ["cluster-1"]}), + cluster_distribution=pd.DataFrame({"cluster": ["cluster-1"]}), + condition_distribution=pd.DataFrame({"condition": ["treated"]}), + distribution_change=None, + cluster_profiles=pd.DataFrame({"profile": [1.0]}), + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={"notes": "ok"}, + ), + contrasts=RegionContrastResult( + regions=pd.DataFrame({"region": ["chr1:0-1000"]}), + summary=pd.DataFrame({"metric": [1.0]}), + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + plot_data={"volcano": pd.DataFrame({"x": [1.0]})}, + metadata={"notes": "ok"}, + figures={}, + ), + selected_regions=[{"chrom": "chr1", "start": 0, "end": 1000}], + metadata={"contrast_scope": "selected"}, + ) + + +@pytest.mark.parametrize( + "selected_regions", + [ + pd.DataFrame([{"start": 0, "end": 1000}]), + pd.DataFrame([{"chrom": "chr1", "end": 1000}]), + pd.DataFrame([{"chromosome": "chr1", "start": 0}]), + ], +) +def test_region_discovery_cluster_contrast_result_rejects_invalid_selected_regions_schema( + selected_regions, +): + with pytest.raises( + ValueError, + match="selected_regions must include 'start', 'end', and either 'chrom' or 'chromosome'", + ): + RegionDiscoveryClusterContrastResult( + discovery=RegionDiscoveryResult( + hits=pd.DataFrame({"region": ["chr1:0-1000"]}), + windows=pd.DataFrame({"window_id": ["chr1:0-1000"]}), + contrast=None, + plot_data={"ranked_hits": pd.DataFrame({"region": ["chr1:0-1000"]})}, + metadata={"analysis_unit": "genome"}, + figures={"hit_track": object()}, + ), + clustering=SharedClusterResult( + model=SharedClusterModel( + mode="shared", + motifs=["A,0"], + feature_names=["A,0_mod_fraction"], + preprocessing={"scale": "standard"}, + estimator=object(), + cluster_labels=["cluster-1"], + fit_metadata={"random_state": 7}, + ), + assignments=pd.DataFrame({"cluster": ["cluster-1"]}), + cluster_distribution=pd.DataFrame({"cluster": ["cluster-1"]}), + condition_distribution=pd.DataFrame({"condition": ["treated"]}), + distribution_change=None, + cluster_profiles=pd.DataFrame({"profile": [1.0]}), + region_summaries=None, + plot_data={"cluster_distribution_bar": {"kind": "bar"}}, + figures={}, + metadata={"notes": "ok"}, + ), + contrasts=RegionContrastResult( + regions=pd.DataFrame({"region": ["chr1:0-1000"]}), + summary=pd.DataFrame({"metric": [1.0]}), + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + plot_data={"volcano": pd.DataFrame({"x": [1.0]})}, + metadata={"notes": "ok"}, + figures={}, + ), + selected_regions=selected_regions, + metadata={"contrast_scope": "selected"}, + ) + + +def test_cohort_spec_stores_workflow_and_params(): + cohort = CohortSpec( + cohort_id="cohort-1", + sample_ids=["sample-1", "sample-2"], + workflow="shared_cluster_distribution", + params={"window_size": 200}, + metadata={"owner": "team-a"}, + ) + + assert cohort.workflow == "shared_cluster_distribution" + assert cohort.params == {"window_size": 200} + assert cohort.sample_ids == ["sample-1", "sample-2"] + assert cohort.metadata == {"owner": "team-a"} + + +def test_batch_job_groups_cohorts(): + cohort = CohortSpec( + cohort_id="cohort-1", + sample_ids=["sample-1"], + workflow="shared_cluster_distribution", + params={}, + ) + job = BatchJob( + job_id="job-1", + workflow="shared_cluster_distribution", + cohorts=[cohort], + ) + + assert job.job_id == "job-1" + assert job.workflow == "shared_cluster_distribution" + assert job.cohorts == [cohort] + assert job.artifact_policy == "prefer_cached" diff --git a/tests/test_parse_bam.py b/tests/test_parse_bam.py new file mode 100644 index 0000000..c0a2bd0 --- /dev/null +++ b/tests/test_parse_bam.py @@ -0,0 +1,642 @@ +import gzip +from pathlib import Path + +import pytest + +from dimelo import parse_bam, run_modkit + + +class _FakeRead: + def __init__(self, tags=None): + self._tags = tags or [] + + def to_dict(self): + return {"tags": self._tags} + + +class _FakeAlignmentFile: + def __init__(self, read_count): + self.read_count = read_count + self.reads_yielded = 0 + + def fetch(self): + for _ in range(self.read_count): + self.reads_yielded += 1 + yield _FakeRead() + + +class _TagSequenceAlignmentFile: + def __init__(self, reads): + self._reads = reads + + def fetch(self): + yield from self._reads + + +def test_check_bam_format_consumes_at_most_the_first_100_reads(monkeypatch): + fake_alignment_file = _FakeAlignmentFile(parse_bam.NUM_READS_TO_CHECK + 1) + + monkeypatch.setattr( + parse_bam.pysam, + "AlignmentFile", + lambda *args, **kwargs: fake_alignment_file, + ) + + parse_bam.check_bam_format("dummy.bam", motifs=["A,0", "CG,0"]) + + assert fake_alignment_file.reads_yielded == parse_bam.NUM_READS_TO_CHECK + + +def test_check_bam_format_raises_on_malformed_tags_after_motifs_are_found(monkeypatch): + fake_alignment_file = _TagSequenceAlignmentFile( + [ + _FakeRead(["MM:Z:A+a?;C+m?"]), + _FakeRead(["Mm:Z:A+a?"]), + ] + ) + + monkeypatch.setattr( + parse_bam.pysam, + "AlignmentFile", + lambda *args, **kwargs: fake_alignment_file, + ) + + with pytest.raises(ValueError, match="Mm and Ml instead of MM and ML"): + parse_bam.check_bam_format("dummy.bam", motifs=["A,0", "CG,0"]) + + +def test_check_bam_format_warns_only_when_requested_motifs_missing(monkeypatch, capsys): + found_reads = [_FakeRead(["MM:Z:A+a?;C+m?"])] * parse_bam.NUM_READS_TO_CHECK + monkeypatch.setattr( + parse_bam.pysam, + "AlignmentFile", + lambda *args, **kwargs: _TagSequenceAlignmentFile(found_reads), + ) + + parse_bam.check_bam_format("dummy.bam", motifs=["A,0,a", "CG,0,m"]) + assert "no modified appropriately-coded" not in capsys.readouterr().out + + missing_reads = [_FakeRead(["MM:Z:A+a?"])] * parse_bam.NUM_READS_TO_CHECK + monkeypatch.setattr( + parse_bam.pysam, + "AlignmentFile", + lambda *args, **kwargs: _TagSequenceAlignmentFile(missing_reads), + ) + + parse_bam.check_bam_format("dummy.bam", motifs=["A,0,a", "CG,0,m"]) + out = capsys.readouterr().out + assert "no modified appropriately-coded values found for ['C']" in out + assert "parse_bam is looking for ['A,0,a', 'CG,0,m']" in out + + +def test_threads_command_list_defaults_to_all_available_cores(monkeypatch): + monkeypatch.setattr(parse_bam.multiprocessing, "cpu_count", lambda: 12) + + command = parse_bam._threads_command_list(cores=None, quiet=True) + + assert command == ["--threads", "12"] + + +def test_threads_command_list_caps_requested_cores_to_available(monkeypatch): + monkeypatch.setattr(parse_bam.multiprocessing, "cpu_count", lambda: 8) + + command = parse_bam._threads_command_list(cores=32, quiet=True) + + assert command == ["--threads", "8"] + + +def test_threads_command_list_uses_requested_cores_when_available(monkeypatch): + monkeypatch.setattr(parse_bam.multiprocessing, "cpu_count", lambda: 16) + + command = parse_bam._threads_command_list(cores=6, quiet=True) + + assert command == ["--threads", "6"] + + +def test_parse_should_render_live_progress_respects_env_off(monkeypatch): + monkeypatch.setenv("DIMELO_PROGRESS_MODE", "off") + monkeypatch.setattr(parse_bam.sys.stderr, "isatty", lambda: True) + + assert parse_bam._should_render_live_progress() is False + + +def test_parse_should_render_live_progress_auto_disables_notebook(monkeypatch): + monkeypatch.delenv("DIMELO_PROGRESS_MODE", raising=False) + monkeypatch.setenv("JPY_PARENT_PID", "12345") + monkeypatch.setattr(parse_bam.sys.stderr, "isatty", lambda: True) + + assert parse_bam._should_render_live_progress() is False + + +def test_create_region_command_list_returns_empty_without_regions(tmp_path): + command, bed_path = parse_bam.create_region_command_list( + output_path=tmp_path, + regions=None, + window_size=None, + ) + + assert command == [] + assert bed_path is None + + +def test_create_region_command_list_writes_processed_bed(tmp_path): + command, bed_path = parse_bam.create_region_command_list( + output_path=tmp_path, + regions="chr1:10-20", + window_size=None, + ) + + assert bed_path == Path(tmp_path) / "regions.processed.bed" + assert command == ["--include-bed", str(bed_path)] + assert bed_path.exists() + + +def test_build_pileup_targeting_command_list_routes_to_modified_bases_on_modkit_0_6(): + capabilities = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.6.1", + version="0.6.1", + version_tuple=(0, 6, 1), + supports_mod_threshold=True, + supports_mod_thresholds=False, + supports_modified_bases=True, + supports_force_allow_implicit=False, + supports_extract_subcommands=True, + extract_supports_reference_long=True, + extract_supports_reference_short=True, + ) + + command = parse_bam._build_pileup_targeting_command_list( + motifs=["A,0,Y", "CG,0,Z"], + capabilities=capabilities, + ) + + assert "--modified-bases" in command + assert "A:Y" in command + assert "C:Z" in command + assert "--cpg" in command + assert "--motif" not in command + + +def test_build_pileup_targeting_command_list_uses_version_fallback_for_modkit_0_6(): + capabilities = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.6.1", + version="0.6.1", + version_tuple=(0, 6, 1), + supports_mod_threshold=True, + supports_mod_thresholds=False, + # Simulate stale/incorrect capability detection. + supports_modified_bases=False, + supports_force_allow_implicit=False, + supports_extract_subcommands=True, + extract_supports_reference_long=True, + extract_supports_reference_short=True, + ) + + command = parse_bam._build_pileup_targeting_command_list( + motifs=["A,0", "CG,0"], + capabilities=capabilities, + ) + + assert "--modified-bases" in command + assert "A:Y" in command + assert "A:a" in command + assert "C:Z" in command + assert "C:m" in command + assert "--motif" not in command + + +def test_build_mod_threshold_command_list_routes_flag_by_modkit_capabilities(): + legacy = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.2.4", + version="0.2.4", + version_tuple=(0, 2, 4), + supports_mod_threshold=False, + supports_mod_thresholds=True, + supports_modified_bases=False, + supports_force_allow_implicit=True, + supports_extract_subcommands=False, + extract_supports_reference_long=False, + extract_supports_reference_short=True, + ) + modern = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.6.1", + version="0.6.1", + version_tuple=(0, 6, 1), + supports_mod_threshold=True, + supports_mod_thresholds=False, + supports_modified_bases=True, + supports_force_allow_implicit=False, + supports_extract_subcommands=True, + extract_supports_reference_long=True, + extract_supports_reference_short=True, + ) + + legacy_flags = parse_bam._build_mod_threshold_command_list( + motifs=["A,0,Y"], + motif_thresholds={"A,0,Y": 0.75}, + capabilities=legacy, + ) + modern_flags = parse_bam._build_mod_threshold_command_list( + motifs=["A,0,Y"], + motif_thresholds={"A,0,Y": 0.75}, + capabilities=modern, + ) + + assert legacy_flags == ["--mod-thresholds", "Y:0.75"] + assert modern_flags == ["--mod-threshold", "Y:0.75"] + + +def test_build_mod_threshold_command_list_rejects_conflicting_thresholds_for_shared_mod_code(): + capabilities = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.6.1", + version="0.6.1", + version_tuple=(0, 6, 1), + supports_mod_threshold=True, + supports_mod_thresholds=False, + supports_modified_bases=True, + supports_force_allow_implicit=False, + supports_extract_subcommands=True, + extract_supports_reference_long=True, + extract_supports_reference_short=True, + ) + + with pytest.raises( + ValueError, + match="Cannot apply different thresholds to motifs that share mod code", + ): + parse_bam._build_mod_threshold_command_list( + motifs=["A,0,a", "GATC,1,a"], + motif_thresholds={"A,0,a": 0.7, "GATC,1,a": 0.8}, + capabilities=capabilities, + ) + + +def test_resolve_motif_thresholds_accepts_scalar_and_per_motif_dict(): + motifs = ["A,0", "CG,0", "GCH,1"] + + scalar = parse_bam._resolve_motif_thresholds( + motifs=motifs, + thresh=0.7, + quiet=True, + ) + assert scalar == {"A,0": 0.7, "CG,0": 0.7, "GCH,1": 0.7} + + by_motif = parse_bam._resolve_motif_thresholds( + motifs=motifs, + thresh={"A,0": 0.6, "CG,0": 0.8, "default": 0.9}, + quiet=True, + ) + assert by_motif == {"A,0": 0.6, "CG,0": 0.8, "GCH,1": 0.9} + + +def test_resolve_motif_thresholds_supports_canonical_key_alias(): + thresholds = parse_bam._resolve_motif_thresholds( + motifs=["A,0,a", "CG,0,m"], + thresh={"A,0": 0.65, "CG,0": 0.75}, + quiet=True, + ) + assert thresholds == {"A,0,a": 0.65, "CG,0,m": 0.75} + + +def test_build_extract_command_prefix_uses_full_subcommand_for_modkit_0_6(): + modern = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.6.1", + version="0.6.1", + version_tuple=(0, 6, 1), + supports_mod_threshold=True, + supports_mod_thresholds=False, + supports_modified_bases=True, + supports_force_allow_implicit=False, + supports_extract_subcommands=True, + extract_supports_reference_long=True, + extract_supports_reference_short=True, + ) + legacy = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.2.4", + version="0.2.4", + version_tuple=(0, 2, 4), + supports_mod_threshold=False, + supports_mod_thresholds=True, + supports_modified_bases=False, + supports_force_allow_implicit=True, + supports_extract_subcommands=False, + extract_supports_reference_long=False, + extract_supports_reference_short=True, + ) + + modern_prefix = parse_bam._build_extract_command_prefix( + input_file=Path("input.bam"), + output_txt=Path("out.txt"), + capabilities=modern, + ) + legacy_prefix = parse_bam._build_extract_command_prefix( + input_file=Path("input.bam"), + output_txt=Path("out.txt"), + capabilities=legacy, + ) + + assert modern_prefix == [ + "modkit", + "extract", + "full", + Path("input.bam"), + Path("out.txt"), + ] + assert legacy_prefix == ["modkit", "extract", Path("input.bam"), Path("out.txt")] + + +def test_build_extract_command_prefix_uses_version_fallback_for_modkit_0_6(): + stale = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.6.1", + version="0.6.1", + version_tuple=(0, 6, 1), + supports_mod_threshold=True, + supports_mod_thresholds=False, + supports_modified_bases=True, + supports_force_allow_implicit=False, + # Simulate stale capability detection. + supports_extract_subcommands=False, + extract_supports_reference_long=False, + extract_supports_reference_short=False, + ) + + prefix = parse_bam._build_extract_command_prefix( + input_file=Path("input.bam"), + output_txt=Path("out.txt"), + capabilities=stale, + ) + ref_flags = parse_bam._build_extract_reference_command_list( + ref_genome=Path("ref.fa"), + capabilities=stale, + ) + + assert prefix == ["modkit", "extract", "full", Path("input.bam"), Path("out.txt")] + assert ref_flags == ["--reference", Path("ref.fa")] + + +def test_prep_output_directory_respects_overwrite_flag(tmp_path): + _, (output_file,) = parse_bam.prep_output_directory( + output_directory=tmp_path, + output_name="demo", + input_file=tmp_path, + output_file_names=["result.txt"], + overwrite=True, + ) + output_file.write_text("keep-me") + + parse_bam.prep_output_directory( + output_directory=tmp_path, + output_name="demo", + input_file=tmp_path, + output_file_names=["result.txt"], + overwrite=False, + ) + assert output_file.exists() + + parse_bam.prep_output_directory( + output_directory=tmp_path, + output_name="demo", + input_file=tmp_path, + output_file_names=["result.txt"], + overwrite=True, + ) + assert not output_file.exists() + + +def test_extract_overwrite_false_reuses_existing_outputs(tmp_path, monkeypatch): + output_path = tmp_path / "existing_extract" + output_path.mkdir(parents=True) + output_reads_path = output_path / "reads.combined_basemods.h5" + output_reads_path.write_bytes(b"") + processed_regions_path = output_path / "regions.processed.bed" + processed_regions_path.write_text("chr1\t0\t1\n") + + def _unexpected_modkit(*args, **kwargs): + raise AssertionError("modkit should not be called when outputs are reused") + + monkeypatch.setattr(run_modkit, "_ensure_modkit_available", _unexpected_modkit) + + output_reads, processed_regions = parse_bam.extract( + input_file=tmp_path / "missing.bam", + output_name="existing_extract", + ref_genome=tmp_path / "missing.fa", + output_directory=tmp_path, + regions="chr1:1-2", + overwrite=False, + quiet=True, + ) + + assert output_reads == output_reads_path + assert processed_regions == processed_regions_path + + +def test_extract_overwrite_false_raises_on_partial_existing_outputs( + tmp_path, monkeypatch +): + output_path = tmp_path / "partial_extract" + output_path.mkdir(parents=True) + output_reads_path = output_path / "reads.combined_basemods.h5" + output_reads_path.write_bytes(b"") + + def _unexpected_modkit(*args, **kwargs): + raise AssertionError("modkit should not be called before conflict check") + + monkeypatch.setattr(run_modkit, "_ensure_modkit_available", _unexpected_modkit) + + with pytest.raises(FileExistsError, match="overwrite=False"): + parse_bam.extract( + input_file=tmp_path / "missing.bam", + output_name="partial_extract", + ref_genome=tmp_path / "missing.fa", + output_directory=tmp_path, + regions="chr1:1-2", + overwrite=False, + quiet=True, + ) + + +def test_pileup_overwrite_false_reuses_existing_outputs(tmp_path, monkeypatch): + output_path = tmp_path / "existing_pileup" + output_path.mkdir(parents=True) + output_pileup_path = output_path / "pileup.sorted.bed.gz" + output_pileup_path.write_bytes(b"") + output_pileup_tbi_path = output_path / "pileup.sorted.bed.gz.tbi" + output_pileup_tbi_path.write_bytes(b"") + processed_regions_path = output_path / "regions.processed.bed" + processed_regions_path.write_text("chr1\t0\t1\n") + + def _unexpected_modkit(*args, **kwargs): + raise AssertionError("modkit should not be called when outputs are reused") + + monkeypatch.setattr(run_modkit, "_ensure_modkit_available", _unexpected_modkit) + + output_pileup, processed_regions = parse_bam.pileup( + input_file=tmp_path / "missing.bam", + output_name="existing_pileup", + ref_genome=tmp_path / "missing.fa", + output_directory=tmp_path, + regions="chr1:1-2", + overwrite=False, + quiet=True, + ) + + assert output_pileup == output_pileup_path + assert processed_regions == processed_regions_path + + +def test_pileup_preserves_explicit_threshold_empty_output_without_retry( + tmp_path, monkeypatch +): + calls = [] + + capabilities = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.6.1", + version="0.6.1", + version_tuple=(0, 6, 1), + supports_mod_threshold=True, + supports_mod_thresholds=False, + supports_modified_bases=True, + supports_force_allow_implicit=False, + supports_extract_subcommands=True, + extract_supports_reference_long=True, + extract_supports_reference_short=True, + ) + + monkeypatch.setattr( + run_modkit, "_ensure_modkit_available", lambda **_: capabilities + ) + monkeypatch.setattr(parse_bam, "verify_inputs", lambda *args, **kwargs: None) + + def _fake_run_with_progress_bars(*, command_list, **kwargs): + calls.append(command_list) + output_bed = Path(command_list[3]) + output_bed.write_text("") + + monkeypatch.setattr( + run_modkit, "run_with_progress_bars", _fake_run_with_progress_bars + ) + + output_pileup, _ = parse_bam.pileup( + input_file=tmp_path / "missing.bam", + output_name="test_out", + ref_genome=tmp_path / "missing.fa", + output_directory=tmp_path, + motifs=["A,0"], + thresh=190, + regions=None, + quiet=True, + overwrite=True, + ) + + assert len(calls) == 1 + assert "--mod-threshold" in calls[0] + + with gzip.open(output_pileup, "rt") as handle: + rows = [line for line in handle if line.strip()] + assert rows == [] + + +def test_group_motifs_for_pileup_splits_multi_context_on_modkit_0_6(): + capabilities = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.6.1", + version="0.6.1", + version_tuple=(0, 6, 1), + supports_mod_threshold=True, + supports_mod_thresholds=False, + supports_modified_bases=True, + supports_force_allow_implicit=False, + supports_extract_subcommands=True, + extract_supports_reference_long=True, + extract_supports_reference_short=True, + ) + groups = parse_bam._group_motifs_for_pileup(["A,0", "CG,0"], capabilities) + assert groups == [("A,0", ["A,0"]), ("CG,0", ["CG,0"])] + + +def test_group_motifs_for_pileup_keeps_combined_on_legacy_modkit(): + capabilities = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.2.4", + version="0.2.4", + version_tuple=(0, 2, 4), + supports_mod_threshold=False, + supports_mod_thresholds=True, + supports_modified_bases=False, + supports_force_allow_implicit=True, + supports_extract_subcommands=False, + extract_supports_reference_long=False, + extract_supports_reference_short=True, + ) + groups = parse_bam._group_motifs_for_pileup(["A,0", "CG,0"], capabilities) + assert groups == [("combined", ["A,0", "CG,0"])] + + +def test_pileup_multi_motif_split_merges_outputs_on_modkit_0_6(tmp_path, monkeypatch): + calls = [] + capabilities = run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.6.1", + version="0.6.1", + version_tuple=(0, 6, 1), + supports_mod_threshold=True, + supports_mod_thresholds=False, + supports_modified_bases=True, + supports_force_allow_implicit=False, + supports_extract_subcommands=True, + extract_supports_reference_long=True, + extract_supports_reference_short=True, + ) + + monkeypatch.setattr( + run_modkit, "_ensure_modkit_available", lambda **_: capabilities + ) + monkeypatch.setattr(parse_bam, "verify_inputs", lambda *args, **kwargs: None) + + def _fake_run_with_progress_bars(*, command_list, **kwargs): + calls.append(command_list) + output_bed = Path(command_list[3]) + modified_bases = [] + for i, token in enumerate(command_list): + if token == "--modified-bases": + modified_bases.append(command_list[i + 1]) + if any(base.startswith("A:") for base in modified_bases): + output_bed.write_text( + "chr1\t10\t11\ta\t1\t+\t10\t11\t255,0,0\t1\t0.0\t0\t1\t0\t0\t0\t0\t0\n" + ) + elif any(base.startswith("C:") for base in modified_bases): + output_bed.write_text( + "chr1\t12\t13\tm\t1\t+\t12\t13\t255,0,0\t1\t0.0\t0\t1\t0\t0\t0\t0\t0\n" + ) + else: + output_bed.write_text("") + + monkeypatch.setattr( + run_modkit, "run_with_progress_bars", _fake_run_with_progress_bars + ) + + output_pileup, _ = parse_bam.pileup( + input_file=tmp_path / "missing.bam", + output_name="test_out_multi", + ref_genome=tmp_path / "missing.fa", + output_directory=tmp_path, + motifs=["A,0", "CG,0"], + thresh=None, + regions=None, + quiet=True, + overwrite=True, + ) + + assert len(calls) == 2 + with gzip.open(output_pileup, "rt") as handle: + rows = [line for line in handle if line.strip()] + assert len(rows) == 2 diff --git a/tests/test_parse_bam_vectors.py b/tests/test_parse_bam_vectors.py new file mode 100644 index 0000000..d7b2028 --- /dev/null +++ b/tests/test_parse_bam_vectors.py @@ -0,0 +1,904 @@ +import gzip +import json +from pathlib import Path + +import h5py +import numpy as np +import pytest + +from dimelo import load_processed, parse_bam + + +def _make_extract_line( + read_name: str, + pos_in_read: int, + pos_in_genome: int, + chromosome: str, + strand: str, + read_len: int, + prob: float, + mod_code: str, + canonical_base: str, +) -> str: + fields = [ + read_name, + str(pos_in_read), + str(pos_in_genome), + chromosome, + ".", + strand, + ".", + ".", + ".", + str(read_len), + str(prob), + mod_code, + ".", + ".", + ".", + canonical_base, + ] + return "\t".join(fields) + + +def _write_extract_file(path: Path) -> None: + header = "\t".join(f"col{i}" for i in range(16)) + lines = [ + header, + _make_extract_line( + "read_sparse", + pos_in_read=0, + pos_in_genome=100, + chromosome="chr1", + strand="+", + read_len=10, + prob=0.10, + mod_code="a", + canonical_base="A", + ), + _make_extract_line( + "read_sparse", + pos_in_read=8, + pos_in_genome=108, + chromosome="chr1", + strand="+", + read_len=10, + prob=0.90, + mod_code="a", + canonical_base="A", + ), + _make_extract_line( + "read_no_hits", + pos_in_read=0, + pos_in_genome=200, + chromosome="chr1", + strand="+", + read_len=10, + prob=0.20, + mod_code="m", + canonical_base="C", + ), + _make_extract_line( + "read_no_hits", + pos_in_read=9, + pos_in_genome=210, + chromosome="chr1", + strand="+", + read_len=10, + prob=0.30, + mod_code="m", + canonical_base="C", + ), + ] + path.write_text("\n".join(lines) + "\n") + + +def _write_coordinate_stress_extract_file(path: Path) -> None: + header = "\t".join(f"col{i}" for i in range(16)) + lines = [ + header, + # plus strand read with inferred start drift across rows (tests coordinate robustness) + _make_extract_line( + "read_plus_drift", + pos_in_read=1, + pos_in_genome=102, + chromosome="chr2", + strand="+", + read_len=10, + prob=0.90, + mod_code="a", + canonical_base="A", + ), + _make_extract_line( + "read_plus_drift", + pos_in_read=0, + pos_in_genome=100, + chromosome="chr2", + strand="+", + read_len=10, + prob=0.80, + mod_code="a", + canonical_base="A", + ), + # minus strand read where reference-oriented positions should still map left-to-right + _make_extract_line( + "read_minus", + pos_in_read=1, + pos_in_genome=208, + chromosome="chr2", + strand="-", + read_len=10, + prob=0.70, + mod_code="a", + canonical_base="A", + ), + _make_extract_line( + "read_minus", + pos_in_read=9, + pos_in_genome=200, + chromosome="chr2", + strand="-", + read_len=10, + prob=0.95, + mod_code="a", + canonical_base="A", + ), + ] + path.write_text("\n".join(lines) + "\n") + + +def _decode_vector(dataset, index: int) -> np.ndarray: + raw_bytes = np.asarray(dataset[index], dtype=np.uint8).tobytes() + return np.frombuffer(gzip.decompress(raw_bytes), dtype=np.uint8) + + +def _assert_read_tuples_equal(left: list[tuple], right: list[tuple]) -> None: + assert len(left) == len(right) + for left_tuple, right_tuple in zip(left, right, strict=True): + assert len(left_tuple) == len(right_tuple) + for left_value, right_value in zip(left_tuple, right_tuple, strict=True): + if isinstance(left_value, np.ndarray): + np.testing.assert_array_equal(left_value, right_value) + else: + assert left_value == right_value + + +def test_read_by_base_txt_to_hdf5_uses_dense_span_vectors(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=None, + quiet=True, + ) + + with h5py.File(output_h5, "r") as h5: + sparse_index = 0 + sparse_span = int(h5["read_end"][sparse_index] - h5["read_start"][sparse_index]) + sparse_mod_vector = _decode_vector(h5["mod_vector"], sparse_index) + sparse_val_vector = _decode_vector(h5["val_vector"], sparse_index) + + no_hits_index = 1 + no_hits_span = int( + h5["read_end"][no_hits_index] - h5["read_start"][no_hits_index] + ) + no_hits_mod_vector = _decode_vector(h5["mod_vector"], no_hits_index) + no_hits_val_vector = _decode_vector(h5["val_vector"], no_hits_index) + + assert sparse_span == 10 + assert len(sparse_mod_vector) == sparse_span + assert len(sparse_val_vector) == sparse_span + + assert no_hits_span == 11 + assert len(no_hits_mod_vector) == no_hits_span + assert len(no_hits_val_vector) == no_hits_span + assert np.all(no_hits_mod_vector == 0) + assert np.all(no_hits_val_vector == 0) + + +def test_read_by_base_txt_to_hdf5_thresholds_at_write_time(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + with h5py.File(output_h5, "r") as h5: + assert np.isclose(h5["threshold"][()], 0.5) + + sparse_index = 0 + sparse_mod_vector = _decode_vector(h5["mod_vector"], sparse_index) + sparse_val_vector = _decode_vector(h5["val_vector"], sparse_index) + + assert set(np.unique(sparse_mod_vector)).issubset({0, 1}) + assert set(np.unique(sparse_val_vector)).issubset({0, 1}) + np.testing.assert_array_equal( + sparse_mod_vector, + np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0], dtype=np.uint8), + ) + np.testing.assert_array_equal( + sparse_val_vector, + np.array([1, 0, 0, 0, 0, 0, 0, 0, 1, 0], dtype=np.uint8), + ) + + +def test_read_by_base_txt_to_hdf5_stores_per_motif_threshold_metadata(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded_multi_motif.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.6, + quiet=True, + ) + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="CG,0", + thresh=0.8, + quiet=True, + ) + + with h5py.File(output_h5, "r") as h5: + assert np.isclose(h5["threshold"][()], 0.0) + threshold_map_raw = h5["threshold_by_motif_json"][()] + if isinstance(threshold_map_raw, bytes): + threshold_map_raw = threshold_map_raw.decode("utf-8") + assert json.loads(threshold_map_raw) == {"A,0": 0.6, "CG,0": 0.8} + + read_tuples, datasets, _ = load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0", "CG,0"], + calculate_mod_fractions=False, + quiet=True, + ) + mod_vector_index = datasets.index("mod_vector") + val_vector_index = datasets.index("val_vector") + + assert len(read_tuples) == 4 + assert all( + read_tuple[mod_vector_index].dtype == np.bool_ for read_tuple in read_tuples + ) + assert all( + read_tuple[val_vector_index].dtype == np.bool_ for read_tuple in read_tuples + ) + + +def test_read_by_base_txt_to_hdf5_rejects_mixed_raw_and_thresholded_motifs(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_mixed_threshold_modes.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=None, + quiet=True, + ) + + with pytest.raises( + ValueError, + match="Cannot mix raw-probability and thresholded motifs", + ): + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="CG,0", + thresh=0.7, + quiet=True, + ) + + +def test_read_vectors_from_hdf5_loads_thresholded_vectors_as_binary(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + read_tuples, datasets, _ = load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + calculate_mod_fractions=False, + quiet=True, + ) + + mod_vector_index = datasets.index("mod_vector") + val_vector_index = datasets.index("val_vector") + read_name_index = datasets.index("read_name") + + assert len(read_tuples) == 2 + assert all( + read_tuple[mod_vector_index].dtype == np.bool_ for read_tuple in read_tuples + ) + assert all( + read_tuple[val_vector_index].dtype == np.bool_ for read_tuple in read_tuples + ) + + reads_by_name = { + read_tuple[read_name_index]: read_tuple for read_tuple in read_tuples + } + + sparse_read = reads_by_name["read_sparse"] + no_hits_read = reads_by_name["read_no_hits"] + + np.testing.assert_array_equal( + sparse_read[mod_vector_index], + np.array([False, False, False, False, False, False, False, False, True, False]), + ) + np.testing.assert_array_equal( + sparse_read[val_vector_index], + np.array([True, False, False, False, False, False, False, False, True, False]), + ) + assert not np.any(no_hits_read[mod_vector_index]) + assert not np.any(no_hits_read[val_vector_index]) + + mod_positions, _, _, _ = load_processed.readwise_binary_modification_arrays( + file=output_h5, + motifs=["A,0"], + regions="chr1:95-220", + thresh=None, + quiet=True, + ) + assert len(mod_positions) == 1 + + +def test_read_by_base_txt_to_hdf5_reconstructs_coordinates_robustly(tmp_path): + input_txt = tmp_path / "extract.coordinate_stress.txt" + output_h5 = tmp_path / "reads.coordinate_stress.h5" + _write_coordinate_stress_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + with h5py.File(output_h5, "r") as h5: + read_names = [ + name.decode() if isinstance(name, bytes) else str(name) + for name in h5["read_name"][:] + ] + read_name_to_index = {name: idx for idx, name in enumerate(read_names)} + + plus_idx = read_name_to_index["read_plus_drift"] + minus_idx = read_name_to_index["read_minus"] + + assert int(h5["read_start"][plus_idx]) == 100 + assert int(h5["read_end"][plus_idx]) == 111 + plus_mod = _decode_vector(h5["mod_vector"], plus_idx) + plus_val = _decode_vector(h5["val_vector"], plus_idx) + assert len(plus_mod) == 11 + np.testing.assert_array_equal( + plus_mod, np.array([1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], dtype=np.uint8) + ) + np.testing.assert_array_equal( + plus_val, np.array([1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], dtype=np.uint8) + ) + + assert int(h5["read_start"][minus_idx]) == 200 + assert int(h5["read_end"][minus_idx]) == 210 + minus_mod = _decode_vector(h5["mod_vector"], minus_idx) + minus_val = _decode_vector(h5["val_vector"], minus_idx) + assert len(minus_mod) == 10 + np.testing.assert_array_equal( + minus_mod, np.array([1, 0, 0, 0, 0, 0, 0, 0, 1, 0], dtype=np.uint8) + ) + np.testing.assert_array_equal( + minus_val, np.array([1, 0, 0, 0, 0, 0, 0, 0, 1, 0], dtype=np.uint8) + ) + + +def test_read_by_base_txt_to_hdf5_rejects_out_of_bounds_pos_in_read(tmp_path): + input_txt = tmp_path / "extract.bad_position.txt" + output_h5 = tmp_path / "reads.bad_position.h5" + header = "\t".join(f"col{i}" for i in range(16)) + lines = [ + header, + _make_extract_line( + "read_bad", + pos_in_read=10, + pos_in_genome=110, + chromosome="chr2", + strand="+", + read_len=10, + prob=0.80, + mod_code="a", + canonical_base="A", + ), + ] + input_txt.write_text("\n".join(lines) + "\n") + + with pytest.raises(ValueError, match="out of bounds for read length"): + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + +def test_read_by_base_txt_to_hdf5_rejects_invalid_strand(tmp_path): + input_txt = tmp_path / "extract.bad_strand.txt" + output_h5 = tmp_path / "reads.bad_strand.h5" + header = "\t".join(f"col{i}" for i in range(16)) + lines = [ + header, + _make_extract_line( + "read_bad_strand", + pos_in_read=2, + pos_in_genome=102, + chromosome="chr2", + strand="?", + read_len=10, + prob=0.80, + mod_code="a", + canonical_base="A", + ), + ] + input_txt.write_text("\n".join(lines) + "\n") + + with pytest.raises(ValueError, match="Unexpected strand"): + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + +def test_read_by_base_txt_to_hdf5_rejects_non_positive_read_length(tmp_path): + input_txt = tmp_path / "extract.bad_read_length.txt" + output_h5 = tmp_path / "reads.bad_read_length.h5" + header = "\t".join(f"col{i}" for i in range(16)) + lines = [ + header, + _make_extract_line( + "read_bad_length", + pos_in_read=0, + pos_in_genome=100, + chromosome="chr2", + strand="+", + read_len=0, + prob=0.80, + mod_code="a", + canonical_base="A", + ), + ] + input_txt.write_text("\n".join(lines) + "\n") + + with pytest.raises(ValueError, match="read length must be positive"): + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + +def test_readwise_binary_modification_arrays_returns_empty_for_empty_region(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + mod_positions, read_ids, motifs, regions_dict = ( + load_processed.readwise_binary_modification_arrays( + file=output_h5, + motifs=["A,0"], + regions="chr1:1000-1010", + thresh=None, + quiet=True, + ) + ) + + assert mod_positions.size == 0 + assert read_ids.size == 0 + assert motifs.size == 0 + assert regions_dict == {"chr1": [(1000, 1010, ".")]} + + +def test_read_vectors_from_hdf5_subset_empty_region_returns_empty(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + read_tuples, datasets, regions_dict = load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + regions="chr1:1000-1010", + subset_parameters={"n": 1}, + calculate_mod_fractions=False, + quiet=True, + ) + + assert read_tuples == [] + assert "mod_vector" in datasets + assert regions_dict == {"chr1": [(1000, 1010, ".")]} + + +def test_readwise_binary_modification_arrays_splits_duplicate_read_names_by_region( + tmp_path, +): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + _, read_ids, motifs, regions_dict = ( + load_processed.readwise_binary_modification_arrays( + file=output_h5, + motifs=["A,0"], + regions=["chr1:95-105", "chr1:100-110"], + thresh=None, + quiet=True, + ) + ) + + assert set(read_ids.tolist()) == {0, 1} + assert motifs.size == read_ids.size == 2 + assert regions_dict == {"chr1": [(95, 105, "."), (100, 110, ".")]} + + +def test_read_vectors_from_hdf5_rejects_subset_without_n_or_frac(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + with pytest.raises(ValueError, match="subset_parameters must include"): + load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + regions="chr1:95-110", + subset_parameters={}, + calculate_mod_fractions=False, + quiet=True, + ) + + +def test_read_vectors_from_hdf5_rejects_non_dict_subset_parameters(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + with pytest.raises(ValueError, match="provided as a dictionary"): + load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + regions="chr1:95-110", + subset_parameters=1, + calculate_mod_fractions=False, + quiet=True, + ) + + +def test_read_vectors_from_hdf5_rejects_subset_with_array_key(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + with pytest.raises(ValueError, match="cannot include 'array'"): + load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + regions="chr1:95-110", + subset_parameters={"array": np.array([0]), "n": 1}, + calculate_mod_fractions=False, + quiet=True, + ) + + +def test_read_vectors_from_hdf5_accepts_tuple_sort_by(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + read_tuples, datasets, _ = load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + sort_by=("read_start", "desc"), + calculate_mod_fractions=False, + quiet=True, + ) + + read_name_index = datasets.index("read_name") + assert [read_tuple[read_name_index] for read_tuple in read_tuples] == [ + "read_no_hits", + "read_sparse", + ] + + +def test_read_vectors_from_hdf5_filters_by_min_read_length(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + read_tuples, datasets, _ = load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + min_read_length_bp=11, + calculate_mod_fractions=False, + quiet=True, + ) + + read_name_index = datasets.index("read_name") + assert [row[read_name_index] for row in read_tuples] == ["read_no_hits"] + + +def test_read_vectors_from_hdf5_supports_global_random_read_sample( + tmp_path, monkeypatch +): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + def _pick_first(array, n=None, frac=None, replace=False): + assert n == 1 + return np.asarray(array[:1]) + + monkeypatch.setattr(load_processed.utils, "random_sample", _pick_first) + + read_tuples, datasets, _ = load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + random_sample_n_reads=1, + calculate_mod_fractions=False, + quiet=True, + ) + + read_name_index = datasets.index("read_name") + sampled_names = {row[read_name_index] for row in read_tuples} + assert len(sampled_names) == 1 + + +def test_read_vectors_from_hdf5_rejects_invalid_random_sample_or_length(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + with pytest.raises(ValueError, match="random_sample_n_reads"): + load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + random_sample_n_reads=0, + calculate_mod_fractions=False, + quiet=True, + ) + + with pytest.raises(ValueError, match="min_read_length_bp"): + load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + min_read_length_bp=-1, + calculate_mod_fractions=False, + quiet=True, + ) + + +def test_readwise_binary_modification_arrays_passes_subset_and_quiet(monkeypatch): + captured_kwargs = {} + + def _fake_read_vectors_from_hdf5(**kwargs): + captured_kwargs.update(kwargs) + return ( + [ + ( + "read1", + np.array([True, False], dtype=np.bool_), + "A,0", + 100, + 110, + 100, + ".", + ) + ], + [ + "read_name", + "mod_vector", + "motif", + "region_start", + "region_end", + "read_start", + "region_strand", + ], + {"chr1": [(100, 110, ".")]}, + ) + + monkeypatch.setattr( + load_processed, "read_vectors_from_hdf5", _fake_read_vectors_from_hdf5 + ) + + mod_positions, read_ids, motifs, regions_dict = ( + load_processed.readwise_binary_modification_arrays( + file=Path("dummy.h5"), + motifs=["A,0"], + regions="chr1:100-110", + subset_parameters={"n": 1}, + random_sample_n_reads=1, + min_read_length_bp=1000, + quiet=False, + ) + ) + + assert captured_kwargs["quiet"] is False + assert captured_kwargs["subset_parameters"] == {"n": 1} + assert captured_kwargs["random_sample_n_reads"] == 1 + assert captured_kwargs["min_read_length_bp"] == 1000 + assert mod_positions.tolist() == [-5] + assert read_ids.tolist() == [0] + assert motifs.tolist() == ["A,0"] + assert regions_dict == {"chr1": [(100, 110, ".")]} + + +def test_readwise_binary_modification_arrays_parallel_matches_serial(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + serial = load_processed.readwise_binary_modification_arrays( + file=output_h5, + motifs=["A,0"], + regions="chr1:95-220", + thresh=None, + quiet=True, + cores=1, + ) + parallel = load_processed.readwise_binary_modification_arrays( + file=output_h5, + motifs=["A,0"], + regions="chr1:95-220", + thresh=None, + quiet=True, + cores=2, + ) + + np.testing.assert_array_equal(serial[0], parallel[0]) + np.testing.assert_array_equal(serial[1], parallel[1]) + np.testing.assert_array_equal(serial[2], parallel[2]) + assert serial[3] == parallel[3] + + +def test_read_vectors_from_hdf5_parallel_region_selection_matches_serial(tmp_path): + input_txt = tmp_path / "extract.txt" + output_h5 = tmp_path / "reads_thresholded.h5" + _write_extract_file(input_txt) + + parse_bam.read_by_base_txt_to_hdf5( + input_txt=input_txt, + output_h5=output_h5, + motif="A,0", + thresh=0.5, + quiet=True, + ) + + serial = load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + regions=["chr1:95-105", "chr1:100-110"], + quiet=True, + calculate_mod_fractions=False, + cores=1, + ) + parallel = load_processed.read_vectors_from_hdf5( + file=output_h5, + motifs=["A,0"], + regions=["chr1:95-105", "chr1:100-110"], + quiet=True, + calculate_mod_fractions=False, + cores=2, + ) + + _assert_read_tuples_equal(serial[0], parallel[0]) + assert serial[1] == parallel[1] + assert serial[2] == parallel[2] diff --git a/tests/test_plot_read_browser.py b/tests/test_plot_read_browser.py new file mode 100644 index 0000000..df9363b --- /dev/null +++ b/tests/test_plot_read_browser.py @@ -0,0 +1,132 @@ +import numpy as np +import pandas as pd + +from dimelo import plot_read_browser + + +def test_collapse_rows_preserves_expected_assignment(): + read_extent_df = pd.DataFrame( + { + "read_start": [0, 10, 30, 60], + "read_end": [50, 20, 40, 70], + "y_index": [0, 1, 2, 3], + "read_name": ["r0", "r1", "r2", "r3"], + } + ) + + collapsed = plot_read_browser.collapse_rows( + read_extent_df, + minimum_gap=0, + meta_sort=None, + ) + + assert collapsed.to_dict() == {0: 0, 1: 1, 2: 1, 3: 0} + + +def test_make_browser_figure_batches_read_extents_into_single_trace(): + read_extent_df = pd.DataFrame( + { + "read_start": [100, 150, 200], + "read_end": [130, 185, 240], + "y_index": [0, 1, 2], + "read_name": ["readA", "readB", "readC"], + } + ) + mod_event_df = pd.DataFrame( + { + "y_index": [0, 1, 2], + "read_name": ["readA", "readB", "readC"], + "motif": ["A,0", "A,0", "A,0"], + "pos": [105, 160, 225], + "prob": [0.4, 0.8, 0.6], + } + ) + + fig = plot_read_browser.make_browser_figure( + read_extent_df=read_extent_df, + mod_event_df=mod_event_df, + collapse=False, + chrom="chr1", + region_start=90, + region_end=260, + colorscales={"A,0": "Viridis"}, + ) + + assert len(fig.data) == 2 + line_trace = fig.data[0] + assert line_trace.mode == "lines" + assert np.count_nonzero(np.array(line_trace.x, dtype=object) == None) == 3 # noqa: E711 + + +def test_format_browser_data_preserves_event_vectors_and_duplicate_extent_handling(): + entry_labels = [ + "chromosome", + "strand", + "region_start", + "region_end", + "read_start", + "read_end", + "read_name", + "motif", + "mod_vector", + "val_vector", + ] + read_tuples = [ + ( + "chr1", + "+", + 90, + 120, + 100, + 110, + "read1", + "A,0", + np.array([0.1, 0.8, 0.2]), + np.array([0, 1, 1]), + ), + ( + "chr1", + "+", + 90, + 120, + 102, + 109, + "read1", + "C,0", + np.array([0.3, 0.4]), + np.array([1, 0]), + ), + ( + "chr1", + "-", + 180, + 210, + 200, + 204, + "read2", + "A,0", + np.array([0.9, 0.7]), + np.array([1, 0]), + ), + ] + + read_extent_df, mod_event_df = plot_read_browser.format_browser_data( + read_tuples=read_tuples, + entry_labels=entry_labels, + ) + + assert read_extent_df.reset_index(drop=True).to_dict("records") == [ + {"read_start": 100, "read_end": 110, "y_index": 0, "read_name": "read1"}, + {"read_start": 200, "read_end": 204, "y_index": 1, "read_name": "read2"}, + ] + + assert list( + mod_event_df[["y_index", "read_name", "motif", "pos", "prob"]].itertuples( + index=False, name=None + ) + ) == [ + (0, "read1", "A,0", 101, 0.8), + (0, "read1", "A,0", 102, 0.2), + (0, "read1", "C,0", 102, 0.3), + (1, "read2", "A,0", 200, 0.9), + ] diff --git a/tests/test_plotting.py b/tests/test_plotting.py new file mode 100644 index 0000000..dd86428 --- /dev/null +++ b/tests/test_plotting.py @@ -0,0 +1,4183 @@ +import numpy as np +import pandas as pd +import pytest + +from dimelo import ( + plot_depth_histogram, + plot_depth_profile, + plot_enrichment, + plot_enrichment_profile, + plot_reads, + plotting, + plotting_matplotlib, +) +from dimelo.models import ( + ContrastSpec, + GlobalAnalysisResult, + RegionContrastResult, + RegionDiscoveryResult, + SharedClusterModel, + SharedClusterResult, +) + + +@pytest.fixture(autouse=True) +def _close_matplotlib_figures(): + yield + try: + from matplotlib import pyplot as plt + except ImportError: + return + plt.close("all") + + +def test_axis_spec_accepts_fixed_window_region_5to3(): + spec = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=1000, + downstream_bp=1000, + ) + + plotting.validate_axis_spec(spec, plot_family="aggregate_profile") + + +def test_plot_enrichment_by_regions_accepts_regions_alias(monkeypatch): + captured = {} + + def fake_plot_enrichment(**kwargs): + captured.update(kwargs) + return "axes" + + monkeypatch.setattr(plot_enrichment, "plot_enrichment", fake_plot_enrichment) + + axes = plot_enrichment.by_regions( + mod_file_name="pileup.fake", + regions=["one.bed", "two.bed"], + motif="A,0", + ) + + assert axes == "axes" + assert captured["regions_list"] == ["one.bed", "two.bed"] + assert captured["sample_names"] == ["one.bed", "two.bed"] + + +def test_plot_depth_profile_by_regions_accepts_regions_alias(monkeypatch): + captured = {} + + def fake_plot_depth_profile(**kwargs): + captured.update(kwargs) + return "axes" + + monkeypatch.setattr( + plot_depth_profile, "plot_depth_profile", fake_plot_depth_profile + ) + + axes = plot_depth_profile.by_regions( + mod_file_name="pileup.fake", + regions=["one.bed", "two.bed"], + motif="A,0", + ) + + assert axes == "axes" + assert captured["regions_list"] == ["one.bed", "two.bed"] + assert captured["sample_names"] == ["one.bed depth", "two.bed depth"] + + +def test_plot_enrichment_profile_by_regions_accepts_regions_alias(monkeypatch): + captured = {} + + def fake_plot_enrichment_profile(**kwargs): + captured.update(kwargs) + return "axes" + + monkeypatch.setattr( + plot_enrichment_profile, + "plot_enrichment_profile", + fake_plot_enrichment_profile, + ) + + axes = plot_enrichment_profile.by_regions( + mod_file_name="pileup.fake", + regions=["one.bed", "two.bed"], + motif="A,0", + ) + + assert axes == "axes" + assert captured["regions_list"] == ["one.bed", "two.bed"] + assert captured["sample_names"] == ["one.bed", "two.bed"] + + +def test_make_enrichment_profile_plot_defaults_motif_legend_title(): + import matplotlib + + matplotlib.use("Agg") + + axes = plot_enrichment_profile.make_enrichment_profile_plot( + trace_vectors=[np.array([0.1, 0.2, 0.3]), np.array([0.2, 0.1, 0.0])], + sample_names=["A,0", "CG,0"], + ) + + legend = axes.get_legend() + assert legend is not None + assert legend.get_title().get_text() == "Modifications (motif, mod_index)" + + +def test_make_depth_profile_plot_defaults_axis_and_legend_labels(): + import matplotlib + + matplotlib.use("Agg") + + axes = plot_depth_profile.make_depth_profile_plot( + trace_vectors=[np.array([1.0, 2.0, 3.0]), np.array([3.0, 2.0, 1.0])], + sample_names=["A,0 depth", "CG,0 depth"], + ) + + assert axes.get_xlabel() == "Position (bp)" + legend = axes.get_legend() + assert legend is not None + assert legend.get_title().get_text() == "Mod, index" + + +def test_plot_depth_histogram_by_regions_accepts_regions_alias(monkeypatch): + captured = {} + + def fake_plot_depth_histogram(**kwargs): + captured.update(kwargs) + return "axes" + + monkeypatch.setattr( + plot_depth_histogram, + "plot_depth_histogram", + fake_plot_depth_histogram, + ) + + axes = plot_depth_histogram.by_regions( + mod_file_name="pileup.fake", + regions=["one.bed", "two.bed"], + motif="A,0", + ) + + assert axes == "axes" + assert captured["regions_list"] == ["one.bed", "two.bed"] + assert captured["sample_names"] == ["one.bed depth", "two.bed depth"] + + +def test_get_depth_counts_forwards_split_large_regions(monkeypatch): + captured = {} + + def fake_regions_to_list(*, split_large_regions=False, **kwargs): + captured["split_large_regions"] = split_large_regions + return [(np.array([1, 0, 2]), np.array([4, 0, 6]))] + + monkeypatch.setattr( + plot_depth_histogram.load_processed, + "regions_to_list", + fake_regions_to_list, + ) + + depth_vectors = plot_depth_histogram.get_depth_counts( + mod_file_names=["pileup.bed.gz"], + regions_list=["regions.bed"], + motifs=["A,0"], + window_size=1000, + split_large_regions=True, + ) + + assert captured["split_large_regions"] is True + assert len(depth_vectors) == 1 + np.testing.assert_array_equal(depth_vectors[0], np.array([4, 6])) + + +def test_axis_spec_rejects_segment_map_without_segments(): + spec = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=None, + ) + + with pytest.raises(ValueError, match="segment_map requires segments"): + plotting.validate_axis_spec(spec, plot_family="aggregate_profile") + + +def test_single_read_rejects_scaled_segments(): + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec( + segment_id="body", + label="Body", + start_ref=100, + end_ref=500, + mode="scaled", + bins=50, + ) + ], + ) + + with pytest.raises(ValueError, match="single_read"): + plotting.validate_axis_spec(axis, plot_family="single_read_raster") + + +def test_aggregation_spec_accepts_equal_region_default(): + spec = plotting.AggregationSpec() + + plotting.validate_aggregation_spec(spec) + + +def test_prepare_cluster_distribution_bar_data_handles_empty_input_shape(): + cluster_distribution = pd.DataFrame( + columns=["sample_id", "condition", "cluster", "count", "fraction"] + ) + + result = plotting.prepare_cluster_distribution_bar_data(cluster_distribution) + + assert list(result.columns) == [ + "sample_id", + "condition", + "cluster", + "count", + "fraction", + ] + assert result.empty + + +def test_prepare_cluster_distribution_bar_data_keeps_stable_sorted_output(): + cluster_distribution = pd.DataFrame( + { + "sample_id": ["s2", "s1", "s1", "s2"], + "condition": ["15min", "NS", "NS", "15min"], + "cluster": ["C0", "C0", "C0", "C1"], + "count": [1, 2, 3, 4], + "fraction": [0.25, 0.66, 0.34, 0.75], + } + ) + + result = plotting.prepare_cluster_distribution_bar_data(cluster_distribution) + + assert list(result["sample_id"]) == ["s1", "s1", "s2", "s2"] + assert list(result["condition"]) == ["NS", "NS", "15min", "15min"] + assert list(result["cluster"]) == ["C0", "C0", "C0", "C1"] + assert list(result["count"]) == [2, 3, 1, 4] + + +def test_prepare_cluster_distribution_heatmap_data_handles_empty_input_shape(): + condition_distribution = pd.DataFrame(columns=["condition", "cluster", "fraction"]) + + result = plotting.prepare_cluster_distribution_heatmap_data(condition_distribution) + + assert list(result.columns) == ["condition"] + assert result.empty + + +def test_prepare_cluster_distribution_heatmap_data_pivots_columns_in_sorted_order(): + condition_distribution = pd.DataFrame( + { + "condition": ["NS", "NS", "15min", "15min"], + "cluster": ["C1", "C0", "C1", "C0"], + "fraction": [0.25, 0.75, 0.75, 0.25], + } + ) + + result = plotting.prepare_cluster_distribution_heatmap_data(condition_distribution) + + assert list(result.columns) == ["condition", "C0", "C1"] + assert list(result["condition"]) == ["15min", "NS"] + fifteen_min = result[result["condition"] == "15min"].iloc[0] + ns_row = result[result["condition"] == "NS"].iloc[0] + assert fifteen_min["C0"] == 0.25 + assert fifteen_min["C1"] == 0.75 + assert ns_row["C0"] == 0.75 + assert ns_row["C1"] == 0.25 + + +def _make_shared_cluster_result() -> SharedClusterResult: + model = SharedClusterModel( + mode="region_anchored", + motifs=["A,0"], + feature_names=["f0", "f1"], + preprocessing={"signal_normalization": "none"}, + estimator=object(), + cluster_labels=["C1", "C0"], + fit_metadata={"clusterer": "minibatch_kmeans", "n_clusters": 2}, + ) + cluster_distribution = pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "cluster": "C0", + "count": 2, + "fraction": 2 / 3, + }, + { + "sample_id": "s1", + "condition": "NS", + "cluster": "C1", + "count": 1, + "fraction": 1 / 3, + }, + { + "sample_id": "s2", + "condition": "treated", + "cluster": "C0", + "count": 1, + "fraction": 1 / 4, + }, + { + "sample_id": "s2", + "condition": "treated", + "cluster": "C1", + "count": 3, + "fraction": 3 / 4, + }, + ] + ) + condition_distribution = pd.DataFrame( + [ + { + "condition": "NS", + "cluster": "C0", + "count": 2, + "fraction": 2 / 3, + "replicate_n": 1, + }, + { + "condition": "NS", + "cluster": "C1", + "count": 1, + "fraction": 1 / 3, + "replicate_n": 1, + }, + { + "condition": "treated", + "cluster": "C0", + "count": 1, + "fraction": 1 / 4, + "replicate_n": 1, + }, + { + "condition": "treated", + "cluster": "C1", + "count": 3, + "fraction": 3 / 4, + "replicate_n": 1, + }, + ] + ) + distribution_change = pd.DataFrame( + [ + { + "condition": "treated", + "cluster": "C0", + "count": 1, + "fraction": 1 / 4, + "replicate_n": 1, + "reference_fraction": 2 / 3, + "delta_fraction": -5 / 12, + "log2_fc": -1.415037499278844, + }, + { + "condition": "treated", + "cluster": "C1", + "count": 3, + "fraction": 3 / 4, + "replicate_n": 1, + "reference_fraction": 1 / 3, + "delta_fraction": 5 / 12, + "log2_fc": 1.1699250014423124, + }, + ] + ) + return SharedClusterResult( + model=model, + assignments=pd.DataFrame(columns=["sample_id", "condition", "cluster"]), + cluster_distribution=cluster_distribution, + condition_distribution=condition_distribution, + distribution_change=distribution_change, + cluster_profiles=pd.DataFrame(columns=["cluster", "count", "f0", "f1"]), + region_summaries=None, + plot_data={}, + metadata={"mode": "region_anchored"}, + ) + + +def _make_shared_cluster_result_with_explicit_display_order() -> SharedClusterResult: + model = SharedClusterModel( + mode="region_anchored", + motifs=["A,0"], + feature_names=["f0", "f1"], + preprocessing={"signal_normalization": "none"}, + estimator=object(), + cluster_labels=["C1", "C0"], + fit_metadata={"clusterer": "minibatch_kmeans", "n_clusters": 2}, + ) + cluster_distribution = pd.DataFrame( + [ + { + "sample_id": "s2", + "condition": "treated", + "cluster": "C1", + "count": 3, + "fraction": 3 / 4, + }, + { + "sample_id": "s2", + "condition": "treated", + "cluster": "C0", + "count": 1, + "fraction": 1 / 4, + }, + { + "sample_id": "s1", + "condition": "NS", + "cluster": "C1", + "count": 1, + "fraction": 1 / 3, + }, + { + "sample_id": "s1", + "condition": "NS", + "cluster": "C0", + "count": 2, + "fraction": 2 / 3, + }, + ] + ) + condition_distribution = pd.DataFrame( + [ + { + "condition": "treated", + "cluster": "C1", + "count": 3, + "fraction": 3 / 4, + "replicate_n": 1, + }, + { + "condition": "treated", + "cluster": "C0", + "count": 1, + "fraction": 1 / 4, + "replicate_n": 1, + }, + { + "condition": "NS", + "cluster": "C1", + "count": 1, + "fraction": 1 / 3, + "replicate_n": 1, + }, + { + "condition": "NS", + "cluster": "C0", + "count": 2, + "fraction": 2 / 3, + "replicate_n": 1, + }, + ] + ) + distribution_change = pd.DataFrame( + [ + { + "condition": "treated", + "cluster": "C1", + "count": 3, + "fraction": 3 / 4, + "replicate_n": 1, + "reference_fraction": 1 / 3, + "delta_fraction": 5 / 12, + "log2_fc": 1.1699250014423124, + }, + { + "condition": "treated", + "cluster": "C0", + "count": 1, + "fraction": 1 / 4, + "replicate_n": 1, + "reference_fraction": 2 / 3, + "delta_fraction": -5 / 12, + "log2_fc": -1.415037499278844, + }, + { + "condition": "baseline", + "cluster": "C1", + "count": 2, + "fraction": 1 / 2, + "replicate_n": 1, + "reference_fraction": 1 / 2, + "delta_fraction": 0.0, + "log2_fc": 0.0, + }, + { + "condition": "baseline", + "cluster": "C0", + "count": 2, + "fraction": 1 / 2, + "replicate_n": 1, + "reference_fraction": 1 / 2, + "delta_fraction": 0.0, + "log2_fc": 0.0, + }, + ] + ) + return SharedClusterResult( + model=model, + assignments=pd.DataFrame(columns=["sample_id", "condition", "cluster"]), + cluster_distribution=cluster_distribution, + condition_distribution=condition_distribution, + distribution_change=distribution_change, + cluster_profiles=pd.DataFrame(columns=["cluster", "count", "f0", "f1"]), + region_summaries=None, + plot_data={}, + metadata={"mode": "region_anchored"}, + ) + + +def test_prepare_shared_cluster_distribution_data_returns_distribution_payload(): + result = _make_shared_cluster_result() + + payload = plotting.prepare_shared_cluster_distribution_data(result=result) + sample_distribution = payload["sample_distribution"] + condition_distribution = payload["condition_distribution"] + distribution_change = payload["distribution_change"] + + assert set(payload) == { + "sample_distribution", + "condition_distribution", + "distribution_change", + "metadata", + } + assert sample_distribution["sample_id"].tolist() == ["s1", "s1", "s2", "s2"] + assert sample_distribution["condition"].tolist() == [ + "NS", + "NS", + "treated", + "treated", + ] + assert sample_distribution["cluster"].tolist() == ["C0", "C1", "C0", "C1"] + assert sample_distribution["count"].tolist() == [2, 1, 1, 3] + assert sample_distribution["fraction"].tolist() == pytest.approx( + [2 / 3, 1 / 3, 1 / 4, 3 / 4] + ) + assert condition_distribution["condition"].tolist() == [ + "NS", + "NS", + "treated", + "treated", + ] + assert condition_distribution["cluster"].tolist() == ["C0", "C1", "C0", "C1"] + assert condition_distribution["count"].tolist() == [2, 1, 1, 3] + assert condition_distribution["fraction"].tolist() == pytest.approx( + [2 / 3, 1 / 3, 1 / 4, 3 / 4] + ) + assert condition_distribution["replicate_n"].tolist() == [1, 1, 1, 1] + assert distribution_change["condition"].tolist() == ["treated", "treated"] + assert distribution_change["cluster"].tolist() == ["C0", "C1"] + assert distribution_change["count"].tolist() == [1, 3] + assert distribution_change["fraction"].tolist() == pytest.approx([1 / 4, 3 / 4]) + assert distribution_change["reference_fraction"].tolist() == pytest.approx( + [2 / 3, 1 / 3] + ) + assert distribution_change["delta_fraction"].tolist() == pytest.approx( + [-5 / 12, 5 / 12] + ) + assert distribution_change["log2_fc"].tolist() == pytest.approx( + [-1.415037499278844, 1.1699250014423124] + ) + assert payload["metadata"]["mode"] == "region_anchored" + assert payload["metadata"]["cluster_labels"] == ["C1", "C0"] + assert payload["metadata"]["change_condition_order"] == ["treated"] + assert "sample_order" not in payload["metadata"] + assert "condition_order" not in payload["metadata"] + + +def test_plotting_matplotlib_module_exports_save_figure(): + from dimelo import plotting_matplotlib + + assert hasattr(plotting_matplotlib, "save_figure") + + +def test_save_figure_writes_png(tmp_path): + from matplotlib import pyplot as plt + + from dimelo import plotting_matplotlib + + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + output_path = tmp_path / "figure.png" + plotting_matplotlib.save_figure(fig, output_path) + + assert output_path.exists() + assert output_path.stat().st_size > 0 + + +def test_prepare_shared_cluster_distribution_data_handles_missing_change_table(): + result = _make_shared_cluster_result() + result.distribution_change = None + + payload = plotting.prepare_shared_cluster_distribution_data(result=result) + sample_distribution = payload["sample_distribution"] + condition_distribution = payload["condition_distribution"] + + assert list(payload["distribution_change"].columns) == [ + "condition", + "cluster", + "count", + "fraction", + "replicate_n", + "reference_fraction", + "delta_fraction", + "log2_fc", + ] + assert payload["distribution_change"].empty + assert sample_distribution["sample_id"].tolist() == ["s1", "s1", "s2", "s2"] + assert sample_distribution["cluster"].tolist() == ["C0", "C1", "C0", "C1"] + assert sample_distribution["count"].tolist() == [2, 1, 1, 3] + assert sample_distribution["fraction"].tolist() == pytest.approx( + [2 / 3, 1 / 3, 1 / 4, 3 / 4] + ) + assert condition_distribution["condition"].tolist() == [ + "NS", + "NS", + "treated", + "treated", + ] + assert condition_distribution["cluster"].tolist() == ["C0", "C1", "C0", "C1"] + assert condition_distribution["count"].tolist() == [2, 1, 1, 3] + assert condition_distribution["fraction"].tolist() == pytest.approx( + [2 / 3, 1 / 3, 1 / 4, 3 / 4] + ) + assert condition_distribution["replicate_n"].tolist() == [1, 1, 1, 1] + + +def test_plot_shared_cluster_distribution_matplotlib_returns_figure_and_axis(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_distribution_data( + result=_make_shared_cluster_result_with_explicit_display_order() + ) + assert payload["sample_distribution"]["sample_id"].tolist() == [ + "s1", + "s1", + "s2", + "s2", + ] + assert payload["condition_distribution"]["condition"].tolist() == [ + "NS", + "NS", + "treated", + "treated", + ] + + fig, ax = plotting_matplotlib.plot_shared_cluster_distribution_matplotlib( + payload, level="sample" + ) + + assert fig is not None + assert ax is not None + assert [text.get_text() for text in ax.get_legend().texts] == ["C1", "C0"] + assert [tick.get_text() for tick in ax.get_xticklabels() if tick.get_text()] == [ + "s1", + "s2", + ] + + fig, ax = plotting_matplotlib.plot_shared_cluster_distribution_matplotlib( + payload, level="condition" + ) + + assert fig is not None + assert ax is not None + assert [text.get_text() for text in ax.get_legend().texts] == ["C1", "C0"] + assert [tick.get_text() for tick in ax.get_xticklabels() if tick.get_text()] == [ + "NS", + "treated", + ] + + +def test_plot_shared_cluster_distribution_matplotlib_rejects_duplicate_x_cluster_rows(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_distribution_data( + result=_make_shared_cluster_result() + ) + duplicate_row = payload["sample_distribution"].iloc[[0]] + payload["sample_distribution"] = pd.concat( + [payload["sample_distribution"], duplicate_row], + ignore_index=True, + ) + + with pytest.raises(ValueError, match="duplicate cluster fractions"): + plotting_matplotlib.plot_shared_cluster_distribution_matplotlib( + payload, level="sample" + ) + + +def test_plot_shared_cluster_change_matplotlib_returns_figure_and_axis(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_distribution_data( + result=_make_shared_cluster_result_with_explicit_display_order() + ) + assert payload["distribution_change"]["condition"].tolist() == [ + "baseline", + "baseline", + "treated", + "treated", + ] + + fig, ax = plotting_matplotlib.plot_shared_cluster_change_matplotlib(payload) + + assert fig is not None + assert ax is not None + image = ax.images[0] + + assert [tick.get_text() for tick in ax.get_xticklabels()] == ["C1", "C0"] + assert [tick.get_text() for tick in ax.get_yticklabels()] == ["baseline", "treated"] + assert image.origin == "upper" + np.testing.assert_allclose( + np.asarray(image.get_array()), + np.array([[0.0, 0.0], [5 / 12, -5 / 12]]), + ) + + +def test_plot_shared_cluster_change_matplotlib_rejects_duplicate_condition_cluster_rows(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_distribution_data( + result=_make_shared_cluster_result() + ) + duplicate_row = payload["distribution_change"].iloc[[0]] + payload["distribution_change"] = pd.concat( + [payload["distribution_change"], duplicate_row], + ignore_index=True, + ) + + with pytest.raises(ValueError, match="duplicate delta fractions"): + plotting_matplotlib.plot_shared_cluster_change_matplotlib(payload) + + +def test_plot_shared_cluster_change_matplotlib_preserves_sparse_missing_pairs(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_distribution_data( + result=_make_shared_cluster_result_with_explicit_display_order() + ) + payload["distribution_change"] = ( + payload["distribution_change"].iloc[[1, 2]].reset_index(drop=True) + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_change_matplotlib(payload) + + assert fig is not None + assert ax is not None + image = ax.images[0] + matrix = np.asarray(image.get_array()) + + assert [tick.get_text() for tick in ax.get_xticklabels()] == ["C1", "C0"] + assert [tick.get_text() for tick in ax.get_yticklabels()] == ["baseline", "treated"] + assert image.origin == "upper" + assert np.isnan(matrix[0, 1]) + assert np.isnan(matrix[1, 0]) + assert matrix[0, 0] == pytest.approx(0.0) + assert matrix[1, 1] == pytest.approx(-5 / 12) + + +def test_plot_shared_cluster_change_matplotlib_preserves_missing_condition_row(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_distribution_data( + result=_make_shared_cluster_result_with_explicit_display_order() + ) + payload["distribution_change"] = ( + payload["distribution_change"] + .loc[payload["distribution_change"]["condition"] == "treated"] + .reset_index(drop=True) + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_change_matplotlib(payload) + + assert fig is not None + assert ax is not None + image = ax.images[0] + matrix = np.asarray(image.get_array()) + + assert [tick.get_text() for tick in ax.get_yticklabels()] == ["baseline", "treated"] + assert image.origin == "upper" + assert np.isnan(matrix[0, 0]) + assert np.isnan(matrix[0, 1]) + assert matrix[1, 0] == pytest.approx(5 / 12) + assert matrix[1, 1] == pytest.approx(-5 / 12) + + +def test_plot_shared_cluster_change_matplotlib_preserves_missing_cluster_column(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_distribution_data( + result=_make_shared_cluster_result_with_explicit_display_order() + ) + payload["distribution_change"] = ( + payload["distribution_change"] + .loc[payload["distribution_change"]["cluster"] == "C1"] + .reset_index(drop=True) + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_change_matplotlib(payload) + + assert fig is not None + assert ax is not None + image = ax.images[0] + matrix = np.asarray(image.get_array()) + + assert [tick.get_text() for tick in ax.get_xticklabels()] == ["C1", "C0"] + assert image.origin == "upper" + assert matrix[0, 0] == pytest.approx(0.0) + assert matrix[1, 0] == pytest.approx(5 / 12) + assert np.isnan(matrix[0, 1]) + assert np.isnan(matrix[1, 1]) + + +def _make_shared_cluster_profile_result() -> SharedClusterResult: + return SharedClusterResult( + model=SharedClusterModel( + mode="region_anchored", + motifs=["A,0"], + feature_names=["f0", "f1"], + preprocessing={"signal_normalization": "none"}, + estimator=object(), + cluster_labels=["C0", "C1"], + fit_metadata={"clusterer": "minibatch_kmeans", "n_clusters": 2}, + ), + assignments=pd.DataFrame(columns=["sample_id", "condition", "cluster"]), + cluster_distribution=pd.DataFrame( + columns=["sample_id", "condition", "cluster", "count", "fraction"] + ), + condition_distribution=pd.DataFrame( + columns=["condition", "cluster", "count", "fraction", "replicate_n"] + ), + distribution_change=None, + cluster_profiles=pd.DataFrame( + [ + {"cluster": "C0", "count": 3, "f0": 0.1, "f1": 0.2}, + {"cluster": "C1", "count": 4, "f0": 0.8, "f1": 0.9}, + ] + ), + region_summaries=None, + plot_data={}, + metadata={"mode": "region_anchored"}, + ) + + +def _make_shared_cluster_profile_result_with_explicit_display_order() -> ( + SharedClusterResult +): + return SharedClusterResult( + model=SharedClusterModel( + mode="region_anchored", + motifs=["A,0"], + feature_names=["f1", "f0"], + preprocessing={"signal_normalization": "none"}, + estimator=object(), + cluster_labels=["C1", "C0"], + fit_metadata={"clusterer": "minibatch_kmeans", "n_clusters": 2}, + ), + assignments=pd.DataFrame(columns=["sample_id", "condition", "cluster"]), + cluster_distribution=pd.DataFrame( + columns=["sample_id", "condition", "cluster", "count", "fraction"] + ), + condition_distribution=pd.DataFrame( + columns=["condition", "cluster", "count", "fraction", "replicate_n"] + ), + distribution_change=None, + cluster_profiles=pd.DataFrame( + [ + {"cluster": "C0", "count": 3, "f0": 0.1, "f1": 0.2}, + {"cluster": "C1", "count": 4, "f0": 0.8, "f1": 0.9}, + ] + ), + region_summaries=None, + plot_data={}, + metadata={"mode": "region_anchored"}, + ) + + +def test_prepare_shared_cluster_profile_data_returns_long_form_profiles(): + result = _make_shared_cluster_profile_result() + + payload = plotting.prepare_shared_cluster_profile_data(result=result) + profile_table = payload["profile_table"] + + assert set(payload) == {"profile_table", "metadata"} + assert set(profile_table.columns) == {"cluster", "feature", "value", "count"} + assert profile_table.to_dict("records") == [ + {"cluster": "C0", "feature": "f0", "value": pytest.approx(0.1), "count": 3}, + {"cluster": "C0", "feature": "f1", "value": pytest.approx(0.2), "count": 3}, + {"cluster": "C1", "feature": "f0", "value": pytest.approx(0.8), "count": 4}, + {"cluster": "C1", "feature": "f1", "value": pytest.approx(0.9), "count": 4}, + ] + assert payload["metadata"]["feature_names"] == ["f0", "f1"] + + +def test_prepare_shared_cluster_profile_data_respects_feature_subset(): + result = _make_shared_cluster_profile_result() + + payload = plotting.prepare_shared_cluster_profile_data( + result=result, features=["f1"] + ) + profile_table = payload["profile_table"] + + assert profile_table.to_dict("records") == [ + {"cluster": "C0", "feature": "f1", "value": pytest.approx(0.2), "count": 3}, + {"cluster": "C1", "feature": "f1", "value": pytest.approx(0.9), "count": 4}, + ] + + +def test_plot_shared_cluster_profile_heatmap_matplotlib_returns_figure_and_axis(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_profile_data( + result=_make_shared_cluster_profile_result() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_profile_heatmap_matplotlib( + payload + ) + + assert fig is not None + assert ax is not None + + +def test_plot_shared_cluster_profile_heatmap_matplotlib_preserves_payload_order(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_profile_data( + result=_make_shared_cluster_profile_result_with_explicit_display_order() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_profile_heatmap_matplotlib( + payload + ) + + assert fig is not None + assert ax is not None + image = ax.images[0] + + assert [tick.get_text() for tick in ax.get_xticklabels()] == ["f1", "f0"] + assert [tick.get_text() for tick in ax.get_yticklabels()] == ["C1", "C0"] + assert image.origin == "upper" + np.testing.assert_allclose( + np.asarray(image.get_array()), + np.array([[0.9, 0.8], [0.2, 0.1]]), + ) + + +def test_plot_shared_cluster_profile_heatmap_matplotlib_rejects_duplicate_cluster_feature_rows(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_profile_data( + result=_make_shared_cluster_profile_result() + ) + duplicate_row = payload["profile_table"].iloc[[0]] + payload["profile_table"] = pd.concat( + [payload["profile_table"], duplicate_row], + ignore_index=True, + ) + + with pytest.raises(ValueError, match="duplicate profile values"): + plotting_matplotlib.plot_shared_cluster_profile_heatmap_matplotlib(payload) + + +def test_plot_shared_cluster_profile_heatmap_matplotlib_handles_empty_payload(): + from dimelo import plotting_matplotlib + + result = _make_shared_cluster_profile_result() + result.cluster_profiles = pd.DataFrame(columns=["cluster", "count", "f0", "f1"]) + payload = plotting.prepare_shared_cluster_profile_data(result=result) + + fig, ax = plotting_matplotlib.plot_shared_cluster_profile_heatmap_matplotlib( + payload + ) + + assert fig is not None + assert ax is not None + assert len(ax.images) == 0 + assert [tick.get_text() for tick in ax.get_xticklabels()] == ["f0", "f1"] + assert [tick.get_text() for tick in ax.get_yticklabels()] == ["C0", "C1"] + + +def test_plot_shared_cluster_profile_series_matplotlib_returns_figure_and_axis(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_profile_data( + result=_make_shared_cluster_profile_result() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_profile_series_matplotlib(payload) + + assert fig is not None + assert ax is not None + + +def test_plot_shared_cluster_profile_series_matplotlib_preserves_payload_order(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_profile_data( + result=_make_shared_cluster_profile_result_with_explicit_display_order() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_profile_series_matplotlib(payload) + + assert fig is not None + assert ax is not None + assert [tick.get_text() for tick in ax.get_xticklabels()] == ["f1", "f0"] + assert [line.get_label() for line in ax.lines] == ["C1", "C0"] + np.testing.assert_allclose(ax.lines[0].get_xdata(), np.array([0, 1])) + np.testing.assert_allclose(ax.lines[0].get_ydata(), np.array([0.9, 0.8])) + np.testing.assert_allclose(ax.lines[1].get_xdata(), np.array([0, 1])) + np.testing.assert_allclose(ax.lines[1].get_ydata(), np.array([0.2, 0.1])) + + +def test_plot_shared_cluster_profile_series_matplotlib_rejects_duplicate_cluster_feature_rows(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_profile_data( + result=_make_shared_cluster_profile_result() + ) + duplicate_row = payload["profile_table"].iloc[[0]] + payload["profile_table"] = pd.concat( + [payload["profile_table"], duplicate_row], + ignore_index=True, + ) + + with pytest.raises(ValueError, match="duplicate profile values"): + plotting_matplotlib.plot_shared_cluster_profile_series_matplotlib(payload) + + +def test_plot_shared_cluster_profile_series_matplotlib_preserves_missing_cluster_line(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_profile_data( + result=_make_shared_cluster_profile_result_with_explicit_display_order() + ) + payload["profile_table"] = ( + payload["profile_table"] + .loc[payload["profile_table"]["cluster"] == "C1"] + .reset_index(drop=True) + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_profile_series_matplotlib(payload) + + assert fig is not None + assert ax is not None + assert [line.get_label() for line in ax.lines] == ["C1", "C0"] + np.testing.assert_allclose(ax.lines[0].get_ydata(), np.array([0.9, 0.8])) + assert np.isnan(ax.lines[1].get_ydata()).all() + + +def test_plot_shared_cluster_profile_series_matplotlib_handles_empty_payload(): + from dimelo import plotting_matplotlib + + result = _make_shared_cluster_profile_result() + result.cluster_profiles = pd.DataFrame(columns=["cluster", "count", "f0", "f1"]) + payload = plotting.prepare_shared_cluster_profile_data(result=result) + + fig, ax = plotting_matplotlib.plot_shared_cluster_profile_series_matplotlib(payload) + + assert fig is not None + assert ax is not None + assert [tick.get_text() for tick in ax.get_xticklabels()] == ["f0", "f1"] + assert [line.get_label() for line in ax.lines] == ["C0", "C1"] + assert np.isnan(ax.lines[0].get_ydata()).all() + assert np.isnan(ax.lines[1].get_ydata()).all() + + +def _make_shared_cluster_region_result() -> SharedClusterResult: + return SharedClusterResult( + model=SharedClusterModel( + mode="region_anchored", + motifs=["A,0"], + feature_names=["f0", "f1"], + preprocessing={"signal_normalization": "none"}, + estimator=object(), + cluster_labels=["C0", "C1"], + fit_metadata={"clusterer": "minibatch_kmeans", "n_clusters": 2}, + ), + assignments=pd.DataFrame(columns=["sample_id", "condition", "cluster"]), + cluster_distribution=pd.DataFrame( + columns=["sample_id", "condition", "cluster", "count", "fraction"] + ), + condition_distribution=pd.DataFrame( + columns=["condition", "cluster", "count", "fraction", "replicate_n"] + ), + distribution_change=None, + cluster_profiles=pd.DataFrame(columns=["cluster", "count", "f0", "f1"]), + region_summaries=pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "cluster": "C0", + "count": 2, + "fraction": 2 / 3, + }, + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "cluster": "C1", + "count": 1, + "fraction": 1 / 3, + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "cluster": "C0", + "count": 1, + "fraction": 1 / 4, + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "cluster": "C1", + "count": 3, + "fraction": 3 / 4, + }, + ] + ), + plot_data={}, + metadata={"mode": "region_anchored"}, + ) + + +def test_prepare_shared_cluster_region_data_returns_sample_and_condition_tables(): + result = _make_shared_cluster_region_result() + + payload = plotting.prepare_shared_cluster_region_data(result=result) + + assert set(payload) == {"region_table", "condition_region_table", "metadata"} + region_table = payload["region_table"] + condition_region_table = payload["condition_region_table"] + assert region_table.to_dict("records") == [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "cluster": "C0", + "count": 2, + "fraction": pytest.approx(2 / 3), + }, + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "cluster": "C1", + "count": 1, + "fraction": pytest.approx(1 / 3), + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "cluster": "C0", + "count": 1, + "fraction": pytest.approx(1 / 4), + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "cluster": "C1", + "count": 3, + "fraction": pytest.approx(3 / 4), + }, + ] + assert condition_region_table.to_dict("records") == [ + { + "region_id": "reg1", + "condition": "NS", + "cluster": "C0", + "count": 2, + "fraction_mean": pytest.approx(2 / 3), + "fraction_median": pytest.approx(2 / 3), + "sample_n": 1, + }, + { + "region_id": "reg1", + "condition": "NS", + "cluster": "C1", + "count": 1, + "fraction_mean": pytest.approx(1 / 3), + "fraction_median": pytest.approx(1 / 3), + "sample_n": 1, + }, + { + "region_id": "reg1", + "condition": "treated", + "cluster": "C0", + "count": 1, + "fraction_mean": pytest.approx(1 / 4), + "fraction_median": pytest.approx(1 / 4), + "sample_n": 1, + }, + { + "region_id": "reg1", + "condition": "treated", + "cluster": "C1", + "count": 3, + "fraction_mean": pytest.approx(3 / 4), + "fraction_median": pytest.approx(3 / 4), + "sample_n": 1, + }, + ] + assert payload["metadata"]["mode"] == "region_anchored" + + +def test_prepare_shared_cluster_region_data_can_disable_condition_aggregation(): + result = _make_shared_cluster_region_result() + + payload = plotting.prepare_shared_cluster_region_data( + result=result, + aggregate_conditions=False, + ) + + assert payload["condition_region_table"].empty + + +def test_prepare_shared_cluster_region_data_rejects_missing_region_summaries(): + result = _make_shared_cluster_region_result() + result.region_summaries = None + + with pytest.raises(ValueError, match="region_summaries"): + plotting.prepare_shared_cluster_region_data(result=result) + + +def test_plot_shared_cluster_region_matplotlib_defaults_to_condition_level(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_region_data( + result=_make_shared_cluster_region_result() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_region_matplotlib(payload) + + assert fig is not None + assert ax is not None + image = ax.images[0] + + assert [tick.get_text() for tick in ax.get_xticklabels()] == ["C0", "C1"] + assert [tick.get_text() for tick in ax.get_yticklabels()] == [ + "reg1 | NS", + "reg1 | treated", + ] + assert image.origin == "upper" + np.testing.assert_allclose( + np.asarray(image.get_array()), + np.array([[2 / 3, 1 / 3], [1 / 4, 3 / 4]]), + ) + + +def test_plot_shared_cluster_region_matplotlib_supports_sample_level(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_region_data( + result=_make_shared_cluster_region_result() + ) + + fig, ax = plotting_matplotlib.plot_shared_cluster_region_matplotlib( + payload, + level="sample", + ) + + assert fig is not None + assert ax is not None + image = ax.images[0] + + assert [tick.get_text() for tick in ax.get_xticklabels()] == ["C0", "C1"] + assert [tick.get_text() for tick in ax.get_yticklabels()] == [ + "reg1 | s1", + "reg1 | s2", + ] + assert image.origin == "upper" + np.testing.assert_allclose( + np.asarray(image.get_array()), + np.array([[2 / 3, 1 / 3], [1 / 4, 3 / 4]]), + ) + + +def test_plot_shared_cluster_region_matplotlib_rejects_duplicate_row_cluster_values(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_shared_cluster_region_data( + result=_make_shared_cluster_region_result() + ) + duplicate_row = payload["condition_region_table"].iloc[[0]] + payload["condition_region_table"] = pd.concat( + [payload["condition_region_table"], duplicate_row], + ignore_index=True, + ) + + with pytest.raises(ValueError, match="duplicate region occupancy values"): + plotting_matplotlib.plot_shared_cluster_region_matplotlib(payload) + + +def _make_read_cluster_region_association_table() -> pd.DataFrame: + return pd.DataFrame( + [ + { + "chrom": "chr1", + "start": 10, + "end": 20, + "strand": "+", + "cluster": "C0", + "fraction": 0.25, + "log2_enrichment": -1.0, + }, + { + "chrom": "chr1", + "start": 10, + "end": 20, + "strand": "+", + "cluster": "C1", + "fraction": 0.75, + "log2_enrichment": 1.5, + }, + { + "chrom": "chr1", + "start": 30, + "end": 40, + "strand": "-", + "cluster": "C0", + "fraction": 0.60, + "log2_enrichment": 0.25, + }, + { + "chrom": "chr1", + "start": 30, + "end": 40, + "strand": "-", + "cluster": "C1", + "fraction": 0.40, + "log2_enrichment": -0.10, + }, + ] + ) + + +def test_prepare_read_cluster_region_association_data_accepts_long_form_fraction_table(): + table = _make_read_cluster_region_association_table() + + payload = plotting.prepare_read_cluster_region_association_data(table) + + association_table = payload["association_table"] + matrix_table = payload["matrix_table"] + top_regions_table = payload["top_regions_table"] + + assert list(association_table["region_id"].drop_duplicates()) == [ + "chr1:30-40:-", + "chr1:10-20:+", + ] + assert list(matrix_table.columns) == ["region_id", "C0", "C1"] + assert list(matrix_table["region_id"]) == ["chr1:30-40:-", "chr1:10-20:+"] + np.testing.assert_allclose( + matrix_table.loc[:, ["C0", "C1"]].to_numpy(), + np.array([[0.60, 0.40], [0.25, 0.75]]), + ) + assert payload["metadata"]["region_sort"] == "cluster_fraction" + assert top_regions_table.iloc[0]["cluster"] == "C0" + assert top_regions_table.iloc[0]["region_id"] == "chr1:30-40:-" + assert top_regions_table.iloc[0]["value"] == pytest.approx(0.60) + + +def test_prepare_read_cluster_region_association_data_supports_cluster_fraction_sort(): + table = _make_read_cluster_region_association_table() + + payload = plotting.prepare_read_cluster_region_association_data( + table, + region_sort="cluster_fraction", + ) + + assert payload["metadata"]["region_sort"] == "cluster_fraction" + # Region chr1:30-40:- is dominant in C0 at 0.60; chr1:10-20:+ is dominant in C1 at 0.75. + # Cluster order is C0, C1, so C0-dominant regions appear first. + assert list(payload["matrix_table"]["region_id"]) == [ + "chr1:30-40:-", + "chr1:10-20:+", + ] + + +def test_prepare_read_cluster_region_association_data_uses_requested_log2_enrichment_values(): + table = _make_read_cluster_region_association_table() + + payload = plotting.prepare_read_cluster_region_association_data( + table, + value_mode="log2_enrichment", + top_n_regions_per_cluster=1, + ) + + matrix_table = payload["matrix_table"] + top_regions_table = payload["top_regions_table"] + + assert list(matrix_table.columns) == ["region_id", "C0", "C1"] + np.testing.assert_allclose( + matrix_table.loc[:, ["C0", "C1"]].to_numpy(), + np.array([[0.25, -0.10], [-1.0, 1.5]]), + ) + assert list(top_regions_table["cluster"]) == ["C0", "C1"] + assert list(top_regions_table["region_id"]) == ["chr1:30-40:-", "chr1:10-20:+"] + assert list(top_regions_table["value"]) == [pytest.approx(0.25), pytest.approx(1.5)] + + +def test_prepare_read_cluster_region_association_data_supports_genomic_sort(): + table = _make_read_cluster_region_association_table() + + payload = plotting.prepare_read_cluster_region_association_data( + table, + region_sort="genomic", + ) + + assert payload["metadata"]["region_sort"] == "genomic" + assert list(payload["matrix_table"]["region_id"]) == [ + "chr1:10-20:+", + "chr1:30-40:-", + ] + assert "region_axis_table" in payload + + +def test_prepare_read_cluster_region_association_data_supports_association_strength_sort(): + table = _make_read_cluster_region_association_table() + + payload = plotting.prepare_read_cluster_region_association_data( + table, + region_sort="association_strength", + association_strength_aggregate="max", + ) + + # chr1:10-20:+ has max value 0.75, chr1:30-40:- has max value 0.60 + assert list(payload["matrix_table"]["region_id"]) == [ + "chr1:10-20:+", + "chr1:30-40:-", + ] + assert payload["metadata"]["association_strength_aggregate"] == "max" + + +def test_prepare_read_cluster_region_association_data_cluster_sort_arbitrary_labels(): + table = pd.DataFrame( + { + "region_id": ["r1", "r1", "r1", "r2", "r2", "r2"], + "cluster": ["10", "alpha", 2, "10", "alpha", 2], + "fraction": [0.1, 0.8, 0.3, 0.9, 0.1, 0.4], + "count": [1, 8, 3, 9, 1, 4], + "total_reads": [10, 10, 10, 10, 10, 10], + } + ) + + natural = plotting.prepare_read_cluster_region_association_data( + table, + cluster_sort="natural", + ) + total = plotting.prepare_read_cluster_region_association_data( + table, + cluster_sort="total_association", + ) + explicit = plotting.prepare_read_cluster_region_association_data( + table, + cluster_order=["alpha"], + ) + + assert natural["metadata"]["cluster_order"] == ["10", 2, "alpha"] + assert total["metadata"]["cluster_order"] == ["10", "alpha", 2] + assert explicit["metadata"]["cluster_order"] == ["alpha", "10", 2] + assert natural["matrix_table"].columns.tolist() == ["region_id", "10", 2, "alpha"] + + +def test_plot_read_cluster_region_association_heatmap_cluster_order_override(): + table = pd.DataFrame( + { + "region_id": ["r1", "r1", "r2", "r2"], + "cluster": ["b", "a", "b", "a"], + "fraction": [0.7, 0.3, 0.2, 0.8], + "count": [7, 3, 2, 8], + "total_reads": [10, 10, 10, 10], + } + ) + payload = plotting.prepare_read_cluster_region_association_data(table) + + _fig, ax = plotting_matplotlib.plot_read_cluster_region_association_heatmap_matplotlib( + payload, + cluster_order=["a", "b"], + ) + + assert [tick.get_text() for tick in ax.get_xticklabels()] == ["a", "b"] + + +def test_prepare_read_cluster_region_association_data_supports_hierarchical_region_sort(): + table = pd.DataFrame( + { + "region_id": [ + "chr2:10-20:+", "chr2:10-20:+", + "chr1:10-20:+", "chr1:10-20:+", + "chr1:30-40:+", "chr1:30-40:+", + ], + "cluster": ["A", "B", "A", "B", "A", "B"], + "fraction": [0.9, 0.1, 0.2, 0.8, 0.7, 0.3], + "count": [9, 1, 2, 8, 7, 3], + "total_reads": [10, 10, 10, 10, 10, 10], + } + ) + + payload = plotting.prepare_read_cluster_region_association_data( + table, + region_sort=["cluster_fraction", "genomic"], + cluster_order=["A", "B"], + ) + + assert payload["metadata"]["region_sort"] == ["cluster_fraction", "genomic"] + assert payload["matrix_table"]["region_id"].tolist() == [ + "chr2:10-20:+", + "chr1:30-40:+", + "chr1:10-20:+", + ] + + +def test_plot_read_cluster_region_association_heatmap_region_sort_list_override(): + table = pd.DataFrame( + { + "region_id": ["chr2:10-20:+", "chr1:10-20:+"], + "cluster": ["A", "A"], + "fraction": [0.9, 0.2], + "count": [9, 2], + "total_reads": [10, 10], + } + ) + payload = plotting.prepare_read_cluster_region_association_data( + table, region_sort="input" + ) + + _fig, ax = plotting_matplotlib.plot_read_cluster_region_association_heatmap_matplotlib( + payload, + region_sort=["genomic"], + region_label_mode="region_id", + ) + + assert [tick.get_text() for tick in ax.get_yticklabels()] == [ + "chr1:10-20:+", + "chr2:10-20:+", + ] + + +def test_prepare_read_cluster_region_association_data_accepts_legacy_wide_region_summary(): + legacy_summary = pd.DataFrame( + [ + { + "chrom": "chr1", + "start": 10, + "end": 20, + "strand": "+", + 0: 2, + 1: 1, + "total_reads": 3, + "dominant_cluster": 0, + "dominant_fraction": 2 / 3, + "entropy": 0.6365141682948128, + }, + { + "chrom": "chr1", + "start": 30, + "end": 40, + "strand": "-", + 0: 3, + 1: 2, + "total_reads": 5, + "dominant_cluster": 0, + "dominant_fraction": 3 / 5, + "entropy": 0.6730116670092565, + }, + ] + ) + + payload = plotting.prepare_read_cluster_region_association_data( + legacy_summary, + top_n_regions_per_cluster=1, + ) + + association_table = payload["association_table"] + matrix_table = payload["matrix_table"] + top_regions_table = payload["top_regions_table"] + + assert list(association_table["region_id"].drop_duplicates()) == [ + "chr1:10-20:+", + "chr1:30-40:-", + ] + assert list(matrix_table.columns) == ["region_id", 0, 1] + np.testing.assert_allclose( + matrix_table.loc[:, [0, 1]].to_numpy(), + np.array([[2 / 3, 1 / 3], [3 / 5, 2 / 5]]), + ) + assert list(top_regions_table["cluster"]) == [0, 1] + assert top_regions_table.iloc[0]["region_id"] == "chr1:10-20:+" + assert top_regions_table.iloc[0]["value"] == pytest.approx(2 / 3) + + +def test_prepare_read_cluster_region_association_data_rejects_missing_requested_mode(): + table = _make_read_cluster_region_association_table().drop( + columns=["log2_enrichment"] + ) + + with pytest.raises(ValueError, match="log2_enrichment"): + plotting.prepare_read_cluster_region_association_data( + table, + value_mode="log2_enrichment", + ) + + +def test_plot_read_cluster_region_association_heatmap_matplotlib_returns_figure_and_axis(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_read_cluster_region_association_data( + _make_read_cluster_region_association_table() + ) + + fig, ax = ( + plotting_matplotlib.plot_read_cluster_region_association_heatmap_matplotlib( + payload + ) + ) + + assert fig is not None + assert ax is not None + assert [tick.get_text() for tick in ax.get_xticklabels()] == ["C0", "C1"] + assert [tick.get_text() for tick in ax.get_yticklabels()] == [ + "chr1:30-40:-", + "chr1:10-20:+", + ] + np.testing.assert_allclose( + np.asarray(ax.images[0].get_array()), + np.array([[0.60, 0.40], [0.25, 0.75]]), + ) + + +def test_plot_read_cluster_region_association_heatmap_matplotlib_accepts_region_sort_override(): + from dimelo import plotting_matplotlib + + table = pd.DataFrame( + [ + {"region_id": "chr1:100-110:+", "cluster": "C0", "fraction": 0.90}, + {"region_id": "chr1:100-110:+", "cluster": "C1", "fraction": 0.10}, + {"region_id": "chr1:10-20:+", "cluster": "C0", "fraction": 0.80}, + {"region_id": "chr1:10-20:+", "cluster": "C1", "fraction": 0.20}, + ] + ) + payload = plotting.prepare_read_cluster_region_association_data( + table, region_sort="genomic" + ) + axis_table = payload["region_axis_table"].copy() + axis_table["source_label"] = ["late", "early"] + payload["region_axis_table"] = axis_table + + fig, ax = ( + plotting_matplotlib.plot_read_cluster_region_association_heatmap_matplotlib( + payload, + region_sort="association_strength", + row_annotation_column="source_label", + ) + ) + + assert fig is not None + assert ax is not None + # association_strength sort should place chr1:100-110:+ before chr1:10-20:+ + ytick_text = [tick.get_text() for tick in ax.get_yticklabels()] + assert ytick_text[0].startswith("chr1:100-110:+") + + +def test_plot_read_cluster_region_association_heatmap_title_reflects_effective_flags(): + table = pd.DataFrame( + { + "region_id": ["chr1:10-20:+", "chr1:10-20:+", "chr2:10-20:+", "chr2:10-20:+"], + "cluster": ["b", "a", "b", "a"], + "fraction": [0.7, 0.3, 0.2, 0.8], + "count": [7, 3, 2, 8], + "total_reads": [10, 10, 10, 10], + } + ) + payload = plotting.prepare_read_cluster_region_association_data( + table, + region_sort="input", + cluster_sort="input", + ) + + _fig, ax = plotting_matplotlib.plot_read_cluster_region_association_heatmap_matplotlib( + payload, + region_sort=["association_strength", "genomic"], + association_strength_aggregate="mean", + cluster_sort="natural", + row_annotation_column="source_label", + region_label_mode="chromosome", + group_region_labels=True, + ) + + title = ax.get_title() + assert "Read-cluster association (fraction)" in title + assert "regions: association_strength > genomic" in title + assert "clusters: natural" in title + assert "strength: mean" in title + assert "annotated by source_label" in title + assert "grouped labels" in title + + +def test_plot_read_cluster_region_association_heatmap_matplotlib_supports_row_annotations(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_read_cluster_region_association_data( + _make_read_cluster_region_association_table(), + region_sort="genomic", + ) + axis_table = payload["region_axis_table"].copy() + axis_table["source_label"] = ["on_target", "off_target"] + payload["region_axis_table"] = axis_table + + fig, ax = ( + plotting_matplotlib.plot_read_cluster_region_association_heatmap_matplotlib( + payload, + row_annotation_column="source_label", + row_annotation_title="Source bed", + row_annotation_palette={"on_target": "#D95F02", "off_target": "#1B9E77"}, + ) + ) + + assert fig is not None + assert ax is not None + assert ax.get_ylabel() == "" + ytick_text = [tick.get_text() for tick in ax.get_yticklabels()] + assert "on_target" in ytick_text[0] + assert "off_target" in ytick_text[1] + main_pos = ax.get_position() + left_side_axes = [ + axis + for axis in fig.axes + if axis is not ax and axis.get_position().x1 <= main_pos.x0 + ] + assert left_side_axes + # The source-bed strip should live fully to the left of the heatmap. + assert max(axis.get_position().x1 for axis in left_side_axes) <= main_pos.x0 + fig.canvas.draw() + renderer = fig.canvas.get_renderer() + tick_left = min( + tick.get_window_extent(renderer).transformed(fig.transFigure.inverted()).x0 + for tick in ax.get_yticklabels() + if tick.get_text() + ) + assert max(axis.get_position().x1 for axis in left_side_axes) < tick_left - 0.01 + + +def test_plot_read_cluster_region_association_heatmap_matplotlib_supports_multiple_row_annotations(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_read_cluster_region_association_data( + _make_read_cluster_region_association_table(), + region_sort="genomic", + ) + axis_table = payload["region_axis_table"].copy() + axis_table["source_label"] = ["on_target", "off_target"] + axis_table["strand_group"] = ["plus", "minus"] + payload["region_axis_table"] = axis_table + + fig, ax = ( + plotting_matplotlib.plot_read_cluster_region_association_heatmap_matplotlib( + payload, + row_annotation_columns=["source_label", "strand_group"], + row_annotation_titles={ + "source_label": "Source bed", + "strand_group": "Strand", + }, + row_annotation_palettes={ + "source_label": { + "on_target": "#D95F02", + "off_target": "#1B9E77", + }, + "strand_group": {"plus": "#4C78A8", "minus": "#F58518"}, + }, + ) + ) + + assert fig is not None + assert ax is not None + assert "annotated by source_label, strand_group" in ax.get_title() + main_pos = ax.get_position() + left_side_axes = [ + axis + for axis in fig.axes + if axis is not ax and axis.get_position().x1 <= main_pos.x0 + ] + assert len(left_side_axes) == 2 + assert sorted(axis.get_xlabel() for axis in left_side_axes) == [ + "Source bed", + "Strand", + ] + assert max(axis.get_position().x1 for axis in left_side_axes) <= main_pos.x0 + fig.canvas.draw() + legend_x0 = [ + legend.get_window_extent(fig.canvas.get_renderer()) + .transformed(fig.transFigure.inverted()) + .x0 + for legend in fig.legends + ] + assert len(legend_x0) == 2 + assert max(legend_x0) - min(legend_x0) < 0.01 + + +def test_plot_read_cluster_region_association_heatmap_preserves_annotations_with_cluster_sort_override(): + from dimelo import plotting_matplotlib + + table = pd.DataFrame( + { + "region_id": ["r1", "r1", "r2", "r2"], + "cluster": ["b", "a", "b", "a"], + "fraction": [0.7, 0.3, 0.2, 0.8], + "count": [7, 3, 2, 8], + "total_reads": [10, 10, 10, 10], + } + ) + payload = plotting.prepare_read_cluster_region_association_data(table) + axis_table = payload["region_axis_table"].copy() + axis_table["source_label"] = ["on_target", "off_target"] + axis_table["strand_group"] = ["plus", "minus"] + payload["region_axis_table"] = axis_table + + fig, ax = ( + plotting_matplotlib.plot_read_cluster_region_association_heatmap_matplotlib( + payload, + cluster_sort="natural", + row_annotation_columns=["source_label", "strand_group"], + ) + ) + + main_pos = ax.get_position() + left_side_axes = [ + axis + for axis in fig.axes + if axis is not ax and axis.get_position().x1 <= main_pos.x0 + ] + assert len(left_side_axes) == 2 + + +def test_plot_read_cluster_region_association_heatmap_matplotlib_grouped_region_labels(): + from dimelo import plotting_matplotlib + + table = pd.DataFrame( + [ + {"region_id": "chr1:10-20:+", "cluster": 0, "fraction": 0.20}, + {"region_id": "chr1:10-20:+", "cluster": 1, "fraction": 0.80}, + {"region_id": "chr1:30-40:+", "cluster": 0, "fraction": 0.25}, + {"region_id": "chr1:30-40:+", "cluster": 1, "fraction": 0.75}, + {"region_id": "chr2:10-20:+", "cluster": 0, "fraction": 0.70}, + {"region_id": "chr2:10-20:+", "cluster": 1, "fraction": 0.30}, + {"region_id": "chr2:30-40:+", "cluster": 0, "fraction": 0.65}, + {"region_id": "chr2:30-40:+", "cluster": 1, "fraction": 0.35}, + ] + ) + payload = plotting.prepare_read_cluster_region_association_data( + table, region_sort="genomic" + ) + axis_table = payload["region_axis_table"].copy() + axis_table["source_label"] = ["on_target", "on_target", "off_target", "off_target"] + payload["region_axis_table"] = axis_table + + fig, ax = ( + plotting_matplotlib.plot_read_cluster_region_association_heatmap_matplotlib( + payload, + region_label_mode="genomic", + row_annotation_column="source_label", + group_region_labels=True, + ) + ) + + assert fig is not None + assert ax is not None + ylabels = [tick.get_text() for tick in ax.get_yticklabels()] + assert "on_target | chr1" in ylabels + assert "off_target | chr2" in ylabels + + +def _minimal_region_contrast_result() -> RegionContrastResult: + regions = pd.DataFrame( + [ + { + "region_id": "chr1:90-110,+", + "condition": "NS", + "fraction": 0.20, + "rank": 2, + }, + { + "region_id": "chr1:90-110,+", + "condition": "15min", + "fraction": 0.55, + "rank": 2, + }, + { + "region_id": "chr1:190-210,-", + "condition": "NS", + "fraction": 0.30, + "rank": 1, + }, + { + "region_id": "chr1:190-210,-", + "condition": "15min", + "fraction": 0.70, + "rank": 1, + }, + ] + ) + summary = pd.DataFrame( + [ + { + "region_id": "chr1:90-110,+", + "fraction": 0.55, + "reference_fraction": 0.20, + "delta_fraction": 0.35, + "rank": 2, + }, + { + "region_id": "chr1:190-210,-", + "fraction": 0.70, + "reference_fraction": 0.30, + "delta_fraction": 0.40, + "rank": 1, + }, + ] + ) + return RegionContrastResult( + regions=regions, + summary=summary, + contrast=ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + reference_condition="NS", + ), + metadata={ + "analysis_unit": "ensemble_region", + "representation": "modified_fraction", + "signal_source": "pileup_counts", + "test": "effect_size_only", + }, + plot_data={}, + ) + + +def _group_vs_group_region_contrast_result() -> RegionContrastResult: + summary = pd.DataFrame( + [ + { + "region_id": "chr1:90-110,+", + "fraction": 0.60, + "reference_fraction": 0.10, + "delta_fraction": 0.50, + "rank": 1, + } + ] + ) + return RegionContrastResult( + regions=pd.DataFrame( + [ + { + "region_id": "chr1:90-110,+", + "condition": "NS", + "fraction": 0.10, + "rank": 1, + }, + { + "region_id": "chr1:90-110,+", + "condition": "15min", + "fraction": 0.50, + "rank": 1, + }, + { + "region_id": "chr1:90-110,+", + "condition": "30min", + "fraction": 0.70, + "rank": 1, + }, + ] + ), + summary=summary, + contrast=ContrastSpec( + mode="group_vs_group", + numerator=["15min", "30min"], + denominator=["NS"], + reference_condition="NS", + ), + metadata={ + "analysis_unit": "ensemble_region", + "representation": "modified_fraction", + "signal_source": "pileup_counts", + "test": "effect_size_only", + }, + plot_data={}, + ) + + +def _region_contrast_plot_setup( + position_rows: list[dict[str, object]], +) -> tuple[ + RegionContrastResult, + pd.DataFrame, + plotting.AxisSpec, + plotting.AggregationSpec, +]: + return ( + _minimal_region_contrast_result(), + pd.DataFrame(position_rows), + plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=20, + downstream_bp=20, + ), + plotting.AggregationSpec(), + ) + + +def _region_contrast_position_rows( + *, include_grouping_key: bool = True +) -> list[dict[str, object]]: + base_rows = [ + { + "region_id": "chr1:90-110,+", + "condition": "NS", + "position": 95, + "anchor": 100, + "value": 0.1, + "region_strand": "+", + }, + { + "region_id": "chr1:90-110,+", + "condition": "15min", + "position": 95, + "anchor": 100, + "value": 0.6, + "region_strand": "+", + }, + { + "region_id": "chr1:90-110,+", + "condition": "NS", + "position": 105, + "anchor": 100, + "value": 0.2, + "region_strand": "+", + }, + { + "region_id": "chr1:90-110,+", + "condition": "15min", + "position": 105, + "anchor": 100, + "value": 0.7, + "region_strand": "+", + }, + { + "region_id": "chr1:190-210,-", + "condition": "NS", + "position": 195, + "anchor": 200, + "value": 0.2, + "region_strand": "-", + }, + { + "region_id": "chr1:190-210,-", + "condition": "15min", + "position": 195, + "anchor": 200, + "value": 0.8, + "region_strand": "-", + }, + { + "region_id": "chr1:190-210,-", + "condition": "NS", + "position": 205, + "anchor": 200, + "value": 0.3, + "region_strand": "-", + }, + { + "region_id": "chr1:190-210,-", + "condition": "15min", + "position": 205, + "anchor": 200, + "value": 0.9, + "region_strand": "-", + }, + ] + if include_grouping_key: + return base_rows + return [ + {key: value for key, value in row.items() if key != "condition"} + for row in base_rows[:1] + ] + + +def _make_region_discovery_result() -> RegionDiscoveryResult: + windows = pd.DataFrame( + [ + { + "window_id": "chr1:0-100:+", + "chromosome": "chr1", + "start": 0, + "end": 100, + "strand": "+", + "score_value": 0.2, + "rank": 3, + }, + { + "window_id": "chr1:100-200:+", + "chromosome": "chr1", + "start": 100, + "end": 200, + "strand": "+", + "score_value": 0.9, + "rank": 1, + }, + { + "window_id": "chr2:0-100:+", + "chromosome": "chr2", + "start": 0, + "end": 100, + "strand": "+", + "score_value": 0.5, + "rank": 2, + }, + ] + ) + hits = windows.loc[windows["rank"] <= 2].copy() + return RegionDiscoveryResult( + windows=windows, + hits=hits, + contrast=None, + plot_data={ + "window_score_table": windows.copy(), + "top_hits_table": hits.copy(), + }, + metadata={ + "score": "effect_size_only", + "contrast_mode": "pairwise", + "merge_hits": False, + }, + ) + + +def _make_global_analysis_result() -> GlobalAnalysisResult: + summary = pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "modified_count": 10, + "valid_count": 20, + "global_fraction": 0.5, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "modified_count": 16, + "valid_count": 20, + "global_fraction": 0.8, + }, + { + "sample_id": "s3", + "condition": "treated", + "replicate": 2, + "motif": "A,0", + "modified_count": 12, + "valid_count": 20, + "global_fraction": 0.6, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "CG,0", + "modified_count": 4, + "valid_count": 20, + "global_fraction": 0.2, + }, + ] + ) + normalization_factors = pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "global_fraction": 0.5, + "reference_fraction": 0.6333333333, + "global_offset": -0.1333333333, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "global_fraction": 0.8, + "reference_fraction": 0.6333333333, + "global_offset": 0.1666666667, + }, + { + "sample_id": "s3", + "condition": "treated", + "replicate": 2, + "motif": "A,0", + "global_fraction": 0.6, + "reference_fraction": 0.6333333333, + "global_offset": -0.0333333333, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "CG,0", + "global_fraction": 0.2, + "reference_fraction": 0.2, + "global_offset": 0.0, + }, + ] + ) + windows = pd.DataFrame( + columns=[ + "sample_id", + "condition", + "replicate", + "motif", + "window_id", + "chromosome", + "start", + "end", + "strand", + "modified_count", + "valid_count", + "window_fraction", + ] + ) + return GlobalAnalysisResult( + summary=summary, + windows=windows, + normalization_factors=normalization_factors, + plot_data={ + "global_fraction_bar": summary.copy(), + "window_fraction_table": windows.copy(), + }, + metadata={"window_size": 100000, "step_size": 50000}, + ) + + +def _make_global_window_result() -> GlobalAnalysisResult: + result = _make_global_analysis_result() + result.windows = pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-100", + "chromosome": "chr1", + "start": 0, + "end": 100, + "strand": ".", + "modified_count": 5, + "valid_count": 10, + "window_fraction": 0.5, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-100", + "chromosome": "chr1", + "start": 0, + "end": 100, + "strand": ".", + "modified_count": 8, + "valid_count": 10, + "window_fraction": 0.8, + }, + { + "sample_id": "s3", + "condition": "treated", + "replicate": 2, + "motif": "A,0", + "window_id": "chr1:0-100", + "chromosome": "chr1", + "start": 0, + "end": 100, + "strand": ".", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "s3", + "condition": "treated", + "replicate": 2, + "motif": "A,0", + "window_id": "chr2:0-100", + "chromosome": "chr2", + "start": 0, + "end": 100, + "strand": ".", + "modified_count": 6, + "valid_count": 10, + "window_fraction": 0.6, + }, + ] + ) + result.plot_data["window_fraction_table"] = result.windows.copy() + return result + + +def test_prepare_region_discovery_scan_data_returns_expected_tables(): + result = _make_region_discovery_result() + + payload = plotting.prepare_region_discovery_scan_data(result=result) + + assert set(payload) == {"scan_table", "hit_table", "metadata"} + assert list(payload["scan_table"]["contig"]) == ["chr1", "chr1", "chr2"] + assert list(payload["hit_table"]["rank"]) == [1, 2] + assert payload["scan_table"]["is_hit"].tolist() == [False, True, True] + assert payload["metadata"]["contig_order"] == ["chr1", "chr2"] + assert payload["metadata"]["score_column"] == "score_value" + + +def test_prepare_region_discovery_scan_data_filters_contigs_in_requested_order(): + result = _make_region_discovery_result() + + payload = plotting.prepare_region_discovery_scan_data( + result=result, + contigs=["chr2"], + ) + + assert payload["metadata"]["contig_order"] == ["chr2"] + assert payload["scan_table"]["contig"].tolist() == ["chr2"] + assert payload["hit_table"]["contig"].tolist() == ["chr2"] + assert "chr1" not in payload["scan_table"]["contig"].tolist() + assert "chr1" not in payload["hit_table"]["contig"].tolist() + + +def test_prepare_region_discovery_scan_data_limits_hit_overlay(): + result = _make_region_discovery_result() + + payload = plotting.prepare_region_discovery_scan_data( + result=result, + top_n_hits=1, + ) + + assert payload["hit_table"]["rank"].tolist() == [1] + assert payload["scan_table"]["is_hit"].tolist() == [False, True, False] + + +def test_prepare_region_discovery_scan_data_rejects_negative_top_n_hits(): + result = _make_region_discovery_result() + + with pytest.raises(ValueError, match="top_n_hits must be non-negative"): + plotting.prepare_region_discovery_scan_data( + result=result, + top_n_hits=-1, + ) + + +def test_prepare_region_discovery_scan_data_rejects_hits_missing_from_filtered_windows(): + result = _make_region_discovery_result() + inconsistent_hit = pd.DataFrame( + [ + { + "window_id": "chr2:100-200:+", + "chromosome": "chr2", + "start": 100, + "end": 200, + "strand": "+", + "score_value": 0.8, + "rank": 1, + } + ] + ) + inconsistent_result = RegionDiscoveryResult( + windows=result.windows.copy(), + hits=pd.concat([result.hits, inconsistent_hit], ignore_index=True), + contrast=result.contrast, + plot_data=result.plot_data, + metadata=result.metadata, + figures=result.figures, + ) + + with pytest.raises( + ValueError, + match="result.hits contains window_id values not present in the filtered windows table", + ): + plotting.prepare_region_discovery_scan_data( + result=inconsistent_result, + contigs=["chr2"], + ) + + +def test_prepare_region_discovery_hit_context_data_uses_top_ranked_hits(): + result = _make_region_discovery_result() + + payload = plotting.prepare_region_discovery_hit_context_data( + result=result, + top_n=1, + padding_windows=1, + ) + + assert set(payload) == {"context_table", "selected_hits", "metadata"} + assert payload["selected_hits"]["window_id"].tolist() == ["chr1:100-200:+"] + assert payload["selected_hits"]["rank"].tolist() == [1] + assert payload["context_table"]["selected_hit_id"].tolist() == [ + "chr1:100-200:+", + "chr1:100-200:+", + ] + assert payload["context_table"]["window_id"].tolist() == [ + "chr1:0-100:+", + "chr1:100-200:+", + ] + assert payload["context_table"]["selected_hit_rank"].nunique() == 1 + assert payload["context_table"]["selected_hit_rank"].iloc[0] == 1 + assert payload["context_table"]["is_selected_hit"].sum() == 1 + + +def test_prepare_region_discovery_hit_context_data_adds_relative_window_offsets(): + result = _make_region_discovery_result() + + payload = plotting.prepare_region_discovery_hit_context_data( + result=result, + top_n=1, + padding_windows=1, + ) + + assert payload["context_table"]["window_id"].tolist() == [ + "chr1:0-100:+", + "chr1:100-200:+", + ] + assert payload["context_table"]["relative_window_offset"].tolist() == [-1, 0] + + +def test_prepare_region_discovery_hit_context_data_returns_empty_payload_for_no_hits(): + base_result = _make_region_discovery_result() + result = RegionDiscoveryResult( + windows=base_result.windows.copy(), + hits=pd.DataFrame(columns=base_result.hits.columns), + contrast=None, + plot_data={}, + metadata={ + "score": "effect_size_only", + "contrast_mode": "pairwise", + "merge_hits": False, + }, + ) + + payload = plotting.prepare_region_discovery_hit_context_data(result=result) + + assert payload["context_table"].empty + assert payload["selected_hits"].empty + assert payload["metadata"]["selection_mode"] == "top_n" + + +def test_plot_region_discovery_scan_matplotlib_returns_figure_and_axes(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_region_discovery_scan_data( + result=_make_region_discovery_result() + ) + + fig, axes = plotting_matplotlib.plot_region_discovery_scan_matplotlib(payload) + + assert fig is not None + assert axes is not None + + +def test_plot_region_discovery_hit_context_matplotlib_returns_figure_and_axes(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_region_discovery_hit_context_data( + result=_make_region_discovery_result(), + top_n=2, + ) + + fig, axes = plotting_matplotlib.plot_region_discovery_hit_context_matplotlib( + payload + ) + + assert fig is not None + assert axes is not None + + +def test_prepare_global_analysis_summary_data_returns_expected_tables(): + result = _make_global_analysis_result() + + payload = plotting.prepare_global_analysis_summary_data(result=result) + + assert set(payload) == { + "sample_summary", + "condition_summary", + "normalization_table", + "metadata", + } + assert payload["sample_summary"]["sample_id"].tolist() == ["s1", "s2", "s3", "s1"] + assert payload["normalization_table"]["sample_id"].tolist() == [ + "s1", + "s2", + "s3", + "s1", + ] + assert payload["condition_summary"][["condition", "motif"]].apply( + tuple, axis=1 + ).tolist() == [("NS", "A,0"), ("treated", "A,0"), ("NS", "CG,0")] + assert payload["condition_summary"]["sample_n"].tolist() == [1, 2, 1] + assert payload["metadata"]["motifs"] == ["A,0", "CG,0"] + + +def test_prepare_global_analysis_summary_data_computes_condition_means(): + result = _make_global_analysis_result() + + payload = plotting.prepare_global_analysis_summary_data(result=result) + condition_summary = payload["condition_summary"].set_index(["condition", "motif"]) + + assert condition_summary.loc[ + ("NS", "A,0"), "global_fraction_mean" + ] == pytest.approx(0.5) + assert condition_summary.loc[ + ("treated", "A,0"), "global_fraction_mean" + ] == pytest.approx(0.7) + assert condition_summary.loc[ + ("treated", "A,0"), "global_fraction_median" + ] == pytest.approx(0.7) + + +def test_prepare_global_analysis_summary_data_filters_motifs(): + result = _make_global_analysis_result() + + payload = plotting.prepare_global_analysis_summary_data( + result=result, + motifs=["A,0"], + ) + + assert payload["sample_summary"]["motif"].unique().tolist() == ["A,0"] + assert payload["normalization_table"]["motif"].unique().tolist() == ["A,0"] + assert "CG,0" not in payload["sample_summary"]["motif"].tolist() + assert "CG,0" not in payload["normalization_table"]["motif"].tolist() + + +def test_prepare_global_analysis_summary_data_skips_condition_aggregation_when_disabled(): + result = _make_global_analysis_result() + + payload = plotting.prepare_global_analysis_summary_data( + result=result, + aggregate_conditions=False, + ) + + assert payload["condition_summary"].empty + assert payload["metadata"]["aggregate_conditions"] is False + + +def test_plot_global_analysis_summary_matplotlib_returns_figure_and_axis(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_global_analysis_summary_data( + result=_make_global_analysis_result() + ) + + fig, ax = plotting_matplotlib.plot_global_analysis_summary_matplotlib(payload) + + assert fig is not None + assert ax is not None + + +def test_prepare_global_analysis_window_data_returns_expected_tables(): + result = _make_global_window_result() + + payload = plotting.prepare_global_analysis_window_data(result=result) + + assert set(payload) == {"window_table", "condition_window_table", "metadata"} + assert payload["window_table"]["contig"].tolist() == [ + "chr1", + "chr1", + "chr1", + "chr2", + ] + assert payload["window_table"]["window_midpoint"].tolist() == [ + 50.0, + 50.0, + 50.0, + 50.0, + ] + assert payload["condition_window_table"].empty + assert payload["metadata"]["contig_order"] == ["chr1", "chr2"] + + +def test_plot_global_analysis_window_matplotlib_returns_figure_and_axes(): + from dimelo import plotting_matplotlib + + payload = plotting.prepare_global_analysis_window_data( + result=_make_global_window_result() + ) + + fig, axes = plotting_matplotlib.plot_global_analysis_window_matplotlib(payload) + + assert fig is not None + assert axes is not None + + +def test_prepare_global_analysis_window_data_filters_contigs_in_requested_order(): + result = _make_global_window_result() + + payload = plotting.prepare_global_analysis_window_data( + result=result, + contigs=["chr2"], + ) + + assert payload["metadata"]["contig_order"] == ["chr2"] + assert payload["window_table"]["contig"].tolist() == ["chr2"] + + +def test_prepare_global_analysis_window_data_aggregates_conditions(): + result = _make_global_window_result() + + payload = plotting.prepare_global_analysis_window_data( + result=result, + aggregate_conditions=True, + ) + + condition_rows = payload["condition_window_table"] + ns_row = condition_rows.loc[condition_rows["condition"] == "NS"].iloc[0] + treated_chr1 = condition_rows.loc[ + (condition_rows["condition"] == "treated") + & (condition_rows["contig"] == "chr1") + ].iloc[0] + + assert ns_row["window_fraction_mean"] == pytest.approx(0.5) + assert treated_chr1["window_fraction_mean"] == pytest.approx(0.6) + assert treated_chr1["sample_n"] == 2 + + +def test_prepare_global_analysis_window_data_aggregates_per_sample_windows_across_replicates_with_chrom_alias(): + result = _make_global_window_result() + duplicate_sample_window = pd.DataFrame( + [ + { + "sample_id": "s2", + "condition": "treated", + "replicate": 2, + "motif": "A,0", + "window_id": "chr1:0-100", + "chromosome": "chr1", + "start": 0, + "end": 100, + "strand": ".", + "modified_count": 2, + "valid_count": 10, + "window_fraction": 0.2, + } + ] + ) + result.windows = pd.concat( + [result.windows, duplicate_sample_window], ignore_index=True + ).rename(columns={"chromosome": "chrom"}) + result.plot_data["window_fraction_table"] = result.windows.copy() + + payload = plotting.prepare_global_analysis_window_data( + result=result, + aggregate_conditions=True, + ) + + treated_chr1 = ( + payload["condition_window_table"] + .loc[ + (payload["condition_window_table"]["condition"] == "treated") + & (payload["condition_window_table"]["contig"] == "chr1") + ] + .iloc[0] + ) + + assert treated_chr1["window_fraction_mean"] == pytest.approx(0.45) + assert treated_chr1["window_fraction_median"] == pytest.approx(0.45) + assert treated_chr1["sample_n"] == 2 + + +def test_prepare_region_discovery_hit_context_data_rejects_padding_bp(): + result = _make_region_discovery_result() + + with pytest.raises(ValueError, match="padding_bp is not supported"): + plotting.prepare_region_discovery_hit_context_data( + result=result, + padding_bp=100, + ) + + +def test_prepare_region_discovery_hit_context_data_rejects_selected_hits_missing_from_windows(): + result = _make_region_discovery_result() + inconsistent_hit = pd.DataFrame( + [ + { + "window_id": "chr1:200-300:+", + "chromosome": "chr1", + "start": 200, + "end": 300, + "strand": "+", + "score_value": 0.7, + "rank": 0, + } + ] + ) + inconsistent_result = RegionDiscoveryResult( + windows=result.windows.copy(), + hits=pd.concat([result.hits, inconsistent_hit], ignore_index=True), + contrast=result.contrast, + plot_data=result.plot_data, + metadata=result.metadata, + figures=result.figures, + ) + + with pytest.raises( + ValueError, + match="selected hits contain window_id values not present in result.windows", + ): + plotting.prepare_region_discovery_hit_context_data( + result=inconsistent_result, + top_n=1, + ) + + +def test_prepare_region_contrast_profile_data_returns_all_value_modes(): + result, position_table, axis, aggregation = _region_contrast_plot_setup( + _region_contrast_position_rows() + ) + + payload = plotting.prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + + plot_table = payload["plot_table"] + assert len(plot_table) == 12 + assert set(payload["plot_table"]["value_mode"]) == { + "numerator", + "denominator", + "delta", + } + assert plot_table.groupby( + ["region_id", "position", "value_mode"] + ).size().to_dict() == { + ("chr1:90-110,+", 95, "numerator"): 1, + ("chr1:90-110,+", 95, "denominator"): 1, + ("chr1:90-110,+", 95, "delta"): 1, + ("chr1:90-110,+", 105, "numerator"): 1, + ("chr1:90-110,+", 105, "denominator"): 1, + ("chr1:90-110,+", 105, "delta"): 1, + ("chr1:190-210,-", 195, "numerator"): 1, + ("chr1:190-210,-", 195, "denominator"): 1, + ("chr1:190-210,-", 195, "delta"): 1, + ("chr1:190-210,-", 205, "numerator"): 1, + ("chr1:190-210,-", 205, "denominator"): 1, + ("chr1:190-210,-", 205, "delta"): 1, + } + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:90-110,+") + & (plot_table["position"] == 95) + & (plot_table["value_mode"] == "numerator"), + "value", + ].iloc[0] == pytest.approx(0.6) + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:90-110,+") + & (plot_table["position"] == 95) + & (plot_table["value_mode"] == "denominator"), + "value", + ].iloc[0] == pytest.approx(0.1) + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:90-110,+") + & (plot_table["position"] == 95) + & (plot_table["value_mode"] == "delta"), + "value", + ].iloc[0] == pytest.approx(0.5) + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:90-110,+") + & (plot_table["position"] == 105) + & (plot_table["value_mode"] == "numerator"), + "value", + ].iloc[0] == pytest.approx(0.7) + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:90-110,+") + & (plot_table["position"] == 105) + & (plot_table["value_mode"] == "denominator"), + "value", + ].iloc[0] == pytest.approx(0.2) + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:90-110,+") + & (plot_table["position"] == 105) + & (plot_table["value_mode"] == "delta"), + "value", + ].iloc[0] == pytest.approx(0.5) + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:190-210,-") + & (plot_table["position"] == 195) + & (plot_table["value_mode"] == "numerator"), + "value", + ].iloc[0] == pytest.approx(0.8) + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:190-210,-") + & (plot_table["position"] == 195) + & (plot_table["value_mode"] == "denominator"), + "value", + ].iloc[0] == pytest.approx(0.2) + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:190-210,-") + & (plot_table["position"] == 195) + & (plot_table["value_mode"] == "delta"), + "value", + ].iloc[0] == pytest.approx(0.6) + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:190-210,-") + & (plot_table["position"] == 205) + & (plot_table["value_mode"] == "numerator"), + "value", + ].iloc[0] == pytest.approx(0.9) + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:190-210,-") + & (plot_table["position"] == 205) + & (plot_table["value_mode"] == "denominator"), + "value", + ].iloc[0] == pytest.approx(0.3) + assert plot_table.loc[ + (plot_table["region_id"] == "chr1:190-210,-") + & (plot_table["position"] == 205) + & (plot_table["value_mode"] == "delta"), + "value", + ].iloc[0] == pytest.approx(0.6) + assert payload["metadata"]["plot_family"] == "region_contrast_profile" + + +def test_plot_region_contrast_profile_matplotlib_defaults_to_delta_and_honors_ax_title(): + from matplotlib import pyplot as plt + + from dimelo import plotting_matplotlib + + result, position_table, axis, aggregation = _region_contrast_plot_setup( + _region_contrast_position_rows() + ) + + payload = plotting.prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + + x_column = "plot_x" if "plot_x" in payload["plot_table"].columns else "position" + expected = ( + payload["plot_table"] + .loc[lambda table: table["value_mode"] == "delta", [x_column, "value"]] + .groupby(x_column, as_index=False, sort=True) + .mean(numeric_only=True) + ) + + fig, provided_ax = plt.subplots() + fig, ax = plotting_matplotlib.plot_region_contrast_profile_matplotlib( + payload, + ax=provided_ax, + title="Custom region contrast profile", + ) + + assert fig is provided_ax.figure + assert ax is provided_ax + assert ax.get_title() == "Custom region contrast profile" + assert len(ax.lines) == 1 + np.testing.assert_allclose( + np.asarray(ax.lines[0].get_xdata()), expected[x_column].to_numpy() + ) + np.testing.assert_allclose( + np.asarray(ax.lines[0].get_ydata()), expected["value"].to_numpy() + ) + plt.close(fig) + + +def test_prepare_region_contrast_profile_data_collapses_same_coordinate_labels_within_each_side(): + result = _group_vs_group_region_contrast_result() + position_table = pd.DataFrame( + [ + { + "region_id": "chr1:90-110,+", + "condition": "NS", + "position": 95, + "anchor": 100, + "value": 0.1, + "region_strand": "+", + }, + { + "region_id": "chr1:90-110,+", + "condition": "15min", + "position": 95, + "anchor": 100, + "value": 0.5, + "region_strand": "+", + }, + { + "region_id": "chr1:90-110,+", + "condition": "30min", + "position": 95, + "anchor": 100, + "value": 0.7, + "region_strand": "+", + }, + { + "region_id": "chr1:90-110,+", + "condition": "NS", + "position": 105, + "anchor": 100, + "value": 0.2, + "region_strand": "+", + }, + { + "region_id": "chr1:90-110,+", + "condition": "15min", + "position": 105, + "anchor": 100, + "value": 0.6, + "region_strand": "+", + }, + { + "region_id": "chr1:90-110,+", + "condition": "30min", + "position": 105, + "anchor": 100, + "value": 0.8, + "region_strand": "+", + }, + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=20, + downstream_bp=20, + ) + aggregation = plotting.AggregationSpec() + + payload = plotting.prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + + plot_table = payload["plot_table"] + assert len(plot_table) == 6 + assert plot_table.groupby(["position", "value_mode"]).size().to_dict() == { + (95, "numerator"): 1, + (95, "denominator"): 1, + (95, "delta"): 1, + (105, "numerator"): 1, + (105, "denominator"): 1, + (105, "delta"): 1, + } + assert plot_table.loc[ + (plot_table["position"] == 95) & (plot_table["value_mode"] == "numerator"), + "value", + ].iloc[0] == pytest.approx(0.6) + assert plot_table.loc[ + (plot_table["position"] == 95) & (plot_table["value_mode"] == "denominator"), + "value", + ].iloc[0] == pytest.approx(0.1) + assert plot_table.loc[ + (plot_table["position"] == 95) & (plot_table["value_mode"] == "delta"), + "value", + ].iloc[0] == pytest.approx(0.5) + assert plot_table.loc[ + (plot_table["position"] == 105) & (plot_table["value_mode"] == "numerator"), + "value", + ].iloc[0] == pytest.approx(0.7) + assert plot_table.loc[ + (plot_table["position"] == 105) & (plot_table["value_mode"] == "denominator"), + "value", + ].iloc[0] == pytest.approx(0.2) + assert plot_table.loc[ + (plot_table["position"] == 105) & (plot_table["value_mode"] == "delta"), + "value", + ].iloc[0] == pytest.approx(0.5) + + +def test_prepare_region_contrast_heatmap_data_orders_rows_by_rank(): + result, position_table, axis, aggregation = _region_contrast_plot_setup( + _region_contrast_position_rows() + ) + result.summary = pd.concat( + [ + result.summary, + pd.DataFrame( + [ + { + "region_id": "chr1:290-310,+", + "fraction": 0.15, + "reference_fraction": 0.05, + "delta_fraction": 0.10, + "rank": 3, + }, + { + "region_id": "chr1:290-310,+", + "fraction": 0.16, + "reference_fraction": 0.05, + "delta_fraction": 0.11, + "rank": 8, + }, + ] + ), + ], + ignore_index=True, + ) + + payload = plotting.prepare_region_contrast_heatmap_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + + rank_rows = ( + payload["plot_table"].loc[:, ["region_id", "row_order"]].drop_duplicates() + ) + assert list(rank_rows.sort_values("row_order")["region_id"]) == [ + "chr1:190-210,-", + "chr1:90-110,+", + ] + assert list(payload["summary_table"]["region_id"]) == [ + "chr1:190-210,-", + "chr1:90-110,+", + ] + assert payload["metadata"]["plot_family"] == "region_contrast_heatmap" + + +def test_plot_region_contrast_heatmap_matplotlib_defaults_to_delta_and_honors_ax_title(): + from matplotlib import pyplot as plt + + from dimelo import plotting_matplotlib + + result, position_table, axis, aggregation = _region_contrast_plot_setup( + _region_contrast_position_rows() + ) + + payload = plotting.prepare_region_contrast_heatmap_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + + x_column = "plot_x" if "plot_x" in payload["plot_table"].columns else "position" + expected = ( + payload["plot_table"] + .loc[ + lambda table: table["value_mode"] == "delta", + [x_column, "row_order", "value"], + ] + .pivot_table( + index="row_order", columns=x_column, values="value", aggfunc="mean" + ) + .sort_index(axis=0) + .sort_index(axis=1) + .to_numpy() + ) + + fig, provided_ax = plt.subplots() + fig, ax = plotting_matplotlib.plot_region_contrast_heatmap_matplotlib( + payload, + ax=provided_ax, + title="Custom region contrast heatmap", + ) + + assert fig is provided_ax.figure + assert ax is provided_ax + assert ax.get_title() == "Custom region contrast heatmap" + assert len(ax.images) == 1 + np.testing.assert_allclose(np.asarray(ax.images[0].get_array()), expected) + plt.close(fig) + + +def test_plot_region_contrast_heatmap_matplotlib_accepts_minimal_payload_without_summary_table(): + from matplotlib import pyplot as plt + + from dimelo import plotting_matplotlib + + result, position_table, axis, aggregation = _region_contrast_plot_setup( + _region_contrast_position_rows() + ) + + payload = plotting.prepare_region_contrast_heatmap_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + payload = { + "plot_table": payload["plot_table"].copy(), + "metadata": dict(payload["metadata"]), + } + + fig, ax = plotting_matplotlib.plot_region_contrast_heatmap_matplotlib(payload) + + assert fig is not None + assert ax is not None + assert len(ax.images) == 1 + assert [tick.get_text() for tick in ax.get_yticklabels()] == [ + "chr1:190-210,-", + "chr1:90-110,+", + ] + plt.close(fig) + + +def test_prepare_region_contrast_heatmap_data_rejects_conflicting_ranks_for_same_region(): + result, position_table, axis, aggregation = _region_contrast_plot_setup( + _region_contrast_position_rows() + ) + result.summary = pd.concat( + [ + result.summary, + pd.DataFrame( + [ + { + "region_id": "chr1:90-110,+", + "fraction": 0.55, + "reference_fraction": 0.20, + "delta_fraction": 0.35, + "rank": 7, + } + ] + ), + ], + ignore_index=True, + ) + + with pytest.raises(ValueError, match="exactly one rank value per plotted region"): + plotting.prepare_region_contrast_heatmap_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + + +def test_prepare_region_contrast_heatmap_data_requires_both_contrast_sides(): + result, _, axis, aggregation = _region_contrast_plot_setup( + _region_contrast_position_rows() + ) + position_table = pd.DataFrame( + [ + { + "region_id": "chr1:90-110,+", + "condition": "NS", + "position": 95, + "anchor": 100, + "value": 0.1, + "region_strand": "+", + } + ] + ) + + with pytest.raises(ValueError, match="both contrast sides"): + plotting.prepare_region_contrast_heatmap_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + + +def test_prepare_region_contrast_profile_data_requires_grouping_key(): + result, position_table, axis, aggregation = _region_contrast_plot_setup( + _region_contrast_position_rows(include_grouping_key=False) + ) + + with pytest.raises(ValueError, match="sample_id or condition"): + plotting.prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + + +@pytest.mark.parametrize( + "position_rows", + [ + [ + { + "region_id": "chr1:90-110,+", + "sample_id": "NS", + "condition": "NS", + "position": 95, + "anchor": 100, + "value": 0.1, + "region_strand": "+", + } + ], + [ + { + "region_id": "chr1:90-110,+", + "sample_id": "sample-a", + "condition": "condition-a", + "position": 95, + "anchor": 100, + "value": 0.1, + "region_strand": "+", + } + ], + ], +) +def test_prepare_region_contrast_profile_data_rejects_ambiguous_grouping_key( + position_rows, +): + result, _, _, _ = _region_contrast_plot_setup(_region_contrast_position_rows()) + position_table = pd.DataFrame(position_rows) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=20, + downstream_bp=20, + ) + aggregation = plotting.AggregationSpec() + + with pytest.raises(ValueError, match="could not resolve a unique grouping key"): + plotting.prepare_region_contrast_profile_data( + result=result, + position_table=position_table, + axis=axis, + aggregation=aggregation, + value_mode="all", + ) + + +def test_prepare_single_read_plot_data_flips_negative_regions_to_5to3(): + reads = pd.DataFrame( + [ + { + "region_id": "reg1", + "region_strand": "-", + "event_pos": 110, + "anchor": 100, + "read_id": "r1", + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="custom", + upstream_bp=20, + downstream_bp=20, + ) + + payload = plotting.prepare_single_read_plot_data( + reads, + plot_family="single_read_raster", + axis=axis, + position_column="event_pos", + anchor_column="anchor", + region_strand_column="region_strand", + ) + + assert payload["plot_table"].loc[0, "plot_x"] == -10 + + +def test_prepare_single_read_plot_data_genomic_fixed_window_does_not_require_strand_column(): + reads = pd.DataFrame( + [ + { + "region_id": "reg1", + "event_pos": 110, + "anchor": 100, + "read_id": "r1", + } + ] + ) + axis = plotting.AxisSpec( + orientation="genomic", + coordinate_mode="fixed_window", + anchor="custom", + upstream_bp=20, + downstream_bp=20, + ) + + payload = plotting.prepare_single_read_plot_data( + reads, + plot_family="single_read_raster", + axis=axis, + position_column="event_pos", + anchor_column="anchor", + ) + + assert list(payload["plot_table"]["plot_x"]) == [10.0] + + +def test_prepare_single_read_plot_data_filters_rows_outside_fixed_window_bounds(): + reads = pd.DataFrame( + [ + { + "region_id": "reg1", + "region_strand": "+", + "event_pos": 89, + "anchor": 100, + "read_id": "r0", + }, + { + "region_id": "reg1", + "region_strand": "+", + "event_pos": 95, + "anchor": 100, + "read_id": "r1", + }, + { + "region_id": "reg1", + "region_strand": "+", + "event_pos": 105, + "anchor": 100, + "read_id": "r2", + }, + { + "region_id": "reg1", + "region_strand": "+", + "event_pos": 111, + "anchor": 100, + "read_id": "r3", + }, + ] + ) + axis = plotting.AxisSpec( + orientation="genomic", + coordinate_mode="fixed_window", + anchor="custom", + upstream_bp=10, + downstream_bp=10, + ) + + payload = plotting.prepare_single_read_plot_data( + reads, + plot_family="single_read_raster", + axis=axis, + position_column="event_pos", + anchor_column="anchor", + ) + + assert list(payload["plot_table"]["read_id"]) == ["r1", "r2"] + assert list(payload["plot_table"]["plot_x"]) == [-5.0, 5.0] + + +@pytest.mark.parametrize("upstream_bp, downstream_bp", [(-1, 10), (10, -1)]) +def test_validate_axis_spec_rejects_negative_fixed_window_bounds( + upstream_bp, downstream_bp +): + axis = plotting.AxisSpec( + orientation="genomic", + coordinate_mode="fixed_window", + anchor="custom", + upstream_bp=upstream_bp, + downstream_bp=downstream_bp, + ) + + with pytest.raises(ValueError, match="non-negative"): + plotting.validate_axis_spec(axis, plot_family="aggregate_profile") + + +def test_prepare_single_read_plot_data_rejects_invalid_region_strand_in_region_5to3(): + reads = pd.DataFrame( + [ + { + "region_id": "reg1", + "region_strand": "?", + "event_pos": 110, + "anchor": 100, + "read_id": "r1", + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="custom", + upstream_bp=20, + downstream_bp=20, + ) + + with pytest.raises(ValueError, match="region_strand values"): + plotting.prepare_single_read_plot_data( + reads, + plot_family="single_read_raster", + axis=axis, + position_column="event_pos", + anchor_column="anchor", + region_strand_column="region_strand", + ) + + +def test_prepare_aggregate_plot_data_retains_metadata_for_fixed_window(): + table = pd.DataFrame( + [ + { + "region_id": "reg1", + "region_strand": "+", + "event_pos": 95, + "anchor": 100, + "signal": 1.0, + }, + { + "region_id": "reg1", + "region_strand": "+", + "event_pos": 105, + "anchor": 100, + "signal": 3.0, + }, + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="fixed_window", + anchor="center", + upstream_bp=10, + downstream_bp=10, + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="faceted", + ) + + payload = plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + position_column="event_pos", + anchor_column="anchor", + region_strand_column="region_strand", + ) + + assert {"plot_table", "axis_table", "metadata"} <= set(payload) + assert payload["metadata"]["orientation"] == "region_5to3" + + +@pytest.mark.parametrize( + "relative, regions_dict, expected_anchor, expected_offset_center", + [ + (True, None, "center", 0), + (False, {"regions.bed": [(100, 140)]}, "absolute", 120), + ], +) +def test_legacy_enrichment_profile_routes_regions_5to3prime_through_shared_prep( + monkeypatch, + relative, + regions_dict, + expected_anchor, + expected_offset_center, +): + called = {} + captured = {} + + real_prepare_aggregate_plot_data = plotting.prepare_aggregate_plot_data + + def spy_prepare_aggregate_plot_data( + table, *, plot_family, axis, aggregation, **kwargs + ): + called["table"] = table + called["plot_family"] = plot_family + called["axis"] = axis + called["aggregation"] = aggregation + return real_prepare_aggregate_plot_data( + table, + plot_family=plot_family, + axis=axis, + aggregation=aggregation, + **kwargs, + ) + + monkeypatch.setattr( + plotting, "prepare_aggregate_plot_data", spy_prepare_aggregate_plot_data + ) + monkeypatch.setattr( + "dimelo.plot_enrichment_profile.get_enrichment_profiles", + lambda **kwargs: [np.array([0.25, 0.75])], + ) + if regions_dict is not None: + monkeypatch.setattr( + "dimelo.plot_enrichment_profile.utils.regions_dict_from_input", + lambda *args, **kwargs: regions_dict, + ) + + def fake_make_enrichment_profile_plot( + *, trace_vectors, sample_names, offset_center=0, **kwargs + ): + captured["trace_vectors"] = trace_vectors + captured["sample_names"] = sample_names + captured["offset_center"] = offset_center + return object() + + monkeypatch.setattr( + "dimelo.plot_enrichment_profile.make_enrichment_profile_plot", + fake_make_enrichment_profile_plot, + ) + + result = plot_enrichment_profile.plot_enrichment_profile( + mod_file_names=["sample.fake"], + regions_list=["regions.bed"], + motifs=["A"], + sample_names=["sample"], + window_size=10, + relative=relative, + regions_5to3prime=True, + ) + + assert result is not None + assert called["plot_family"] == "aggregate_profile" + assert called["axis"].orientation == "region_5to3" + assert called["axis"].coordinate_mode == "fixed_window" + assert called["axis"].anchor == expected_anchor + assert called["aggregation"].layout == "faceted" + assert list(called["table"]["sample_name"]) == ["sample", "sample"] + assert list(called["table"]["value"]) == [0.25, 0.75] + assert list(called["table"]["region_strand"]) == ["+", "+"] + assert len(captured["trace_vectors"]) == 1 + assert np.array_equal(captured["trace_vectors"][0], np.array([0.25, 0.75])) + assert captured["sample_names"] == ["sample"] + assert captured["offset_center"] == expected_offset_center + + +def test_prepare_single_read_plot_data_rejects_segment_map_axes(): + reads = pd.DataFrame( + [ + { + "region_id": "reg1", + "region_strand": "+", + "event_pos": 110, + "anchor": 100, + "read_id": "r1", + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec( + segment_id="body", + label="Body", + start_ref=100, + end_ref=200, + ) + ], + ) + + with pytest.raises(ValueError, match="segment_map is aggregate-only"): + plotting.prepare_single_read_plot_data( + reads, + plot_family="single_read_raster", + axis=axis, + position_column="event_pos", + anchor_column="anchor", + region_strand_column="region_strand", + ) + + +def test_prepare_single_read_plot_data_rejects_scaled_segment_axes(): + reads = pd.DataFrame( + [ + { + "region_id": "reg1", + "region_strand": "+", + "event_pos": 110, + "anchor": 100, + "read_id": "r1", + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec( + segment_id="body", + label="Body", + start_ref=100, + end_ref=200, + mode="scaled", + bins=25, + ) + ], + ) + + with pytest.raises(ValueError, match="segment_map is aggregate-only"): + plotting.prepare_single_read_plot_data( + reads, + plot_family="single_read_raster", + axis=axis, + position_column="event_pos", + anchor_column="anchor", + region_strand_column="region_strand", + ) + + +def test_plot_reads_routes_region_5to3prime_into_shared_single_read_prep(monkeypatch): + captured = {} + + def fake_loader(**kwargs): + return ( + [np.array([110.0])], + np.array(["read-1"]), + np.array(["A,0"]), + {"chr1": [(100, 120, "-")]}, + ) + + real_prepare = plotting.prepare_single_read_plot_data + + def spy_prepare(table, *, plot_family, axis, **kwargs): + captured["table"] = table + captured["plot_family"] = plot_family + captured["axis"] = axis + captured["kwargs"] = kwargs + return real_prepare(table, plot_family=plot_family, axis=axis, **kwargs) + + class FakeAxes: + def __init__(self): + self.legend_ = None + self.xlim = None + + def legend(self, *args, **kwargs): + self.legend_args = (args, kwargs) + + def get_legend_handles_labels(self): + return ([], []) + + def set_xlim(self, limits): + self.xlim = limits + + monkeypatch.setattr( + "dimelo.plot_reads.load_processed.readwise_binary_modification_arrays", + fake_loader, + ) + monkeypatch.setattr( + "dimelo.plot_reads.plotting.prepare_single_read_plot_data", + spy_prepare, + ) + monkeypatch.setattr( + "dimelo.plot_reads.sns.scatterplot", lambda **kwargs: FakeAxes() + ) + + axes = plot_reads.plot_reads( + mod_file_name="sample.h5", + regions="regions.bed", + motifs=["A,0"], + window_size=20, + relative=True, + regions_5to3prime=True, + ) + + assert isinstance(axes, FakeAxes) + assert captured["plot_family"] == "single_read_raster" + assert captured["axis"].orientation == "region_5to3" + assert captured["axis"].coordinate_mode == "fixed_window" + assert captured["axis"].anchor == "center" + assert captured["axis"].upstream_bp == 20 + assert captured["axis"].downstream_bp == 20 + assert list(captured["table"]["region_strand"]) == ["+"] + + +def test_plot_reads_defaults_read_index_axis_and_legend(monkeypatch): + captured = {} + + def fake_loader(**kwargs): + return ( + [np.array([100.0, 101.0]), np.array([200.0])], + np.array(["read-1", "read-2"]), + np.array(["A,0", "CG,0"]), + {"chr1": [(90, 210, "+")]}, + ) + + real_prepare = plotting.prepare_single_read_plot_data + + def spy_prepare(table, *, plot_family, axis, **kwargs): + captured["table"] = table.copy() + return real_prepare(table, plot_family=plot_family, axis=axis, **kwargs) + + class FakeAxes: + def __init__(self): + self.legend_ = None + self.xlabel = None + self.ylabel = None + + def legend(self, *args, **kwargs): + self.legend_args = (args, kwargs) + + def get_legend_handles_labels(self): + return ([], []) + + def set_xlim(self, limits): + self.xlim = limits + + def set_xlabel(self, label): + self.xlabel = label + + def set_ylabel(self, label): + self.ylabel = label + + monkeypatch.setattr( + "dimelo.plot_reads.load_processed.readwise_binary_modification_arrays", + fake_loader, + ) + monkeypatch.setattr( + "dimelo.plot_reads.plotting.prepare_single_read_plot_data", + spy_prepare, + ) + monkeypatch.setattr( + "dimelo.plot_reads.sns.scatterplot", lambda **kwargs: FakeAxes() + ) + + axes = plot_reads.plot_reads( + mod_file_name="sample.h5", + regions="regions.bed", + motifs=["A,0", "CG,0"], + window_size=50, + relative=True, + ) + + assert list(captured["table"]["read_index"]) == [0, 0, 1] + assert axes.xlabel == "Position (bp)" + assert axes.ylabel == "Read index" + assert axes.legend_args[1]["title"] == "Mod, index" + + +def test_prepare_aggregate_plot_data_builds_concatenated_segment_axis(): + table = pd.DataFrame( + [ + { + "region_id": "reg1", + "segment_id": "upstream", + "segment_pos": 0, + "signal": 1.0, + }, + { + "region_id": "reg1", + "segment_id": "body", + "segment_pos": 10, + "signal": 2.0, + }, + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("upstream", "Upstream", 0, 100, "raw", bins=20), + plotting.SegmentSpec( + "body", + "Body", + 100, + 400, + "scaled", + bins=50, + contiguous_with_previous=True, + ), + ], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="concatenated", + ) + + payload = plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + assert list(payload["axis_table"]["segment_id"]) == ["upstream", "body"] + assert list(payload["axis_table"]["plot_start"]) == [0, 20] + assert list(payload["axis_table"]["plot_end"]) == [20, 70] + assert payload["axis_table"]["plot_start"].is_monotonic_increasing + assert list(payload["plot_table"]["plot_x"]) == [0.0, 30.0] + + +def test_prepare_aggregate_plot_data_rejects_duplicate_segment_ids_in_axis_segments(): + table = pd.DataFrame( + [ + { + "region_id": "reg1", + "segment_id": "upstream", + "segment_pos": 1, + "signal": 1.0, + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("upstream", "Upstream A", 0, 100, "raw", bins=20), + plotting.SegmentSpec("upstream", "Upstream B", 100, 200, "raw", bins=20), + ], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="concatenated", + ) + + with pytest.raises(ValueError, match="duplicate segment_id values"): + plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + +def test_prepare_aggregate_plot_data_rejects_unknown_segment_ids(): + table = pd.DataFrame( + [ + { + "region_id": "reg1", + "segment_id": "unknown", + "segment_pos": 1, + "signal": 1.0, + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("upstream", "Upstream", 0, 100, "raw", bins=20), + ], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="concatenated", + ) + + with pytest.raises(ValueError, match="unknown segment_id values"): + plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + +def test_prepare_aggregate_plot_data_rejects_missing_segment_positions(): + table = pd.DataFrame( + [ + { + "region_id": "reg1", + "segment_id": "upstream", + "segment_pos": float("nan"), + "signal": 1.0, + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("upstream", "Upstream", 0, 100, "raw", bins=20), + ], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="concatenated", + ) + + with pytest.raises(ValueError, match="missing or NaN values"): + plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + +@pytest.mark.parametrize("segment_pos", [-1, 20]) +def test_prepare_aggregate_plot_data_rejects_segment_positions_outside_declared_span( + segment_pos, +): + table = pd.DataFrame( + [ + { + "region_id": "reg1", + "segment_id": "upstream", + "segment_pos": segment_pos, + "signal": 1.0, + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("upstream", "Upstream", 0, 100, "raw", bins=20), + ], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="concatenated", + ) + + with pytest.raises(ValueError, match="declared segment span"): + plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + +def test_prepare_aggregate_plot_data_marks_non_contiguous_segment_breaks(): + table = pd.DataFrame( + [{"region_id": "reg1", "segment_id": "exon1", "segment_pos": 5, "signal": 1.0}] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("exon1", "Exon 1", 100, 200, "scaled", bins=20), + plotting.SegmentSpec( + "exon3", + "Exon 3", + 500, + 650, + "scaled", + bins=20, + contiguous_with_previous=False, + plot_gap_after=True, + ), + ], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="faceted", + ) + + payload = plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + assert list(payload["axis_table"]["contiguous_with_previous"]) == [True, False] + assert list(payload["axis_table"]["plot_gap_after"]) == [False, True] + + +@pytest.mark.parametrize( + "segment", + [ + plotting.SegmentSpec("bad_bins", "Bad bins", 0, 100, "raw", bins=0), + plotting.SegmentSpec( + "bad_bins_neg", "Bad bins negative", 0, 100, "scaled", bins=-5 + ), + plotting.SegmentSpec("bad_raw", "Bad raw", 100, 100, "raw"), + plotting.SegmentSpec( + "bad_raw_reverse", "Bad raw reverse", 200, 100, "raw", bins=20 + ), + ], +) +def test_prepare_aggregate_plot_data_rejects_malformed_segment_spans(segment): + table = pd.DataFrame( + [ + { + "region_id": "reg1", + "segment_id": "bad_raw", + "segment_pos": 1, + "signal": 1.0, + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[segment], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="concatenated", + ) + + with pytest.raises(ValueError, match="invalid"): + plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + +def test_prepare_aggregate_plot_data_rejects_invalid_segment_mode(): + table = pd.DataFrame( + [ + { + "region_id": "reg1", + "segment_id": "upstream", + "segment_pos": 1, + "signal": 1.0, + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("upstream", "Upstream", 0, 100, "bogus", bins=20), + ], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="concatenated", + ) + + with pytest.raises(ValueError, match="mode must be 'raw' or 'scaled'"): + plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + +@pytest.mark.parametrize("start_ref, end_ref", [(100, 100), (200, 100)]) +def test_prepare_aggregate_plot_data_rejects_zero_or_negative_width_scaled_segment_without_bins( + start_ref, end_ref +): + table = pd.DataFrame( + [{"region_id": "reg1", "segment_id": "body", "segment_pos": 1, "signal": 1.0}] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("body", "Body", start_ref, end_ref, "scaled"), + ], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="concatenated", + ) + + with pytest.raises(ValueError, match="invalid scaled span"): + plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + +def test_prepare_aggregate_plot_data_segment_map_keeps_user_plot_columns(): + table = pd.DataFrame( + [ + { + "region_id": "reg1", + "segment_id": "upstream", + "segment_pos": 3, + "plot_start": "user-start", + "plot_end": "user-end", + "signal": 1.0, + } + ] + ) + axis = plotting.AxisSpec( + orientation="region_5to3", + coordinate_mode="segment_map", + segments=[ + plotting.SegmentSpec("upstream", "Upstream", 0, 100, "raw", bins=20), + ], + ) + aggregation = plotting.AggregationSpec( + weighting="equal_region", + within_region_summary="mean", + signal_normalization="none", + layout="concatenated", + ) + + payload = plotting.prepare_aggregate_plot_data( + table, + plot_family="aggregate_profile", + axis=axis, + aggregation=aggregation, + value_column="signal", + segment_id_column="segment_id", + segment_position_column="segment_pos", + ) + + assert list(payload["plot_table"]["plot_start"]) == ["user-start"] + assert list(payload["plot_table"]["plot_end"]) == ["user-end"] + assert list(payload["plot_table"]["plot_x"]) == [3.0] diff --git a/tests/test_region_analysis.py b/tests/test_region_analysis.py new file mode 100644 index 0000000..acdbddf --- /dev/null +++ b/tests/test_region_analysis.py @@ -0,0 +1,121 @@ +import threading +import time +from pathlib import Path + +import numpy as np + +from dimelo import region_analysis +from dimelo.models import SampleSpec + + +def _samples() -> list[SampleSpec]: + return [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="TR", + extract_h5="s2.h5", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + + +def test_build_region_feature_table_parallelizes_across_samples(monkeypatch): + started = 0 + started_lock = threading.Lock() + second_started = threading.Event() + + def fake_region_feature_matrix_from_pileup( + *, bedmethyl_file, motif, regions, cores, **kwargs + ): + nonlocal started + with started_lock: + started += 1 + local_started = started + if started >= 2: + second_started.set() + + if local_started == 1: + # If sample processing is serial, this wait will timeout and inflate wall time. + second_started.wait(timeout=1.0) + time.sleep(0.05) + + if str(bedmethyl_file).endswith("s1.bed.gz"): + return np.array([[1.0, 2.0]], dtype=float), [("chr1", 0, 10, "+")] + return np.array([[3.0, 4.0]], dtype=float), [("chr1", 10, 20, "-")] + + monkeypatch.setattr( + region_analysis.cluster, + "region_feature_matrix_from_pileup", + fake_region_feature_matrix_from_pileup, + ) + + start = time.perf_counter() + matrix, metadata_rows = region_analysis.build_region_feature_table( + samples=_samples(), + motifs=["A,0"], + matched_regions=Path("regions.bed"), + cores=2, + ) + elapsed = time.perf_counter() - start + + assert elapsed < 0.5 + assert matrix.shape == (2, 2) + assert matrix.tolist() == [[1.0, 2.0], [3.0, 4.0]] + assert metadata_rows[0]["sample_id"] == "s1" + assert metadata_rows[1]["sample_id"] == "s2" + + +def test_build_region_feature_table_reuses_regions_executor_in_serial_sample_mode( + monkeypatch, +): + class _FakeProcessPoolExecutor: + instances = [] + + def __init__(self, *, max_workers): + self.max_workers = max_workers + self.shutdown_called = False + _FakeProcessPoolExecutor.instances.append(self) + + def shutdown(self, wait=True, cancel_futures=False): + self.shutdown_called = True + + observed_executor_ids = [] + + def fake_region_feature_matrix_from_pileup( + *, bedmethyl_file, motif, regions, cores, regions_executor=None, **kwargs + ): + observed_executor_ids.append(id(regions_executor)) + if str(bedmethyl_file).endswith("s1.bed.gz"): + return np.array([[1.0, 2.0]], dtype=float), [("chr1", 0, 10, "+")] + return np.array([[3.0, 4.0]], dtype=float), [("chr1", 10, 20, "-")] + + monkeypatch.setattr( + region_analysis.concurrent.futures, + "ProcessPoolExecutor", + _FakeProcessPoolExecutor, + ) + monkeypatch.setattr(region_analysis.utils, "cores_to_run", lambda _cores: 8) + monkeypatch.setattr( + region_analysis.cluster, + "region_feature_matrix_from_pileup", + fake_region_feature_matrix_from_pileup, + ) + + matrix, metadata_rows = region_analysis.build_region_feature_table( + samples=_samples(), + motifs=["A,0"], + matched_regions=Path("regions.bed"), + cores=None, + ) + + assert matrix.shape == (2, 2) + assert len(metadata_rows) == 2 + assert len(_FakeProcessPoolExecutor.instances) == 1 + assert _FakeProcessPoolExecutor.instances[0].shutdown_called + assert len(set(observed_executor_ids)) == 1 diff --git a/tests/test_region_contrasts.py b/tests/test_region_contrasts.py new file mode 100644 index 0000000..036e33f --- /dev/null +++ b/tests/test_region_contrasts.py @@ -0,0 +1,2986 @@ +import math +from types import SimpleNamespace + +import pandas as pd +import pytest + +from dimelo import region_contrasts +from dimelo.models import ContrastSpec, SampleSpec + + +def test_validate_supported_v1_combination(): + region_contrasts.validate_region_contrast_request( + analysis_unit="ensemble_region", + representation="modified_fraction", + signal_source="pileup_counts", + test="effect_size_only", + ) + + +def test_validate_accepts_beta_binomial_for_supported_v1_combination(): + region_contrasts.validate_region_contrast_request( + analysis_unit="ensemble_region", + representation="modified_fraction", + signal_source="pileup_counts", + test="beta_binomial", + ) + + +def test_validate_region_contrast_request_accepts_cluster_occupancy_fraction_mode(): + region_contrasts.validate_region_contrast_request( + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="effect_size_only", + ) + + +def test_validate_region_contrast_request_rejects_beta_binomial_for_cluster_occupancy(): + with pytest.raises(ValueError, match="cluster_occupancy"): + region_contrasts.validate_region_contrast_request( + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="beta_binomial", + ) + + +def test_validate_region_contrast_request_accepts_single_read_mod_fraction(): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="extract_reads", + test="sample_distribution_shift", + ) + + +def test_validate_region_contrast_request_accepts_single_read_window_features(): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_window_features", + signal_source="extract_features", + test="feature_summary_shift", + ) + + +def test_validate_region_contrast_request_rejects_single_read_wrong_signal_source(): + with pytest.raises(ValueError, match="extract_reads"): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="pileup_counts", + test="sample_distribution_shift", + ) + + +def test_validate_region_contrast_request_rejects_single_read_unknown_representation(): + with pytest.raises(ValueError, match="read_mod_fraction"): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_shape", + signal_source="extract_reads", + test="sample_distribution_shift", + ) + + +def test_validate_region_contrast_request_rejects_single_read_unknown_test(): + with pytest.raises(ValueError, match="sample_distribution_shift"): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="extract_reads", + test="beta_binomial", + ) + + +def test_validate_rejects_unsupported_single_read_beta_binomial(): + with pytest.raises(ValueError, match="extract_reads"): + region_contrasts.validate_region_contrast_request( + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="pileup_counts", + test="beta_binomial", + ) + + +def _mock_region_summaries(): + return pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "cluster": "C1", + "count": 6, + "fraction": 0.6, + }, + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "cluster": "C2", + "count": 4, + "fraction": 0.4, + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "15min", + "cluster": "C1", + "count": 1, + "fraction": 0.2, + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "15min", + "cluster": "C2", + "count": 4, + "fraction": 0.8, + }, + { + "region_id": "reg2", + "sample_id": "s1", + "condition": "NS", + "cluster": "C3", + "count": 5, + "fraction": 1.0, + }, + ] + ) + + +def _mock_cluster_occupancy_evidence(): + region_summaries = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "ns1", + "condition": "NS", + "cluster": "C1", + "count": 2, + }, + { + "region_id": "reg1", + "sample_id": "ns1", + "condition": "NS", + "cluster": "C2", + "count": 8, + }, + { + "region_id": "reg1", + "sample_id": "ns2", + "condition": "NS", + "cluster": "C1", + "count": 3, + }, + { + "region_id": "reg1", + "sample_id": "ns2", + "condition": "NS", + "cluster": "C2", + "count": 7, + }, + { + "region_id": "reg1", + "sample_id": "tx1", + "condition": "15min", + "cluster": "C1", + "count": 8, + }, + { + "region_id": "reg1", + "sample_id": "tx1", + "condition": "15min", + "cluster": "C2", + "count": 2, + }, + { + "region_id": "reg1", + "sample_id": "tx2", + "condition": "15min", + "cluster": "C1", + "count": 7, + }, + { + "region_id": "reg1", + "sample_id": "tx2", + "condition": "15min", + "cluster": "C2", + "count": 3, + }, + { + "region_id": "reg2", + "sample_id": "ns1", + "condition": "NS", + "cluster": "C1", + "count": 0, + }, + { + "region_id": "reg2", + "sample_id": "ns1", + "condition": "NS", + "cluster": "C2", + "count": 0, + }, + { + "region_id": "reg2", + "sample_id": "ns2", + "condition": "NS", + "cluster": "C1", + "count": 0, + }, + { + "region_id": "reg2", + "sample_id": "ns2", + "condition": "NS", + "cluster": "C2", + "count": 0, + }, + { + "region_id": "reg2", + "sample_id": "tx1", + "condition": "15min", + "cluster": "C1", + "count": 10, + }, + { + "region_id": "reg2", + "sample_id": "tx1", + "condition": "15min", + "cluster": "C2", + "count": 0, + }, + { + "region_id": "reg2", + "sample_id": "tx2", + "condition": "15min", + "cluster": "C1", + "count": 0, + }, + { + "region_id": "reg2", + "sample_id": "tx2", + "condition": "15min", + "cluster": "C2", + "count": 0, + }, + ] + ) + return region_contrasts.build_cluster_occupancy_evidence_table( + region_summaries=region_summaries, + ) + + +def _mock_group_vs_group_cluster_fraction_evidence(): + return pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "na1", + "condition": "treated_a", + "cluster": "C1", + "fraction": 0.2, + }, + { + "region_id": "reg1", + "sample_id": "na2", + "condition": "treated_a", + "cluster": "C1", + "fraction": 0.4, + }, + { + "region_id": "reg1", + "sample_id": "nb1", + "condition": "treated_b", + "cluster": "C1", + "fraction": 0.8, + }, + { + "region_id": "reg1", + "sample_id": "nb2", + "condition": "treated_b", + "cluster": "C1", + "fraction": 1.0, + }, + { + "region_id": "reg1", + "sample_id": "ca1", + "condition": "control_a", + "cluster": "C1", + "fraction": 0.0, + }, + { + "region_id": "reg1", + "sample_id": "ca2", + "condition": "control_a", + "cluster": "C1", + "fraction": 0.2, + }, + { + "region_id": "reg1", + "sample_id": "cb1", + "condition": "control_b", + "cluster": "C1", + "fraction": 0.4, + }, + { + "region_id": "reg1", + "sample_id": "cb2", + "condition": "control_b", + "cluster": "C1", + "fraction": 0.6, + }, + ] + ) + + +def test_build_cluster_occupancy_evidence_table_summarizes_region_sample_clusters(): + evidence = region_contrasts.build_cluster_occupancy_evidence_table( + region_summaries=_mock_region_summaries(), + ) + + assert { + "region_id", + "sample_id", + "condition", + "cluster", + "fraction", + "dominant_cluster", + "cluster_entropy", + } <= set(evidence.columns) + + +def test_build_cluster_occupancy_evidence_table_computes_fraction_dominant_and_entropy(): + evidence = _mock_cluster_occupancy_evidence() + + reg1_ns1_c1 = evidence[ + (evidence["region_id"] == "reg1") + & (evidence["sample_id"] == "ns1") + & (evidence["cluster"] == "C1") + ].iloc[0] + assert reg1_ns1_c1["fraction"] == pytest.approx(0.2) + assert reg1_ns1_c1["dominant_cluster"] == "C2" + assert reg1_ns1_c1["cluster_entropy"] == pytest.approx( + -(0.2 * math.log2(0.2) + 0.8 * math.log2(0.8)) + ) + + +def test_build_cluster_occupancy_evidence_table_zero_count_groups_stay_zero_entropy(): + evidence = _mock_cluster_occupancy_evidence() + + reg2_ns1 = evidence[ + (evidence["region_id"] == "reg2") & (evidence["sample_id"] == "ns1") + ] + assert list(reg2_ns1["fraction"]) == [0.0, 0.0] + assert reg2_ns1["dominant_cluster"].isna().all() + assert reg2_ns1["cluster_entropy"].tolist() == [0.0, 0.0] + + +@pytest.mark.parametrize("count", [-1, float("nan"), float("inf"), float("-inf")]) +def test_build_cluster_occupancy_evidence_table_rejects_invalid_counts(count): + with pytest.raises(ValueError, match="count values must be finite and >= 0"): + region_contrasts.build_cluster_occupancy_evidence_table( + region_summaries=pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "cluster": "C1", + "count": count, + } + ] + ), + ) + + +def test_build_single_read_mod_fraction_evidence_table(): + extract_table = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r1", + "modified_count": 2, + "valid_count": 4, + }, + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r2", + "modified_count": 1, + "valid_count": 4, + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "read_id": "r3", + "modified_count": 4, + "valid_count": 4, + }, + ] + ) + + result = region_contrasts.build_single_read_mod_fraction_evidence_table( + extract_table=extract_table + ) + + assert result.to_dict("records") == [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r1", + "modified_count": 2, + "valid_count": 4, + "read_mod_fraction": pytest.approx(0.5), + }, + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r2", + "modified_count": 1, + "valid_count": 4, + "read_mod_fraction": pytest.approx(0.25), + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "read_id": "r3", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": pytest.approx(1.0), + }, + ] + + +def test_build_single_read_mod_fraction_evidence_table_rejects_missing_columns(): + with pytest.raises(ValueError, match="modified_count"): + region_contrasts.build_single_read_mod_fraction_evidence_table( + extract_table=pd.DataFrame([{"region_id": "reg1", "sample_id": "s1"}]) + ) + + +@pytest.mark.parametrize( + ("modified_count", "valid_count"), + [ + (-1, 4), + (1, -1), + (1.5, 4), + (1, 4.5), + (float("nan"), 4), + (1, float("nan")), + (float("inf"), 4), + (1, float("inf")), + (5, 4), + ], +) +def test_build_single_read_mod_fraction_evidence_table_rejects_invalid_counts( + modified_count, + valid_count, +): + with pytest.raises(ValueError, match="modified_count and valid_count"): + region_contrasts.build_single_read_mod_fraction_evidence_table( + extract_table=pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r1", + "modified_count": modified_count, + "valid_count": valid_count, + } + ] + ) + ) + + +def test_score_regions_single_read_mod_fraction_effect_size_only(): + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r1", + "modified_count": 1, + "valid_count": 4, + "read_mod_fraction": 0.25, + }, + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r2", + "modified_count": 2, + "valid_count": 4, + "read_mod_fraction": 0.50, + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "read_id": "r3", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": 1.00, + }, + { + "region_id": "reg1", + "sample_id": "s3", + "condition": "treated", + "read_id": "r4", + "modified_count": 3, + "valid_count": 4, + "read_mod_fraction": 0.75, + }, + ] + ) + + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="group_vs_group", numerator=["treated"], denominator=["NS"] + ), + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="extract_reads", + test="effect_size_only", + read_table=evidence, + ) + + row = result.summary.iloc[0] + assert row["region_id"] == "reg1" + assert row["sample_summary_numerator_mean"] == pytest.approx(0.875) + assert row["sample_summary_denominator_mean"] == pytest.approx(0.375) + assert row["delta_summary_mean"] == pytest.approx(0.5) + + +def test_score_regions_single_read_mod_fraction_supports_matched_pairwise(): + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r1", + "modified_count": 0, + "valid_count": 4, + "read_mod_fraction": 0.0, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_after", + "condition": "after", + "read_id": "r2", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": 1.0, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s2_before", + "condition": "before", + "read_id": "r3", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": 1.0, + "pair_id": "p2", + }, + { + "region_id": "reg1", + "sample_id": "s2_after", + "condition": "after", + "read_id": "r4", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": 1.0, + "pair_id": "p2", + }, + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r5", + "modified_count": 0, + "valid_count": 4, + "read_mod_fraction": 0.0, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r6", + "modified_count": 0, + "valid_count": 4, + "read_mod_fraction": 0.0, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s2_after", + "condition": "after", + "read_id": "r7", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": 1.0, + "pair_id": "p2", + }, + { + "region_id": "reg1", + "sample_id": "s2_after", + "condition": "after", + "read_id": "r8", + "modified_count": 3, + "valid_count": 4, + "read_mod_fraction": 0.75, + "pair_id": "p2", + }, + { + "region_id": "reg1", + "sample_id": "s3_after", + "condition": "after", + "read_id": "r5", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": 1.0, + "pair_id": "p3", + }, + ] + ) + + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["after"], + denominator=["before"], + pairing_key="pair_id", + ), + analysis_unit="single_read", + representation="read_mod_fraction", + signal_source="extract_reads", + test="sample_distribution_shift", + read_table=evidence, + ) + + row = result.summary.iloc[0] + assert row["sample_summary_numerator_mean"] == pytest.approx(0.9583333333333333) + assert row["sample_summary_denominator_mean"] == pytest.approx(0.5) + assert row["delta_summary_mean"] == pytest.approx(0.45833333333333326) + assert row["numerator_replicate_n"] == 2 + assert row["denominator_replicate_n"] == 2 + + +def test_build_single_read_feature_evidence_table_accepts_user_features(): + feature_table = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r1", + "f0": 0.1, + "f1": 0.2, + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "read_id": "r2", + "f0": 0.8, + "f1": 0.9, + }, + ] + ) + + result = region_contrasts.build_single_read_feature_evidence_table( + feature_table=feature_table + ) + + assert result.to_dict("records") == feature_table.to_dict("records") + + +def test_build_single_read_feature_evidence_table_rejects_missing_read_id(): + with pytest.raises(ValueError, match="read_id"): + region_contrasts.build_single_read_feature_evidence_table( + feature_table=pd.DataFrame( + [{"region_id": "reg1", "sample_id": "s1", "condition": "NS", "f0": 0.1}] + ) + ) + + +def test_build_single_read_feature_evidence_table_rejects_non_numeric_features(): + with pytest.raises(ValueError, match="numeric"): + region_contrasts.build_single_read_feature_evidence_table( + feature_table=pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r1", + "f0": "high", + } + ] + ) + ) + + +def test_score_regions_single_read_window_features_effect_size_only(): + feature_table = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r1", + "f0": 0.1, + "f1": 0.2, + }, + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "read_id": "r2", + "f0": 0.2, + "f1": 0.3, + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "read_id": "r3", + "f0": 0.8, + "f1": 0.9, + }, + { + "region_id": "reg1", + "sample_id": "s3", + "condition": "treated", + "read_id": "r4", + "f0": 0.7, + "f1": 0.8, + }, + ] + ) + + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="group_vs_group", numerator=["treated"], denominator=["NS"] + ), + analysis_unit="single_read", + representation="read_window_features", + signal_source="extract_features", + test="effect_size_only", + feature_table=feature_table, + ) + + row = result.summary.iloc[0] + assert row["region_id"] == "reg1" + assert row["f0_delta_mean"] == pytest.approx(0.6) + assert row["f1_delta_mean"] == pytest.approx(0.6) + + +def test_score_regions_single_read_window_features_supports_matched_pairwise(): + feature_table = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r1", + "f0": 0.0, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_after", + "condition": "after", + "read_id": "r2", + "f0": 1.0, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s2_before", + "condition": "before", + "read_id": "r3", + "f0": 1.0, + "pair_id": "p2", + }, + { + "region_id": "reg1", + "sample_id": "s2_after", + "condition": "after", + "read_id": "r4", + "f0": 0.6, + "pair_id": "p2", + }, + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r5", + "f0": 0.0, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r6", + "f0": 0.0, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s2_after", + "condition": "after", + "read_id": "r7", + "f0": 0.6, + "pair_id": "p2", + }, + { + "region_id": "reg1", + "sample_id": "s2_after", + "condition": "after", + "read_id": "r8", + "f0": 0.6, + "pair_id": "p2", + }, + { + "region_id": "reg1", + "sample_id": "s3_after", + "condition": "after", + "read_id": "r9", + "f0": 0.5, + "pair_id": "p3", + }, + ] + ) + + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["after"], + denominator=["before"], + pairing_key="pair_id", + ), + analysis_unit="single_read", + representation="read_window_features", + signal_source="extract_features", + test="feature_summary_shift", + feature_table=feature_table, + ) + + row = result.summary.iloc[0] + assert row["f0_numerator_mean"] == pytest.approx(0.8) + assert row["f0_denominator_mean"] == pytest.approx(0.5) + assert row["f0_delta_mean"] == pytest.approx(0.3) + + +@pytest.mark.parametrize( + "representation,table_factory", + [ + ("read_mod_fraction", "read_table"), + ("read_window_features", "feature_table"), + ], +) +def test_score_regions_single_read_matched_pairwise_requires_pairing_key_column( + representation, + table_factory, +): + contrast = ContrastSpec( + mode="matched_pairwise", + numerator=["after"], + denominator=["before"], + pairing_key="pair_id", + ) + if table_factory == "read_table": + evidence_kwargs = { + "read_table": pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r1", + "modified_count": 1, + "valid_count": 4, + "read_mod_fraction": 0.25, + }, + { + "region_id": "reg1", + "sample_id": "s1_after", + "condition": "after", + "read_id": "r2", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": 1.0, + }, + ] + ) + } + signal_source = "extract_reads" + test_name = "sample_distribution_shift" + else: + evidence_kwargs = { + "feature_table": pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r1", + "f0": 0.1, + }, + { + "region_id": "reg1", + "sample_id": "s1_after", + "condition": "after", + "read_id": "r2", + "f0": 0.9, + }, + ] + ) + } + signal_source = "extract_features" + test_name = "feature_summary_shift" + + with pytest.raises(ValueError, match="pair_id"): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=contrast, + analysis_unit="single_read", + representation=representation, + signal_source=signal_source, + test=test_name, + **evidence_kwargs, + ) + + +@pytest.mark.parametrize( + "representation,table_factory,signal_source,test_name", + [ + ( + "read_mod_fraction", + "read_table", + "extract_reads", + "sample_distribution_shift", + ), + ( + "read_window_features", + "feature_table", + "extract_features", + "feature_summary_shift", + ), + ], +) +def test_score_regions_single_read_matched_pairwise_requires_non_null_pairing_keys( + representation, + table_factory, + signal_source, + test_name, +): + contrast = ContrastSpec( + mode="matched_pairwise", + numerator=["after"], + denominator=["before"], + pairing_key="pair_id", + ) + if table_factory == "read_table": + evidence_kwargs = { + "read_table": pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r1", + "modified_count": 1, + "valid_count": 4, + "read_mod_fraction": 0.25, + "pair_id": None, + }, + { + "region_id": "reg1", + "sample_id": "s1_after", + "condition": "after", + "read_id": "r2", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": 1.0, + "pair_id": "p1", + }, + ] + ) + } + else: + evidence_kwargs = { + "feature_table": pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r1", + "f0": 0.1, + "pair_id": None, + }, + { + "region_id": "reg1", + "sample_id": "s1_after", + "condition": "after", + "read_id": "r2", + "f0": 0.9, + "pair_id": "p1", + }, + ] + ) + } + + with pytest.raises(ValueError, match="non-null"): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=contrast, + analysis_unit="single_read", + representation=representation, + signal_source=signal_source, + test=test_name, + **evidence_kwargs, + ) + + +@pytest.mark.parametrize( + "representation,table_factory,signal_source,test_name", + [ + ( + "read_mod_fraction", + "read_table", + "extract_reads", + "sample_distribution_shift", + ), + ( + "read_window_features", + "feature_table", + "extract_features", + "feature_summary_shift", + ), + ], +) +def test_score_regions_single_read_matched_pairwise_rejects_multi_condition_sides( + representation, + table_factory, + signal_source, + test_name, +): + contrast = ContrastSpec( + mode="matched_pairwise", + numerator=["after", "later"], + denominator=["before"], + pairing_key="pair_id", + ) + if table_factory == "read_table": + evidence_kwargs = { + "read_table": pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r1", + "modified_count": 1, + "valid_count": 4, + "read_mod_fraction": 0.25, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_after", + "condition": "after", + "read_id": "r2", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": 1.0, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_later", + "condition": "later", + "read_id": "r3", + "modified_count": 3, + "valid_count": 4, + "read_mod_fraction": 0.75, + "pair_id": "p1", + }, + ] + ) + } + else: + evidence_kwargs = { + "feature_table": pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r1", + "f0": 0.1, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_after", + "condition": "after", + "read_id": "r2", + "f0": 0.9, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_later", + "condition": "later", + "read_id": "r3", + "f0": 0.7, + "pair_id": "p1", + }, + ] + ) + } + + with pytest.raises(ValueError, match="exactly one numerator and one denominator"): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=contrast, + analysis_unit="single_read", + representation=representation, + signal_source=signal_source, + test=test_name, + **evidence_kwargs, + ) + + +@pytest.mark.parametrize( + "representation,table_factory,signal_source,test_name", + [ + ( + "read_mod_fraction", + "read_table", + "extract_reads", + "sample_distribution_shift", + ), + ( + "read_window_features", + "feature_table", + "extract_features", + "feature_summary_shift", + ), + ], +) +def test_score_regions_single_read_matched_pairwise_rejects_multiple_samples_on_one_side( + representation, + table_factory, + signal_source, + test_name, +): + contrast = ContrastSpec( + mode="matched_pairwise", + numerator=["after"], + denominator=["before"], + pairing_key="pair_id", + ) + if table_factory == "read_table": + evidence_kwargs = { + "read_table": pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r1", + "modified_count": 1, + "valid_count": 4, + "read_mod_fraction": 0.25, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_after_a", + "condition": "after", + "read_id": "r2", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": 1.0, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_after_b", + "condition": "after", + "read_id": "r3", + "modified_count": 3, + "valid_count": 4, + "read_mod_fraction": 0.75, + "pair_id": "p1", + }, + ] + ) + } + else: + evidence_kwargs = { + "feature_table": pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1_before", + "condition": "before", + "read_id": "r1", + "f0": 0.1, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_after_a", + "condition": "after", + "read_id": "r2", + "f0": 0.9, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "s1_after_b", + "condition": "after", + "read_id": "r3", + "f0": 0.7, + "pair_id": "p1", + }, + ] + ) + } + + with pytest.raises( + ValueError, match="exactly one sample per region, pair, and condition" + ): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=contrast, + analysis_unit="single_read", + representation=representation, + signal_source=signal_source, + test=test_name, + **evidence_kwargs, + ) + + +@pytest.mark.parametrize( + "representation,table_factory,signal_source,test_name", + [ + ( + "read_mod_fraction", + "read_table", + "extract_reads", + "sample_distribution_shift", + ), + ( + "read_window_features", + "feature_table", + "extract_features", + "feature_summary_shift", + ), + ], +) +def test_score_regions_single_read_matched_pairwise_rejects_sample_ids_mapped_to_multiple_pairs( + representation, + table_factory, + signal_source, + test_name, +): + contrast = ContrastSpec( + mode="matched_pairwise", + numerator=["after"], + denominator=["before"], + pairing_key="pair_id", + ) + if table_factory == "read_table": + evidence_kwargs = { + "read_table": pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "shared_sample", + "condition": "before", + "read_id": "r1", + "modified_count": 1, + "valid_count": 4, + "read_mod_fraction": 0.25, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "shared_sample", + "condition": "before", + "read_id": "r2", + "modified_count": 2, + "valid_count": 4, + "read_mod_fraction": 0.5, + "pair_id": "p2", + }, + { + "region_id": "reg1", + "sample_id": "s1_after", + "condition": "after", + "read_id": "r3", + "modified_count": 4, + "valid_count": 4, + "read_mod_fraction": 1.0, + "pair_id": "p1", + }, + ] + ) + } + else: + evidence_kwargs = { + "feature_table": pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "shared_sample", + "condition": "before", + "read_id": "r1", + "f0": 0.1, + "pair_id": "p1", + }, + { + "region_id": "reg1", + "sample_id": "shared_sample", + "condition": "before", + "read_id": "r2", + "f0": 0.2, + "pair_id": "p2", + }, + { + "region_id": "reg1", + "sample_id": "s1_after", + "condition": "after", + "read_id": "r3", + "f0": 0.9, + "pair_id": "p1", + }, + ] + ) + } + + with pytest.raises(ValueError, match="sample_id to map to exactly one pairing key"): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=contrast, + analysis_unit="single_read", + representation=representation, + signal_source=signal_source, + test=test_name, + **evidence_kwargs, + ) + + +def test_score_regions_single_read_window_features_uses_builtin_loader(monkeypatch): + extracted_by_path = { + "ns.h5": SimpleNamespace( + metadata=[ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 10, + "region_strand": "+", + } + ] + ), + "treated.h5": SimpleNamespace( + metadata=[ + { + "read_name": "r2", + "chromosome": "chr1", + "region_start": 0, + "region_end": 10, + "region_strand": "+", + } + ] + ), + } + + feature_rows_by_path = { + "ns.h5": ([[0.1]], ["f0"]), + "treated.h5": ([[0.9]], ["f0"]), + } + + monkeypatch.setattr( + region_contrasts, + "cluster", + SimpleNamespace( + extract_read_windows=lambda **kwargs: extracted_by_path[ + kwargs["hdf5_file"] + ], + read_window_feature_matrix=lambda extracted: feature_rows_by_path[ + "ns.h5" if extracted is extracted_by_path["ns.h5"] else "treated.h5" + ], + ), + raising=False, + ) + + result = region_contrasts.score_regions( + samples=[ + SampleSpec(sample_id="s1", condition="NS", extract_h5="ns.h5"), + SampleSpec(sample_id="s2", condition="treated", extract_h5="treated.h5"), + ], + regions="regions.bed", + motifs=["A,0"], + contrast=ContrastSpec( + mode="pairwise", numerator=["treated"], denominator=["NS"] + ), + analysis_unit="single_read", + representation="read_window_features", + signal_source="extract_features", + test="feature_summary_shift", + ) + + assert result.summary.iloc[0]["f0_delta_mean"] == pytest.approx(0.8) + + +def test_build_region_evidence_table_from_pileup_counts(monkeypatch): + samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="15min", + extract_h5="s2.h5", + replicate=2, + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + + monkeypatch.setattr( + region_contrasts.utils, + "regions_dict_from_input", + lambda regions, window_size=None: { + "chr1": [(0, 10, "+")], + "chr2": [(20, 30, "-")], + }, + ) + + counts_by_file = { + "s1.bed.gz": [(2, 10), (0, 0)], + "s2.bed.gz": [(7, 10), (8, 10)], + } + + def fake_regions_to_list( + function_handle, + regions, + window_size, + quiet, + cores, + split_large_regions=False, + ): + return counts_by_file[function_handle.keywords["bedmethyl_file"]] + + monkeypatch.setattr( + region_contrasts.load_processed, + "regions_to_list", + fake_regions_to_list, + ) + + evidence = region_contrasts.build_region_evidence_table( + samples=samples, + regions="matched.bed", + motifs=["A,0"], + ) + + expected = pd.DataFrame( + [ + { + "region_id": "chr1:0-10,+", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "s1", + "condition": "NS", + "replicate": None, + "modified_count": 2, + "valid_count": 10, + "mod_fraction": 0.2, + }, + { + "region_id": "chr2:20-30,-", + "chromosome": "chr2", + "start": 20, + "end": 30, + "strand": "-", + "sample_id": "s1", + "condition": "NS", + "replicate": None, + "modified_count": 0, + "valid_count": 0, + "mod_fraction": 0.0, + }, + { + "region_id": "chr1:0-10,+", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "s2", + "condition": "15min", + "replicate": 2, + "modified_count": 7, + "valid_count": 10, + "mod_fraction": 0.7, + }, + { + "region_id": "chr2:20-30,-", + "chromosome": "chr2", + "start": 20, + "end": 30, + "strand": "-", + "sample_id": "s2", + "condition": "15min", + "replicate": 2, + "modified_count": 8, + "valid_count": 10, + "mod_fraction": 0.8, + }, + ] + ) + + pd.testing.assert_frame_equal(evidence.reset_index(drop=True), expected) + + +def test_score_regions_effect_size_only_pairwise_ranks_largest_delta_first(monkeypatch): + contrast = ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["control"], + ) + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 8, + "valid_count": 10, + "mod_fraction": 0.8, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 2, + "valid_count": 10, + "mod_fraction": 0.2, + }, + { + "region_id": "reg2", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 6, + "valid_count": 10, + "mod_fraction": 0.6, + }, + { + "region_id": "reg2", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 4, + "valid_count": 10, + "mod_fraction": 0.4, + }, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: evidence.copy(), + ) + + result = region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + test="effect_size_only", + ) + + expected_summary = pd.DataFrame( + [ + { + "region_id": "reg1", + "fraction": 0.8, + "reference_fraction": 0.2, + "delta_fraction": 0.6, + "rank": 1, + }, + { + "region_id": "reg2", + "fraction": 0.6, + "reference_fraction": 0.4, + "delta_fraction": 0.2, + "rank": 2, + }, + ] + ) + + actual_summary = result.summary[ + [ + "region_id", + "fraction", + "reference_fraction", + "delta_fraction", + "log2_fc", + "rank", + ] + ].reset_index(drop=True) + + pd.testing.assert_frame_equal( + actual_summary.drop(columns=["log2_fc"]), + expected_summary, + ) + assert actual_summary.loc[0, "log2_fc"] == pytest.approx(1.99999459) + assert actual_summary.loc[1, "log2_fc"] == pytest.approx(0.58496124) + assert list(result.regions["region_id"]) == ["reg1", "reg2"] + assert set(result.plot_data) == {"region_effect_sizes"} + assert result.metadata["contrast_mode"] == "pairwise" + + +def test_score_regions_defaults_to_effect_size_only(monkeypatch): + contrast = ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["control"], + ) + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 8, + "valid_count": 10, + "mod_fraction": 0.8, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 2, + "valid_count": 10, + "mod_fraction": 0.2, + }, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: evidence.copy(), + ) + + result = region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + ) + + assert result.summary.loc[0, "region_id"] == "reg1" + assert result.summary.loc[0, "delta_fraction"] == pytest.approx(0.6) + assert result.metadata["test"] == "effect_size_only" + + +def test_score_regions_effect_size_only_group_vs_group_pools_conditions(monkeypatch): + contrast = ContrastSpec( + mode="group_vs_group", + numerator=["treated_a", "treated_b"], + denominator=["control_a", "control_b"], + ) + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "n1", + "condition": "treated_a", + "replicate": 1, + "modified_count": 4, + "valid_count": 10, + "mod_fraction": 0.4, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "n2", + "condition": "treated_b", + "replicate": 1, + "modified_count": 8, + "valid_count": 10, + "mod_fraction": 0.8, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "d1", + "condition": "control_a", + "replicate": 1, + "modified_count": 2, + "valid_count": 10, + "mod_fraction": 0.2, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "d2", + "condition": "control_b", + "replicate": 1, + "modified_count": 4, + "valid_count": 10, + "mod_fraction": 0.4, + }, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: evidence.copy(), + ) + + result = region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + test="effect_size_only", + ) + + summary_row = result.summary.iloc[0] + assert summary_row["region_id"] == "reg1" + assert summary_row["fraction"] == pytest.approx(0.6) + assert summary_row["reference_fraction"] == pytest.approx(0.3) + assert summary_row["delta_fraction"] == pytest.approx(0.3) + assert summary_row["numerator_replicate_n"] == 2 + assert summary_row["denominator_replicate_n"] == 2 + + +def test_score_regions_beta_binomial_adds_p_values(monkeypatch): + contrast = ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["control"], + ) + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 9, + "valid_count": 10, + "mod_fraction": 0.9, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 2, + "valid_count": 10, + "mod_fraction": 0.2, + }, + { + "region_id": "reg2", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 5, + "valid_count": 10, + "mod_fraction": 0.5, + }, + { + "region_id": "reg2", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 4, + "valid_count": 10, + "mod_fraction": 0.4, + }, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: evidence.copy(), + ) + + result = region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + test="beta_binomial", + ) + + for table in (result.regions, result.summary): + assert "p_value" in table.columns + assert "adjusted_p_value" in table.columns + assert ((table["p_value"] >= 0) & (table["p_value"] <= 1)).all() + assert ( + (table["adjusted_p_value"] >= 0) & (table["adjusted_p_value"] <= 1) + ).all() + + +def test_score_regions_beta_binomial_rejects_unsupported_multiple_testing(monkeypatch): + contrast = ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["control"], + ) + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 9, + "valid_count": 10, + "mod_fraction": 0.9, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 2, + "valid_count": 10, + "mod_fraction": 0.2, + }, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: evidence.copy(), + ) + + with pytest.raises(ValueError, match="fdr_bh"): + region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + test="beta_binomial", + multiple_testing="bonferroni", + ) + + +def test_score_regions_beta_binomial_uses_row_specific_denominator_counts(monkeypatch): + contrast = ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["control"], + ) + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 5, + "valid_count": 10, + "mod_fraction": 0.5, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 1, + "valid_count": 10, + "mod_fraction": 0.1, + }, + { + "region_id": "reg2", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 5, + "valid_count": 10, + "mod_fraction": 0.5, + }, + { + "region_id": "reg2", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 4, + "valid_count": 10, + "mod_fraction": 0.4, + }, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: evidence.copy(), + ) + + result = region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + test="beta_binomial", + ) + + row1 = result.summary.loc[result.summary["region_id"] == "reg1", "p_value"].iloc[0] + row2 = result.summary.loc[result.summary["region_id"] == "reg2", "p_value"].iloc[0] + assert row1 != row2 + + +def test_score_regions_beta_binomial_ranks_by_adjusted_p_value(monkeypatch): + contrast = ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["control"], + ) + evidence = pd.DataFrame( + [ + { + "region_id": "high_effect_low_significance", + "chromosome": "chr1", + "start": 0, + "end": 1, + "strand": "+", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 1, + "valid_count": 1, + "mod_fraction": 1.0, + }, + { + "region_id": "high_effect_low_significance", + "chromosome": "chr1", + "start": 0, + "end": 1, + "strand": "+", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 0, + "valid_count": 1, + "mod_fraction": 0.0, + }, + { + "region_id": "lower_effect_higher_significance", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 18, + "valid_count": 20, + "mod_fraction": 0.9, + }, + { + "region_id": "lower_effect_higher_significance", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 10, + "valid_count": 20, + "mod_fraction": 0.5, + }, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: evidence.copy(), + ) + + result = region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + test="beta_binomial", + ) + + assert result.summary.iloc[0]["region_id"] == "lower_effect_higher_significance" + assert ( + result.summary.iloc[0]["adjusted_p_value"] + <= result.summary.iloc[1]["adjusted_p_value"] + ) + assert ( + result.summary.iloc[0]["delta_fraction"] + < result.summary.iloc[1]["delta_fraction"] + ) + assert result.summary.iloc[0]["rank"] == 1 + + +def test_score_regions_beta_binomial_uses_p_value_tiebreak_when_adjusted_values_match( + monkeypatch, +): + contrast = ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["control"], + ) + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 8, + "valid_count": 10, + "mod_fraction": 0.8, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 2, + "valid_count": 10, + "mod_fraction": 0.2, + }, + { + "region_id": "reg2", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 5, + "valid_count": 10, + "mod_fraction": 0.5, + }, + { + "region_id": "reg2", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 4, + "valid_count": 10, + "mod_fraction": 0.4, + }, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: evidence.copy(), + ) + + original_add = region_contrasts._add_beta_binomial_scores + + def fake_add_beta_binomial_scores(regions_table, *, multiple_testing): + scored = original_add(regions_table, multiple_testing=multiple_testing) + scored.loc[scored["region_id"] == "reg1", "p_value"] = 0.02 + scored.loc[scored["region_id"] == "reg2", "p_value"] = 0.01 + scored["adjusted_p_value"] = 0.05 + return scored + + monkeypatch.setattr( + region_contrasts, + "_add_beta_binomial_scores", + fake_add_beta_binomial_scores, + ) + + result = region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + test="beta_binomial", + ) + + assert list(result.summary["region_id"]) == ["reg2", "reg1"] + assert list(result.summary["rank"]) == [1, 2] + + +def test_beta_binomial_two_sided_p_value_rejects_invalid_counts(): + with pytest.raises(ValueError, match="modified_count"): + region_contrasts._beta_binomial_two_sided_p_value(6, 5, 2.0, 2.0) + + +@pytest.mark.parametrize( + ("denominator_modified_count", "denominator_valid_count"), + [ + (-1, 5), + (3, -1), + (6, 5), + ], +) +def test_estimate_beta_binomial_prior_rejects_invalid_denominator_counts( + denominator_modified_count, denominator_valid_count +): + with pytest.raises(ValueError): + region_contrasts._estimate_beta_binomial_prior( + denominator_modified_count, + denominator_valid_count, + ) + + +@pytest.mark.parametrize( + ("modified_count", "valid_count"), + [ + (-1, 5), + (1, -1), + ], +) +def test_beta_binomial_two_sided_p_value_rejects_negative_counts( + modified_count, valid_count +): + with pytest.raises(ValueError): + region_contrasts._beta_binomial_two_sided_p_value( + modified_count, + valid_count, + 2.0, + 2.0, + ) + + +def test_score_regions_rejects_missing_denominator_condition(monkeypatch): + contrast = ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["cntrol"], + ) + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 8, + "valid_count": 10, + "mod_fraction": 0.8, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 2, + "valid_count": 10, + "mod_fraction": 0.2, + }, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: evidence.copy(), + ) + + with pytest.raises(ValueError, match="denominator.*cntrol"): + region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + ) + + +def test_score_regions_rejects_missing_numerator_condition(monkeypatch): + contrast = ContrastSpec( + mode="pairwise", + numerator=["trated"], + denominator=["control"], + ) + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 8, + "valid_count": 10, + "mod_fraction": 0.8, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 2, + "valid_count": 10, + "mod_fraction": 0.2, + }, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: evidence.copy(), + ) + + with pytest.raises(ValueError, match="numerator.*trated"): + region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + ) + + +def test_score_regions_modified_count_representation_ranks_by_delta_count(monkeypatch): + contrast = ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["control"], + ) + evidence = pd.DataFrame( + [ + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 11, + "valid_count": 20, + "mod_fraction": 0.55, + }, + { + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 10, + "strand": "+", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 1, + "valid_count": 10, + "mod_fraction": 0.1, + }, + { + "region_id": "reg2", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "n1", + "condition": "treated", + "replicate": 1, + "modified_count": 7, + "valid_count": 10, + "mod_fraction": 0.7, + }, + { + "region_id": "reg2", + "chromosome": "chr2", + "start": 10, + "end": 20, + "strand": "-", + "sample_id": "d1", + "condition": "control", + "replicate": 1, + "modified_count": 0, + "valid_count": 5, + "mod_fraction": 0.0, + }, + ] + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: evidence.copy(), + ) + + result = region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + representation="modified_count", + ) + + assert list(result.summary["region_id"]) == ["reg1", "reg2"] + assert list(result.summary["rank"]) == [1, 2] + assert result.summary.loc[0, "count"] == 11 + assert result.summary.loc[0, "reference_count"] == 1 + assert result.summary.loc[0, "delta_count"] == 10 + assert result.summary.loc[1, "delta_count"] == 7 + assert result.summary.loc[0, "log2_fc_count"] == pytest.approx( + math.log2((11 + 1e-6) / (1 + 1e-6)) + ) + assert result.metadata["representation"] == "modified_count" + + +def test_score_regions_effect_size_only_rejects_unsupported_contrast_mode(monkeypatch): + contrast = ContrastSpec( + mode="matched_pairwise", + numerator=["treated"], + denominator=["control"], + pairing_key="donor_id", + ) + + monkeypatch.setattr( + region_contrasts, + "build_region_evidence_table", + lambda **kwargs: pd.DataFrame(), + ) + + with pytest.raises(NotImplementedError, match="matched_pairwise"): + region_contrasts.score_regions( + samples=[], + regions="regions.bed", + motifs=["A,0"], + contrast=contrast, + test="effect_size_only", + ) + + +def test_score_regions_cluster_fraction_effect_size_only_ranks_largest_fraction_shift_first(): + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec(mode="pairwise", numerator=["15min"], denominator=["NS"]), + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=_mock_cluster_occupancy_evidence(), + ) + + first_row = result.regions.iloc[0] + assert first_row["region_id"] == "reg1" + assert first_row["cluster"] == "C1" + assert first_row["fraction"] == pytest.approx(0.75) + assert first_row["reference_fraction"] == pytest.approx(0.25) + assert first_row["delta_fraction"] == pytest.approx(0.5) + assert first_row["numerator_replicate_n"] == 2 + assert first_row["denominator_replicate_n"] == 2 + assert result.summary.iloc[0]["delta_fraction"] == pytest.approx(0.5) + + +def test_score_regions_cluster_fraction_fraction_test_adds_p_values(): + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec(mode="pairwise", numerator=["15min"], denominator=["NS"]), + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="fraction_test", + occupancy_table=_mock_cluster_occupancy_evidence(), + ) + + assert {"p_value", "adjusted_p_value"} <= set(result.regions.columns) + assert ((result.regions["p_value"] >= 0) & (result.regions["p_value"] <= 1)).all() + assert ( + (result.regions["adjusted_p_value"] >= 0) + & (result.regions["adjusted_p_value"] <= 1) + ).all() + + +def test_score_regions_cluster_occupancy_dominant_cluster_returns_descriptive_summary_only(): + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec(mode="pairwise", numerator=["15min"], denominator=["NS"]), + analysis_unit="cluster_occupancy", + representation="dominant_cluster", + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=_mock_cluster_occupancy_evidence(), + ) + + reg1 = result.summary.loc[result.summary["region_id"] == "reg1"].iloc[0] + reg2 = result.summary.loc[result.summary["region_id"] == "reg2"].iloc[0] + assert reg1["dominant_cluster"] == "C1" + assert reg1["reference_dominant_cluster"] == "C2" + assert reg2["dominant_cluster"] == "C1" + assert pd.isna(reg2["reference_dominant_cluster"]) + assert reg2["dominant_cluster_changed"] + assert "p_value" not in result.regions.columns + + +def test_score_regions_cluster_occupancy_dominant_cluster_rejects_missing_requested_conditions(): + occupancy_table = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "tx1", + "condition": "15min", + "dominant_cluster": "C1", + } + ] + ) + + with pytest.raises(ValueError, match="Missing denominator evidence"): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="pairwise", numerator=["15min"], denominator=["NS"] + ), + analysis_unit="cluster_occupancy", + representation="dominant_cluster", + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=occupancy_table, + ) + + +def test_score_regions_cluster_occupancy_dominant_cluster_rejects_region_specific_missing_side(): + occupancy_table = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "tx1", + "condition": "15min", + "dominant_cluster": "C1", + }, + { + "region_id": "reg2", + "sample_id": "ns1", + "condition": "NS", + "dominant_cluster": "C2", + }, + ] + ) + + with pytest.raises(ValueError, match="Missing dominant_cluster evidence"): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="pairwise", numerator=["15min"], denominator=["NS"] + ), + analysis_unit="cluster_occupancy", + representation="dominant_cluster", + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=occupancy_table, + ) + + +def test_score_regions_cluster_occupancy_cluster_entropy_returns_descriptive_summary_only(): + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec(mode="pairwise", numerator=["15min"], denominator=["NS"]), + analysis_unit="cluster_occupancy", + representation="cluster_entropy", + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=_mock_cluster_occupancy_evidence(), + ) + + reg1 = result.summary.loc[result.summary["region_id"] == "reg1"].iloc[0] + reg2 = result.summary.loc[result.summary["region_id"] == "reg2"].iloc[0] + expected_entropy = ( + -( + 0.8 * math.log2(0.8) + + 0.2 * math.log2(0.2) + + 0.7 * math.log2(0.7) + + 0.3 * math.log2(0.3) + ) + / 2.0 + ) + assert reg1["cluster_entropy"] == pytest.approx(expected_entropy) + assert reg1["reference_cluster_entropy"] == pytest.approx(expected_entropy) + assert reg1["delta_cluster_entropy"] == pytest.approx(0.0) + assert reg2["cluster_entropy"] == pytest.approx(0.0) + assert reg2["reference_cluster_entropy"] == pytest.approx(0.0) + assert "p_value" not in result.regions.columns + + +def test_score_regions_cluster_occupancy_rejects_matched_pairwise(): + with pytest.raises(NotImplementedError, match="matched_pairwise"): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["15min"], + denominator=["NS"], + pairing_key="donor_id", + ), + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=_mock_cluster_occupancy_evidence(), + ) + + +@pytest.mark.parametrize( + "representation", + ["cluster_fraction", "dominant_cluster", "cluster_entropy"], +) +def test_score_regions_cluster_occupancy_rejects_unsupported_contrast_modes_for_all_representations( + representation, +): + with pytest.raises(NotImplementedError, match="matched_pairwise"): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["15min"], + denominator=["NS"], + pairing_key="donor_id", + ), + analysis_unit="cluster_occupancy", + representation=representation, + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=_mock_cluster_occupancy_evidence(), + ) + + +def test_score_regions_cluster_occupancy_rejects_fraction_test_for_dominant_cluster(): + with pytest.raises(ValueError, match="cluster_fraction"): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="pairwise", numerator=["15min"], denominator=["NS"] + ), + analysis_unit="cluster_occupancy", + representation="dominant_cluster", + signal_source="cluster_occupancy", + test="fraction_test", + occupancy_table=_mock_cluster_occupancy_evidence(), + ) + + +def test_score_regions_cluster_occupancy_group_vs_group_aggregates_condition_groups(): + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="group_vs_group", + numerator=["treated_a", "treated_b"], + denominator=["control_a", "control_b"], + ), + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=_mock_group_vs_group_cluster_fraction_evidence(), + ) + + row = result.regions.iloc[0] + assert row["fraction"] == pytest.approx(0.6) + assert row["reference_fraction"] == pytest.approx(0.3) + assert row["delta_fraction"] == pytest.approx(0.3) + assert row["numerator_replicate_n"] == 4 + assert row["denominator_replicate_n"] == 4 + assert result.summary.iloc[0]["delta_fraction"] == pytest.approx(0.3) + + +def test_score_regions_cluster_occupancy_group_vs_group_uses_sample_weighted_dominant_cluster(): + occupancy_table = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "na1", + "condition": "treated_a", + "dominant_cluster": "C1", + }, + { + "region_id": "reg1", + "sample_id": "nb1", + "condition": "treated_b", + "dominant_cluster": "C2", + }, + { + "region_id": "reg1", + "sample_id": "nb2", + "condition": "treated_b", + "dominant_cluster": "C2", + }, + { + "region_id": "reg1", + "sample_id": "ca1", + "condition": "control_a", + "dominant_cluster": "C1", + }, + { + "region_id": "reg1", + "sample_id": "ca2", + "condition": "control_a", + "dominant_cluster": "C1", + }, + ] + ) + + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="group_vs_group", + numerator=["treated_a", "treated_b"], + denominator=["control_a"], + ), + analysis_unit="cluster_occupancy", + representation="dominant_cluster", + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=occupancy_table, + ) + + row = result.regions.iloc[0] + assert row["dominant_cluster"] == "C2" + assert row["reference_dominant_cluster"] == "C1" + assert row["dominant_cluster_changed"] + assert row["numerator_replicate_n"] == 3 + assert row["denominator_replicate_n"] == 2 + + +def test_score_regions_cluster_fraction_treats_missing_sample_cluster_rows_as_zero(): + occupancy_table = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "ns1", + "condition": "NS", + "cluster": "C1", + "fraction": 1.0, + }, + { + "region_id": "reg1", + "sample_id": "ns2", + "condition": "NS", + "cluster": "C1", + "fraction": 1.0, + }, + { + "region_id": "reg1", + "sample_id": "tx1", + "condition": "15min", + "cluster": "C1", + "fraction": 1.0, + }, + { + "region_id": "reg1", + "sample_id": "tx1", + "condition": "15min", + "cluster": "C2", + "fraction": 0.0, + }, + { + "region_id": "reg1", + "sample_id": "tx2", + "condition": "15min", + "cluster": "C1", + "fraction": 0.0, + }, + { + "region_id": "reg1", + "sample_id": "tx2", + "condition": "15min", + "cluster": "C2", + "fraction": 1.0, + }, + ] + ) + + result = region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec(mode="pairwise", numerator=["15min"], denominator=["NS"]), + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="fraction_test", + occupancy_table=occupancy_table, + ) + + c2_row = result.regions.loc[result.regions["cluster"] == "C2"].iloc[0] + assert c2_row["fraction"] == pytest.approx(0.5) + assert c2_row["reference_fraction"] == pytest.approx(0.0) + assert c2_row["numerator_replicate_n"] == 2 + assert c2_row["denominator_replicate_n"] == 2 + assert c2_row["numerator_sample_values"] == (0.0, 1.0) + assert c2_row["denominator_sample_values"] == (0.0, 0.0) + assert c2_row["p_value"] == pytest.approx(0.5) + + +@pytest.mark.parametrize( + ("representation", "column", "value", "message"), + [ + ( + "cluster_fraction", + "fraction", + 1.2, + "fraction values must be finite and between 0 and 1", + ), + ( + "cluster_fraction", + "fraction", + float("nan"), + "fraction values must be finite and between 0 and 1", + ), + ( + "cluster_entropy", + "cluster_entropy", + float("inf"), + "cluster_entropy values must be finite and >= 0", + ), + ( + "cluster_entropy", + "cluster_entropy", + -0.1, + "cluster_entropy values must be finite and >= 0", + ), + ], +) +def test_score_regions_cluster_occupancy_rejects_invalid_numeric_values( + representation, + column, + value, + message, +): + occupancy_table = pd.DataFrame( + [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + column: value, + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "15min", + column: 0.5 if column == "fraction" else 0.5, + }, + ] + ) + if representation == "cluster_fraction": + occupancy_table["cluster"] = ["C1", "C1"] + + with pytest.raises(ValueError, match=message): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="pairwise", numerator=["15min"], denominator=["NS"] + ), + analysis_unit="cluster_occupancy", + representation=representation, + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=occupancy_table, + ) + + +def test_score_regions_cluster_occupancy_rejects_missing_occupancy_columns(): + with pytest.raises(ValueError, match="occupancy_table requires columns"): + region_contrasts.score_regions( + samples=[], + regions=None, + motifs=[], + contrast=ContrastSpec( + mode="pairwise", numerator=["15min"], denominator=["NS"] + ), + analysis_unit="cluster_occupancy", + representation="cluster_fraction", + signal_source="cluster_occupancy", + test="effect_size_only", + occupancy_table=pd.DataFrame({"region_id": ["reg1"]}), + ) diff --git a/tests/test_region_discovery.py b/tests/test_region_discovery.py new file mode 100644 index 0000000..0a32b47 --- /dev/null +++ b/tests/test_region_discovery.py @@ -0,0 +1,2628 @@ +from __future__ import annotations + +import pandas as pd +import pytest + +from dimelo import global_analysis, region_contrasts, region_discovery +from dimelo.models import ContrastSpec, RegionDiscoveryResult, SampleSpec + + +def _mock_window_summary() -> pd.DataFrame: + return pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 10, + "valid_count": 20, + "window_fraction": 0.5, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 18, + "valid_count": 20, + "window_fraction": 0.9, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 12, + "valid_count": 20, + "window_fraction": 0.6, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 14, + "valid_count": 20, + "window_fraction": 0.7, + }, + ] + ) + + +def _mock_paired_window_summary() -> pd.DataFrame: + return pd.DataFrame( + [ + { + "sample_id": "t1", + "condition": "targeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 10, + "valid_count": 20, + "window_fraction": 0.5, + }, + { + "sample_id": "t1_rep2", + "condition": "targeting", + "replicate": 2, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "d1", + "condition": "nontargeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 4, + "valid_count": 20, + "window_fraction": 0.2, + }, + { + "sample_id": "t2", + "condition": "targeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 6, + "valid_count": 20, + "window_fraction": 0.3, + }, + { + "sample_id": "d2", + "condition": "nontargeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 2, + "valid_count": 20, + "window_fraction": 0.1, + }, + { + "sample_id": "t3", + "condition": "targeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 100, + "valid_count": 100, + "window_fraction": 1.0, + }, + ] + ) + + +def _mock_paired_pairwise_window_summary() -> pd.DataFrame: + return pd.DataFrame( + [ + { + "sample_id": "t1", + "condition": "targeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 4, + "valid_count": 5, + "window_fraction": 0.8, + }, + { + "sample_id": "t1_rep2", + "condition": "targeting", + "replicate": 2, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 4, + "valid_count": 5, + "window_fraction": 0.8, + }, + { + "sample_id": "d1", + "condition": "nontargeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "t2", + "condition": "targeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 7, + "valid_count": 10, + "window_fraction": 0.7, + }, + { + "sample_id": "d2", + "condition": "nontargeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "t1", + "condition": "targeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 1, + "valid_count": 5, + "window_fraction": 0.2, + }, + { + "sample_id": "t1_rep2", + "condition": "targeting", + "replicate": 2, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 1, + "valid_count": 5, + "window_fraction": 0.2, + }, + { + "sample_id": "d1", + "condition": "nontargeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "t2", + "condition": "targeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 3, + "valid_count": 10, + "window_fraction": 0.3, + }, + { + "sample_id": "d2", + "condition": "nontargeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "t3", + "condition": "targeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 5, + "valid_count": 10, + "window_fraction": 0.5, + }, + ] + ) + + +def _paired_samplespecs() -> list[SampleSpec]: + return [ + SampleSpec( + sample_id="t1", + condition="targeting", + extract_h5="t1.h5", + metadata={"pileup_path": "t1.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="t1_rep2", + condition="targeting", + extract_h5="t1_rep2.h5", + metadata={"pileup_path": "t1_rep2.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="d1", + condition="nontargeting", + extract_h5="d1.h5", + metadata={"pileup_path": "d1.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="t2", + condition="targeting", + extract_h5="t2.h5", + metadata={"pileup_path": "t2.bed.gz", "pair_id": "pair-2"}, + ), + SampleSpec( + sample_id="d2", + condition="nontargeting", + extract_h5="d2.h5", + metadata={"pileup_path": "d2.bed.gz", "pair_id": "pair-2"}, + ), + SampleSpec( + sample_id="t3", + condition="targeting", + extract_h5="t3.h5", + metadata={"pileup_path": "t3.bed.gz", "pair_id": "pair-3"}, + ), + ] + + +def _merge_helper_hits() -> pd.DataFrame: + return pd.DataFrame( + [ + { + "chromosome": "chr2", + "start": 100, + "end": 200, + "strand": "+", + "window_id": "chr2:100-200", + "score_value": 0.95, + "p_value": 0.01, + "adjusted_p_value": 0.01, + "rank": 1, + "modified_count": 19, + "valid_count": 20, + "window_fraction": 0.95, + }, + { + "chromosome": "chr1", + "start": 0, + "end": 100, + "strand": "+", + "window_id": "chr1:0-100", + "score_value": 0.2, + "p_value": 0.04, + "adjusted_p_value": 0.08, + "rank": 2, + "modified_count": 2, + "valid_count": 10, + "window_fraction": 0.2, + "numerator_modified_count": 2, + "numerator_valid_count": 4, + "denominator_modified_count": 1, + "denominator_valid_count": 4, + }, + { + "chromosome": "chr1", + "start": 101, + "end": 200, + "strand": "+", + "window_id": "chr1:101-200", + "score_value": 0.4, + "p_value": 0.03, + "adjusted_p_value": 0.06, + "rank": 3, + "modified_count": 6, + "valid_count": 10, + "window_fraction": 0.6, + "numerator_modified_count": 4, + "numerator_valid_count": 6, + "denominator_modified_count": 2, + "denominator_valid_count": 6, + }, + { + "chromosome": "chr1", + "start": 500, + "end": 600, + "strand": "-", + "window_id": "chr1:500-600", + "score_value": 0.8, + "p_value": 0.05, + "adjusted_p_value": 0.07, + "rank": 4, + "modified_count": 8, + "valid_count": 10, + "window_fraction": 0.8, + }, + ] + ) + + +def test_scan_genome_basic_behavior_with_mocked_window_summary(monkeypatch): + captured = {} + + def fake_build_window_summary(**kwargs): + captured.update(kwargs) + return _mock_window_summary() + + monkeypatch.setattr( + global_analysis, "build_window_summary", fake_build_window_summary + ) + + result = region_discovery.scan_genome( + samples=[ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="treated", + extract_h5="s2.h5", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ], + motifs=["A,0"], + genome_sizes={"chr1": 2000}, + window_size=1000, + step_size=1000, + contrast=ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + reference_condition="NS", + ), + score="effect_size_only", + min_coverage=5, + ) + + assert isinstance(result, RegionDiscoveryResult) + assert captured["motifs"] == ["A,0"] + assert captured["window_size"] == 1000 + assert captured["step_size"] == 1000 + assert list(result.windows["window_id"]) == ["chr1:0-1000", "chr1:1000-2000"] + assert result.windows.loc[0, "score_value"] == pytest.approx(0.4) + assert list(result.hits["rank"]) == [1, 2] + assert list(result.hits["window_id"]) == ["chr1:0-1000", "chr1:1000-2000"] + assert set(result.plot_data) == {"window_score_table", "top_hits_table"} + assert result.metadata == { + "analysis_unit": "ensemble_region", + "representation": "modified_fraction", + "signal_source": "pileup_counts", + "score": "effect_size_only", + "window_size": 1000, + "step_size": 1000, + "min_coverage": 5, + "merge_hits": False, + "merge_distance": 0, + "motifs": ["A,0"], + "include_contigs": None, + "exclude_contigs": None, + "contrast_mode": "pairwise", + "contrast_numerator": ["treated"], + "contrast_denominator": ["NS"], + } + + +def test_scan_genome_matched_pairwise_uses_only_complete_pairs(monkeypatch): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: _mock_paired_window_summary(), + ) + + result = region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + assert result.metadata["pairing_policy"] == "complete_pairs_only" + assert result.metadata["n_pairs_used"] == 2 + assert result.metadata["n_pairs_dropped"] == 1 + assert result.windows.loc[0, "modified_count"] == 26 + assert result.windows.loc[0, "valid_count"] == 90 + assert result.windows.loc[0, "window_fraction"] == pytest.approx(26 / 90) + assert result.windows.loc[0, "score_value"] == pytest.approx(7 / 30) + + +def test_scan_genome_matched_pairwise_ranks_by_mean_abs_delta(monkeypatch): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: _mock_paired_pairwise_window_summary(), + ) + + result = region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + assert list(result.hits["window_id"]) == ["chr1:0-500", "chr1:500-1000"] + assert result.hits.loc[0, "mean_delta"] == pytest.approx(0.35) + assert result.hits.loc[0, "mean_abs_delta"] == pytest.approx(0.35) + assert result.hits.loc[0, "sign_agreement"] == pytest.approx(1.0) + assert result.metadata["paired_mode"] == "matched_pairwise" + assert result.metadata["rank_by"] == "mean_abs_delta" + + +def test_scan_genome_matched_pairwise_errors_on_missing_pairs_in_strict_mode( + monkeypatch, +): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: _mock_paired_window_summary(), + ) + + with pytest.raises(ValueError, match="incomplete matched units"): + region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + pairing_policy="error_on_missing", + ) + + +def test_scan_genome_matched_pairwise_applies_min_coverage_filtering(monkeypatch): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: _mock_paired_pairwise_window_summary(), + ) + + result = region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + min_coverage=50, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + assert result.hits.empty + assert pd.isna(result.windows.loc[0, "rank"]) + assert pd.isna(result.windows.loc[0, "score_value"]) + + +def test_scan_genome_matched_pairwise_reranks_surviving_hits_after_coverage_filter( + monkeypatch, +): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: pd.DataFrame( + [ + { + "sample_id": "t1", + "condition": "targeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 9, + "valid_count": 10, + "window_fraction": 0.9, + }, + { + "sample_id": "d1", + "condition": "nontargeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 1, + "valid_count": 10, + "window_fraction": 0.1, + }, + { + "sample_id": "t2", + "condition": "targeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 12, + "valid_count": 60, + "window_fraction": 0.2, + }, + { + "sample_id": "d2", + "condition": "nontargeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 6, + "valid_count": 60, + "window_fraction": 0.1, + }, + { + "sample_id": "t1", + "condition": "targeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-1500", + "chromosome": "chr1", + "start": 1000, + "end": 1500, + "strand": ".", + "modified_count": 10, + "valid_count": 60, + "window_fraction": 1 / 6, + }, + { + "sample_id": "d1", + "condition": "nontargeting", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-1500", + "chromosome": "chr1", + "start": 1000, + "end": 1500, + "strand": ".", + "modified_count": 6, + "valid_count": 60, + "window_fraction": 0.1, + }, + ] + ), + ) + result = region_discovery.scan_genome( + samples=[ + SampleSpec( + sample_id="t1", + condition="targeting", + extract_h5="t1.h5", + metadata={"pileup_path": "t1.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="d1", + condition="nontargeting", + extract_h5="d1.h5", + metadata={"pileup_path": "d1.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="t2", + condition="targeting", + extract_h5="t2.h5", + metadata={"pileup_path": "t2.bed.gz", "pair_id": "pair-2"}, + ), + SampleSpec( + sample_id="d2", + condition="nontargeting", + extract_h5="d2.h5", + metadata={"pileup_path": "d2.bed.gz", "pair_id": "pair-2"}, + ), + ], + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + window_size=500, + step_size=500, + min_coverage=50, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + assert list(result.hits["window_id"]) == ["chr1:500-1000", "chr1:1000-1500"] + assert list(result.hits["rank"]) == [1, 2] + covered_windows = result.windows.loc[result.windows["valid_count"] >= 50] + assert list(covered_windows["window_id"]) == list(result.hits["window_id"]) + assert list(covered_windows["rank"]) == list(result.hits["rank"]) + + +def test_scan_genome_rejects_invalid_pairing_policy(monkeypatch): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: _mock_paired_pairwise_window_summary(), + ) + + with pytest.raises(ValueError, match="pairing_policy"): + region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + pairing_policy="complete_pairs_typo", + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + +def test_scan_genome_matched_pairwise_rejects_merge_hits(monkeypatch): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: _mock_paired_pairwise_window_summary(), + ) + + with pytest.raises(ValueError, match="does not support merge_hits=True"): + region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + merge_hits=True, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + +def test_scan_genome_matched_pairwise_rejects_multi_condition_sides(monkeypatch): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: _mock_paired_pairwise_window_summary(), + ) + + with pytest.raises( + ValueError, match="exactly one numerator and one denominator condition" + ): + region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting", "backup"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + +def test_build_paired_window_table_collapses_duplicate_rows(): + paired_table, pairing_meta = region_discovery._build_paired_window_table( + _mock_paired_window_summary(), + samples=_paired_samplespecs(), + pairing_key="pair_id", + required_conditions=["targeting", "nontargeting"], + pairing_policy="complete_pairs_only", + ) + + collapsed = paired_table.loc[ + (paired_table["window_id"] == "chr1:0-500") + & (paired_table["pair_id"] == "pair-1") + & (paired_table["condition"] == "targeting") + ] + + assert len(collapsed) == 1 + assert collapsed.iloc[0]["modified_count"] == 14 + assert collapsed.iloc[0]["valid_count"] == 30 + assert collapsed.iloc[0]["window_fraction"] == pytest.approx(14 / 30) + assert pairing_meta == {"n_pairs_used": 2, "n_pairs_dropped": 1} + + +def _paired_time_course_samplespecs() -> list[SampleSpec]: + return [ + SampleSpec( + sample_id="tp1_0", + condition="0min", + extract_h5="tp1_0.h5", + metadata={"pileup_path": "tp1_0.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="tp1_15", + condition="15min", + extract_h5="tp1_15.h5", + metadata={"pileup_path": "tp1_15.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="tp1_30", + condition="30min", + extract_h5="tp1_30.h5", + metadata={"pileup_path": "tp1_30.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="tp2_0", + condition="0min", + extract_h5="tp2_0.h5", + metadata={"pileup_path": "tp2_0.bed.gz", "pair_id": "pair-2"}, + ), + SampleSpec( + sample_id="tp2_15", + condition="15min", + extract_h5="tp2_15.h5", + metadata={"pileup_path": "tp2_15.bed.gz", "pair_id": "pair-2"}, + ), + SampleSpec( + sample_id="tp2_30", + condition="30min", + extract_h5="tp2_30.h5", + metadata={"pileup_path": "tp2_30.bed.gz", "pair_id": "pair-2"}, + ), + SampleSpec( + sample_id="tp3_0", + condition="0min", + extract_h5="tp3_0.h5", + metadata={"pileup_path": "tp3_0.bed.gz", "pair_id": "pair-3"}, + ), + SampleSpec( + sample_id="tp3_15", + condition="15min", + extract_h5="tp3_15.h5", + metadata={"pileup_path": "tp3_15.bed.gz", "pair_id": "pair-3"}, + ), + ] + + +def _mock_paired_time_course_window_summary() -> pd.DataFrame: + return pd.DataFrame( + [ + { + "sample_id": "tp1_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 2, + "valid_count": 10, + "window_fraction": 0.2, + }, + { + "sample_id": "tp1_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 7, + "valid_count": 10, + "window_fraction": 0.7, + }, + { + "sample_id": "tp1_30", + "condition": "30min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 8, + "valid_count": 10, + "window_fraction": 0.8, + }, + { + "sample_id": "tp2_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 1, + "valid_count": 10, + "window_fraction": 0.1, + }, + { + "sample_id": "tp2_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 6, + "valid_count": 10, + "window_fraction": 0.6, + }, + { + "sample_id": "tp2_30", + "condition": "30min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 6, + "valid_count": 10, + "window_fraction": 0.6, + }, + { + "sample_id": "tp3_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 3, + "valid_count": 10, + "window_fraction": 0.3, + }, + { + "sample_id": "tp3_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 5, + "valid_count": 10, + "window_fraction": 0.5, + }, + { + "sample_id": "tp1_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 1, + "valid_count": 10, + "window_fraction": 0.1, + }, + { + "sample_id": "tp1_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 2, + "valid_count": 10, + "window_fraction": 0.2, + }, + { + "sample_id": "tp1_30", + "condition": "30min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "tp2_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 2, + "valid_count": 10, + "window_fraction": 0.2, + }, + { + "sample_id": "tp2_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 3, + "valid_count": 10, + "window_fraction": 0.3, + }, + { + "sample_id": "tp2_30", + "condition": "30min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 3, + "valid_count": 10, + "window_fraction": 0.3, + }, + { + "sample_id": "tp3_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "tp3_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 6, + "valid_count": 10, + "window_fraction": 0.6, + }, + ] + ) + + +def test_scan_genome_time_course_ranks_by_trajectory_amplitude_mean(monkeypatch): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: _mock_paired_time_course_window_summary(), + ) + + result = region_discovery.scan_genome( + samples=_paired_time_course_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="time_course", + time_order=["0min", "15min", "30min"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + assert result.hits.loc[0, "trajectory_amplitude_mean"] == pytest.approx(0.55) + assert result.hits.loc[0, "trajectory_amplitude_sd"] == pytest.approx(0.05) + assert result.metadata["pairing_key"] == "pair_id" + assert result.metadata["paired_mode"] == "time_course" + assert result.metadata["time_order"] == ["0min", "15min", "30min"] + assert result.metadata["rank_by"] == "trajectory_amplitude_mean" + + +def test_scan_genome_time_course_errors_when_time_order_conditions_are_missing( + monkeypatch, +): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: _mock_paired_time_course_window_summary(), + ) + + with pytest.raises(ValueError, match="time_order"): + region_discovery.scan_genome( + samples=_paired_time_course_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="time_course", + time_order=["0min", "15min", "60min"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + +def test_scan_genome_time_course_filters_incomplete_trajectories_per_window( + monkeypatch, +): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: pd.DataFrame( + [ + { + "sample_id": "tp1_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 2, + "valid_count": 10, + "window_fraction": 0.2, + }, + { + "sample_id": "tp1_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "tp1_30", + "condition": "30min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 8, + "valid_count": 10, + "window_fraction": 0.8, + }, + { + "sample_id": "tp2_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 1, + "valid_count": 10, + "window_fraction": 0.1, + }, + { + "sample_id": "tp2_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 3, + "valid_count": 10, + "window_fraction": 0.3, + }, + { + "sample_id": "tp2_30", + "condition": "30min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 6, + "valid_count": 10, + "window_fraction": 0.6, + }, + { + "sample_id": "tp1_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 2, + "valid_count": 10, + "window_fraction": 0.2, + }, + { + "sample_id": "tp1_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 5, + "valid_count": 10, + "window_fraction": 0.5, + }, + { + "sample_id": "tp2_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 1, + "valid_count": 10, + "window_fraction": 0.1, + }, + { + "sample_id": "tp2_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 2, + "valid_count": 10, + "window_fraction": 0.2, + }, + { + "sample_id": "tp2_30", + "condition": "30min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": ".", + "modified_count": 9, + "valid_count": 10, + "window_fraction": 0.9, + }, + ] + ), + ) + + result = region_discovery.scan_genome( + samples=_paired_time_course_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="time_course", + time_order=["0min", "15min", "30min"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + first_window = result.hits.loc[result.hits["window_id"] == "chr1:0-500"].iloc[0] + second_window = result.hits.loc[result.hits["window_id"] == "chr1:500-1000"].iloc[0] + assert first_window["n_pairs_used"] == 2 + assert second_window["n_pairs_used"] == 1 + assert second_window["trajectory_amplitude_mean"] == pytest.approx(0.8) + + +def test_scan_genome_time_course_ignores_extra_conditions_outside_time_order( + monkeypatch, +): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: pd.DataFrame( + [ + { + "sample_id": "tp1_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 2, + "valid_count": 10, + "window_fraction": 0.2, + }, + { + "sample_id": "tp1_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 5, + "valid_count": 10, + "window_fraction": 0.5, + }, + { + "sample_id": "tp1_30", + "condition": "30min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 6, + "valid_count": 10, + "window_fraction": 0.6, + }, + { + "sample_id": "tp1_45", + "condition": "45min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 10, + "valid_count": 10, + "window_fraction": 1.0, + }, + { + "sample_id": "tp2_0", + "condition": "0min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 1, + "valid_count": 10, + "window_fraction": 0.1, + }, + { + "sample_id": "tp2_15", + "condition": "15min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 3, + "valid_count": 10, + "window_fraction": 0.3, + }, + { + "sample_id": "tp2_30", + "condition": "30min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "tp2_45", + "condition": "45min", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": ".", + "modified_count": 0, + "valid_count": 10, + "window_fraction": 0.0, + }, + ] + ), + ) + + result = region_discovery.scan_genome( + samples=[ + SampleSpec( + sample_id="tp1_0", + condition="0min", + extract_h5="tp1_0.h5", + metadata={"pileup_path": "tp1_0.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="tp1_15", + condition="15min", + extract_h5="tp1_15.h5", + metadata={"pileup_path": "tp1_15.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="tp1_30", + condition="30min", + extract_h5="tp1_30.h5", + metadata={"pileup_path": "tp1_30.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="tp1_45", + condition="45min", + extract_h5="tp1_45.h5", + metadata={"pileup_path": "tp1_45.bed.gz", "pair_id": "pair-1"}, + ), + SampleSpec( + sample_id="tp2_0", + condition="0min", + extract_h5="tp2_0.h5", + metadata={"pileup_path": "tp2_0.bed.gz", "pair_id": "pair-2"}, + ), + SampleSpec( + sample_id="tp2_15", + condition="15min", + extract_h5="tp2_15.h5", + metadata={"pileup_path": "tp2_15.bed.gz", "pair_id": "pair-2"}, + ), + SampleSpec( + sample_id="tp2_30", + condition="30min", + extract_h5="tp2_30.h5", + metadata={"pileup_path": "tp2_30.bed.gz", "pair_id": "pair-2"}, + ), + SampleSpec( + sample_id="tp2_45", + condition="45min", + extract_h5="tp2_45.h5", + metadata={"pileup_path": "tp2_45.bed.gz", "pair_id": "pair-2"}, + ), + ], + motifs=["A,0"], + genome_sizes={"chr1": 500}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="time_course", + time_order=["0min", "15min", "30min"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + + assert result.hits.loc[0, "trajectory_amplitude_mean"] == pytest.approx(0.35) + assert result.hits.loc[0, "trajectory_amplitude_sd"] == pytest.approx(0.05) + + +def test_scan_genome_time_course_errors_on_missing_pairing_key(monkeypatch): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: _mock_paired_window_summary(), + ) + + with pytest.raises(ValueError, match="pairing_key"): + region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="time_course", + time_order=["0min", "15min"], + ), + score="effect_size_only", + ) + + +def test_merge_adjacent_hits_preserves_rank_order_and_merges_counts(): + merged = region_discovery.merge_adjacent_hits( + _merge_helper_hits(), merge_distance=1 + ) + + assert list(merged["window_id"]) == ["chr2:100-200", "chr1:0-200", "chr1:500-600"] + assert list(merged["rank"]) == [1, 2, 4] + assert list(merged["merged_window_count"]) == [1, 2, 1] + assert list(merged["score_value"]) == [ + pytest.approx(0.95), + pytest.approx(0.3), + pytest.approx(0.8), + ] + assert list(merged["p_value"]) == [ + pytest.approx(0.01), + pytest.approx(0.03), + pytest.approx(0.05), + ] + assert list(merged["adjusted_p_value"]) == [ + pytest.approx(0.01), + pytest.approx(0.06), + pytest.approx(0.07), + ] + assert list(merged["modified_count"]) == [19, 8, 8] + assert list(merged["valid_count"]) == [20, 20, 10] + assert list(merged["window_fraction"]) == [ + pytest.approx(0.95), + pytest.approx(0.4), + pytest.approx(0.8), + ] + assert pd.isna(merged.loc[0, "numerator_modified_count"]) + assert merged.loc[1, "numerator_modified_count"] == 6 + assert pd.isna(merged.loc[2, "numerator_modified_count"]) + assert pd.isna(merged.loc[0, "numerator_valid_count"]) + assert merged.loc[1, "numerator_valid_count"] == 10 + assert pd.isna(merged.loc[2, "numerator_valid_count"]) + assert pd.isna(merged.loc[0, "denominator_modified_count"]) + assert merged.loc[1, "denominator_modified_count"] == 3 + assert pd.isna(merged.loc[2, "denominator_modified_count"]) + assert pd.isna(merged.loc[0, "denominator_valid_count"]) + assert merged.loc[1, "denominator_valid_count"] == 10 + assert pd.isna(merged.loc[2, "denominator_valid_count"]) + + +def test_hits_to_bed_projects_required_columns_in_order(): + merged = region_discovery.merge_adjacent_hits( + _merge_helper_hits(), merge_distance=1 + ) + bed = region_discovery.hits_to_bed(merged) + + assert list(bed.columns) == ["chrom", "start", "end", "name", "score", "strand"] + assert pd.api.types.is_integer_dtype(bed["score"]) + assert bed["score"].between(0, 1000).all() + assert bed.to_dict(orient="records") == [ + { + "chrom": "chr2", + "start": 100, + "end": 200, + "name": "chr2:100-200", + "score": 950, + "strand": "+", + }, + { + "chrom": "chr1", + "start": 0, + "end": 200, + "name": "chr1:0-200", + "score": 300, + "strand": "+", + }, + { + "chrom": "chr1", + "start": 500, + "end": 600, + "name": "chr1:500-600", + "score": 800, + "strand": "-", + }, + ] + + +def test_discovery_bed_handoff_into_region_contrasts(tmp_path, monkeypatch): + hits = _merge_helper_hits() + bed_df = region_discovery.hits_to_bed(hits) + bed_path = tmp_path / "discovered_hits.bed" + bed_df.to_csv(bed_path, sep="\t", header=False, index=False) + + counts_by_pileup = { + "s1.bed.gz": [(1, 10), (1, 10), (8, 10)], + "s2.bed.gz": [(9, 10), (2, 10), (1, 10)], + } + + def fake_regions_to_list( + function_handle, + regions, + window_size=None, + quiet=True, + cores=None, + split_large_regions=False, + ): + pileup_path = function_handle.keywords["bedmethyl_file"] + regions_dict = region_contrasts.utils.regions_dict_from_input( + regions, window_size + ) + n_regions = sum(len(region_list) for region_list in regions_dict.values()) + base_counts = counts_by_pileup[pileup_path] + if len(base_counts) >= n_regions: + return base_counts[:n_regions] + repeats = (n_regions // len(base_counts)) + 1 + return (base_counts * repeats)[:n_regions] + + monkeypatch.setattr( + region_contrasts.load_processed, "regions_to_list", fake_regions_to_list + ) + + samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="treated", + extract_h5="s2.h5", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + contrast = ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + reference_condition="NS", + ) + + result = region_contrasts.score_regions( + samples=samples, + regions=bed_path, + motifs=["A,0"], + contrast=contrast, + test="effect_size_only", + ) + + assert len(result.regions) == 4 + assert set(result.regions["region_id"]) == { + "chr2:100-200,+", + "chr1:0-100,+", + "chr1:101-200,+", + "chr1:500-600,-", + } + assert list(result.summary["rank"]) == [1, 2, 3, 4] + + +def test_paired_discovery_bed_handoff_into_region_contrasts(tmp_path, monkeypatch): + monkeypatch.setattr( + global_analysis, + "build_window_summary", + lambda **_: _mock_paired_pairwise_window_summary(), + ) + + discovery = region_discovery.scan_genome( + samples=_paired_samplespecs(), + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=500, + step_size=500, + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + pairing_key="pair_id", + ), + score="effect_size_only", + ) + assert discovery.metadata["pairing_key"] == "pair_id" + + bed_df = region_discovery.hits_to_bed(discovery.hits) + bed_path = tmp_path / "paired_discovered_hits.bed" + bed_df.to_csv(bed_path, sep="\t", header=False, index=False) + + counts_by_pileup = { + "t1.bed.gz": [(8, 10), (7, 10)], + "t2.bed.gz": [(3, 10), (4, 10)], + "d1.bed.gz": [(2, 10), (3, 10)], + "d2.bed.gz": [(1, 10), (2, 10)], + } + + def fake_regions_to_list( + function_handle, + regions, + window_size=None, + quiet=True, + cores=None, + split_large_regions=False, + ): + pileup_path = function_handle.keywords["bedmethyl_file"] + regions_dict = region_contrasts.utils.regions_dict_from_input( + regions, window_size + ) + n_regions = sum(len(region_list) for region_list in regions_dict.values()) + base_counts = counts_by_pileup[pileup_path] + return (base_counts * ((n_regions // len(base_counts)) + 1))[:n_regions] + + monkeypatch.setattr( + region_contrasts.load_processed, "regions_to_list", fake_regions_to_list + ) + + result = region_contrasts.score_regions( + samples=[ + SampleSpec( + sample_id="t1", + condition="targeting", + extract_h5="t1.h5", + metadata={"pileup_path": "t1.bed.gz"}, + ), + SampleSpec( + sample_id="t2", + condition="targeting", + extract_h5="t2.h5", + metadata={"pileup_path": "t2.bed.gz"}, + ), + SampleSpec( + sample_id="d1", + condition="nontargeting", + extract_h5="d1.h5", + metadata={"pileup_path": "d1.bed.gz"}, + ), + SampleSpec( + sample_id="d2", + condition="nontargeting", + extract_h5="d2.h5", + metadata={"pileup_path": "d2.bed.gz"}, + ), + ], + regions=bed_path, + motifs=["A,0"], + contrast=ContrastSpec( + mode="pairwise", + numerator=["targeting"], + denominator=["nontargeting"], + reference_condition="nontargeting", + ), + test="effect_size_only", + ) + + assert len(result.regions) == 2 + assert list(result.summary["rank"]) == [1, 2] + + +def test_scan_genome_filters_low_coverage_windows(monkeypatch): + def fake_build_window_summary(**kwargs): + return pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 10, + "valid_count": 20, + "window_fraction": 0.5, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 18, + "valid_count": 20, + "window_fraction": 0.9, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 1, + "valid_count": 2, + "window_fraction": 0.5, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 1, + "valid_count": 2, + "window_fraction": 0.5, + }, + ] + ) + + monkeypatch.setattr( + global_analysis, "build_window_summary", fake_build_window_summary + ) + + result = region_discovery.scan_genome( + samples=[ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="treated", + extract_h5="s2.h5", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ], + motifs=["A,0"], + genome_sizes={"chr1": 2000}, + window_size=1000, + step_size=1000, + contrast=ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + reference_condition="NS", + ), + score="effect_size_only", + min_coverage=10, + ) + + assert list(result.hits["window_id"]) == ["chr1:0-1000"] + low_coverage = result.windows.loc[ + result.windows["window_id"] == "chr1:1000-2000", "score_value" + ] + assert low_coverage.isna().all() + assert ( + result.windows.loc[result.windows["window_id"] == "chr1:1000-2000", "rank"] + .isna() + .all() + ) + + +def test_scan_genome_hands_include_and_exclude_contigs_to_window_builder(monkeypatch): + captured = {} + + def fake_build_window_summary(**kwargs): + captured.update(kwargs) + return pd.DataFrame( + columns=[ + "sample_id", + "condition", + "replicate", + "motif", + "window_id", + "chromosome", + "start", + "end", + "strand", + "modified_count", + "valid_count", + "window_fraction", + ] + ) + + monkeypatch.setattr( + global_analysis, "build_window_summary", fake_build_window_summary + ) + + region_discovery.scan_genome( + samples=[], + motifs=["A,0"], + genome_sizes={"chr1": 2000, "chr2": 1000}, + window_size=1000, + step_size=500, + include_contigs=["chr1"], + exclude_contigs=["chr2"], + score="effect_size_only", + ) + + assert captured["include_contigs"] == ["chr1"] + assert captured["exclude_contigs"] == ["chr2"] + + +def test_scan_genome_beta_binomial_adds_ranking_fields(monkeypatch): + def fake_build_window_summary(**kwargs): + return pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 4, + "valid_count": 20, + "window_fraction": 0.2, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 16, + "valid_count": 20, + "window_fraction": 0.8, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 10, + "valid_count": 20, + "window_fraction": 0.5, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 12, + "valid_count": 20, + "window_fraction": 0.6, + }, + ] + ) + + monkeypatch.setattr( + global_analysis, "build_window_summary", fake_build_window_summary + ) + + result = region_discovery.scan_genome( + samples=[ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="treated", + extract_h5="s2.h5", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ], + motifs=["A,0"], + genome_sizes={"chr1": 2000}, + window_size=1000, + step_size=1000, + contrast=ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + reference_condition="NS", + ), + score="beta_binomial", + min_coverage=5, + ) + + assert {"score_value", "p_value", "adjusted_p_value", "rank"}.issubset( + result.windows.columns + ) + assert list(result.hits["rank"]) == [1, 2] + assert ( + result.windows.loc[0, "adjusted_p_value"] + <= result.windows.loc[1, "adjusted_p_value"] + ) + assert result.hits.loc[0, "score_value"] >= result.hits.loc[1, "score_value"] + + +def test_scan_genome_excludes_low_coverage_before_beta_binomial_scoring(monkeypatch): + observed_p_value_calls = [] + observed_bh_inputs = [] + + def fake_build_window_summary(**kwargs): + return pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 5, + "valid_count": 20, + "window_fraction": 0.25, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 15, + "valid_count": 20, + "window_fraction": 0.75, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 1, + "valid_count": 1, + "window_fraction": 1.0, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 0, + "valid_count": 1, + "window_fraction": 0.0, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:2000-3000", + "chromosome": "chr1", + "start": 2000, + "end": 3000, + "strand": ".", + "modified_count": 8, + "valid_count": 20, + "window_fraction": 0.4, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:2000-3000", + "chromosome": "chr1", + "start": 2000, + "end": 3000, + "strand": ".", + "modified_count": 12, + "valid_count": 20, + "window_fraction": 0.6, + }, + ] + ) + + def fake_p_value(modified_count, valid_count, alpha, beta): + observed_p_value_calls.append((modified_count, valid_count, alpha, beta)) + return 0.5 + + def fake_bh_adjustment(p_values): + observed_bh_inputs.append(len(p_values)) + return p_values.astype(float) + + monkeypatch.setattr( + global_analysis, "build_window_summary", fake_build_window_summary + ) + monkeypatch.setattr( + region_discovery, "_beta_binomial_two_sided_p_value", fake_p_value + ) + monkeypatch.setattr(region_discovery, "_adjust_p_values_bh", fake_bh_adjustment) + + result = region_discovery.scan_genome( + samples=[], + motifs=["A,0"], + genome_sizes={"chr1": 3000}, + window_size=1000, + step_size=1000, + contrast=ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + reference_condition="NS", + ), + score="beta_binomial", + min_coverage=10, + ) + + assert len(observed_p_value_calls) == 2 + assert observed_bh_inputs == [2] + assert list(result.hits["window_id"]) == ["chr1:0-1000", "chr1:2000-3000"] + + +def test_scan_genome_effect_size_only_ranks_by_absolute_difference(monkeypatch): + def fake_build_window_summary(**kwargs): + return pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 19, + "valid_count": 20, + "window_fraction": 0.95, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 1, + "valid_count": 20, + "window_fraction": 0.05, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 5, + "valid_count": 20, + "window_fraction": 0.25, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 15, + "valid_count": 20, + "window_fraction": 0.75, + }, + ] + ) + + monkeypatch.setattr( + global_analysis, "build_window_summary", fake_build_window_summary + ) + + result = region_discovery.scan_genome( + samples=[], + motifs=["A,0"], + genome_sizes={"chr1": 2000}, + window_size=1000, + step_size=1000, + contrast=ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + reference_condition="NS", + ), + score="effect_size_only", + min_coverage=1, + ) + + assert list(result.hits["window_id"]) == ["chr1:0-1000", "chr1:1000-2000"] + assert list(result.hits["score_value"]) == [pytest.approx(0.9), pytest.approx(0.5)] + + +def test_scan_genome_raises_for_missing_contrast_condition(monkeypatch): + def fake_build_window_summary(**kwargs): + return pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 10, + "valid_count": 20, + "window_fraction": 0.5, + } + ] + ) + + monkeypatch.setattr( + global_analysis, "build_window_summary", fake_build_window_summary + ) + + with pytest.raises(ValueError, match="treated"): + region_discovery.scan_genome( + samples=[], + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=1000, + step_size=1000, + contrast=ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + reference_condition="NS", + ), + score="effect_size_only", + min_coverage=1, + ) + + +def test_scan_genome_rejects_unimplemented_contrast_modes(): + with pytest.raises(ValueError, match="paired contrast modes"): + region_discovery.scan_genome( + samples=[], + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=1000, + step_size=1000, + contrast=ContrastSpec( + mode="single_dataset", + ), + score="effect_size_only", + min_coverage=1, + ) + + +def test_scan_genome_beta_binomial_requires_explicit_contrast(): + with pytest.raises(ValueError, match="requires an explicit contrast"): + region_discovery.scan_genome( + samples=[], + motifs=["A,0"], + genome_sizes={"chr1": 1000}, + window_size=1000, + step_size=1000, + score="beta_binomial", + min_coverage=1, + ) + + +def test_scan_genome_reranks_hits_after_min_coverage_filtering(monkeypatch): + def fake_build_window_summary(**kwargs): + return pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 1, + "valid_count": 20, + "window_fraction": 0.05, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": ".", + "modified_count": 19, + "valid_count": 20, + "window_fraction": 0.95, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 1, + "valid_count": 1, + "window_fraction": 1.0, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": ".", + "modified_count": 0, + "valid_count": 1, + "window_fraction": 0.0, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:2000-3000", + "chromosome": "chr1", + "start": 2000, + "end": 3000, + "strand": ".", + "modified_count": 6, + "valid_count": 20, + "window_fraction": 0.3, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:2000-3000", + "chromosome": "chr1", + "start": 2000, + "end": 3000, + "strand": ".", + "modified_count": 14, + "valid_count": 20, + "window_fraction": 0.7, + }, + ] + ) + + monkeypatch.setattr( + global_analysis, "build_window_summary", fake_build_window_summary + ) + + result = region_discovery.scan_genome( + samples=[], + motifs=["A,0"], + genome_sizes={"chr1": 3000}, + window_size=1000, + step_size=1000, + contrast=ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + reference_condition="NS", + ), + score="effect_size_only", + min_coverage=10, + ) + + assert list(result.hits["window_id"]) == ["chr1:0-1000", "chr1:2000-3000"] + assert list(result.hits["rank"]) == [1, 2] + + +def test_scan_genome_merge_hits_keeps_strand_and_recomputes_window_fields(monkeypatch): + def fake_build_window_summary(**kwargs): + return pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": "+", + "modified_count": 1, + "valid_count": 10, + "window_fraction": 0.1, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:0-1000", + "chromosome": "chr1", + "start": 0, + "end": 1000, + "strand": "+", + "modified_count": 1, + "valid_count": 10, + "window_fraction": 0.1, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": "+", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:1000-2000", + "chromosome": "chr1", + "start": 1000, + "end": 2000, + "strand": "+", + "modified_count": 4, + "valid_count": 10, + "window_fraction": 0.4, + }, + { + "sample_id": "s1", + "condition": "NS", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:2000-3000", + "chromosome": "chr1", + "start": 2000, + "end": 3000, + "strand": "-", + "modified_count": 5, + "valid_count": 10, + "window_fraction": 0.5, + }, + { + "sample_id": "s2", + "condition": "treated", + "replicate": 1, + "motif": "A,0", + "window_id": "chr1:2000-3000", + "chromosome": "chr1", + "start": 2000, + "end": 3000, + "strand": "-", + "modified_count": 5, + "valid_count": 10, + "window_fraction": 0.5, + }, + ] + ) + + monkeypatch.setattr( + global_analysis, "build_window_summary", fake_build_window_summary + ) + + result = region_discovery.scan_genome( + samples=[], + motifs=["A,0"], + genome_sizes={"chr1": 3000}, + window_size=1000, + step_size=1000, + contrast=ContrastSpec( + mode="pairwise", + numerator=["treated"], + denominator=["NS"], + reference_condition="NS", + ), + score="effect_size_only", + min_coverage=1, + merge_hits=True, + merge_distance=1000, + ) + + assert list(result.hits["window_id"]) == ["chr1:0-2000", "chr1:2000-3000"] + assert list(result.hits["strand"]) == ["+", "-"] + assert result.hits.loc[0, "window_fraction"] == pytest.approx(0.25) + assert result.hits.loc[0, "merged_window_count"] == 2 + assert list(result.hits["rank"]) == [1, 3] diff --git a/tests/test_regulatory_enrichment.py b/tests/test_regulatory_enrichment.py new file mode 100644 index 0000000..e47cd8f --- /dev/null +++ b/tests/test_regulatory_enrichment.py @@ -0,0 +1,414 @@ +from pathlib import Path + +import pytest + +from dimelo import regulatory_enrichment, workflows +from dimelo.models import UniBindJobResult + + +def test_regulatory_spec_defaults_to_human_and_enables_screen_and_unibind(): + spec = regulatory_enrichment.RegulatoryEnrichmentSpec() + + assert spec.species == "homo_sapiens" + assert spec.target_genome == "hg38" + assert spec.enabled_providers == ("screen", "unibind") + assert spec.provider_notes == {} + + +def test_regulatory_spec_infers_mouse_from_target_genome(): + spec = regulatory_enrichment.RegulatoryEnrichmentSpec( + species=None, + target_genome="mm10", + ) + + assert spec.species == "mus_musculus" + assert spec.enabled_providers == ("screen", "unibind") + + +def test_regulatory_spec_filters_screen_for_non_screen_species(): + spec = regulatory_enrichment.RegulatoryEnrichmentSpec( + species="rat", + providers=("screen", "unibind"), + ) + + assert spec.species == "rattus_norvegicus" + assert spec.enabled_providers == ("unibind",) + assert "screen" in spec.provider_notes + assert ( + "supports only homo_sapiens and mus_musculus" in spec.provider_notes["screen"] + ) + + +def test_regulatory_spec_can_raise_on_filtered_screen_when_strict(): + with pytest.raises( + regulatory_enrichment.RegulatoryEnrichmentSpecError, match="SCREEN disabled" + ): + regulatory_enrichment.RegulatoryEnrichmentSpec( + species="rattus norvegicus", + providers=("screen",), + strict_provider_support=True, + ) + + +def test_regulatory_spec_rejects_species_not_supported_by_unibind(): + with pytest.raises( + regulatory_enrichment.RegulatoryEnrichmentSpecError, match="Unsupported species" + ): + regulatory_enrichment.RegulatoryEnrichmentSpec( + species="bos_taurus", + providers=("unibind",), + ) + + +def test_regulatory_spec_preserves_arbitrary_genome_and_chain_fields(): + spec = regulatory_enrichment.RegulatoryEnrichmentSpec( + species="human", + providers=("unibind",), + reference_genome="custom_build_v2", + target_genome="custom_build_v3", + crossmap_chain_file=Path("cache/chains/custom.over.chain.gz"), + crossmap_chain_url="https://example.org/custom.chain.gz", + crossmap_chain_cache_dir=Path("cache/chains/custom"), + crossmap_executable="CrossMap", + ) + + serialized = spec.as_dict() + assert serialized["reference_genome"] == "custom_build_v2" + assert serialized["target_genome"] == "custom_build_v3" + assert serialized["crossmap_chain_file"] == "cache/chains/custom.over.chain.gz" + assert serialized["crossmap_chain_url"] == "https://example.org/custom.chain.gz" + assert serialized["crossmap_chain_cache_dir"] == "cache/chains/custom" + assert serialized["crossmap_executable"] == "CrossMap" + + +def test_workflow_wrapper_returns_regulatory_spec(): + spec = workflows.resolve_regulatory_enrichment_spec( + species="mouse", + providers=("screen", "unibind"), + reference_genome="mm39", + ) + + assert isinstance(spec, regulatory_enrichment.RegulatoryEnrichmentSpec) + assert spec.species == "mus_musculus" + assert spec.enabled_providers == ("screen", "unibind") + + +def test_provider_alias_normalization_and_deduplication(): + spec = regulatory_enrichment.RegulatoryEnrichmentSpec( + species="human", + providers=("SCREEN2.0", "screen", "UniBind2021", "unibind"), + ) + + assert spec.providers == ("screen", "unibind") + assert spec.enabled_providers == ("screen", "unibind") + + +def test_unibind_trackhub_url_supports_named_collections(): + robust = regulatory_enrichment.unibind_trackhub_url("robust") + permissive = regulatory_enrichment.unibind_trackhub_url("permissive") + assert robust.endswith("/UniBind_hubs_Robust/UCSC/hub.txt") + assert permissive.endswith("/UniBind_hubs_Permissive/UCSC/hub.txt") + + +def test_search_unibind_trackhub_tracks_and_resolve_to_cache(monkeypatch, tmp_path): + hub_url = "https://example.org/hub.txt" + genomes_url = "https://example.org/genomes.txt" + trackdb_url = "https://example.org/hg38/trackDb.txt" + bigbed_url = "https://example.org/hg38/CTCF_track.bb" + other_url = "https://example.org/hg38/ATAC_track.bb" + + payloads = { + hub_url: ( + b"hub test\n" + b"shortLabel Test\n" + b"longLabel Test\n" + b"genomesFile genomes.txt\n" + b"email test@example.org\n" + ), + genomes_url: ( + b"genome hg38\n" + b"trackDb hg38/trackDb.txt\n\n" + b"genome mm10\n" + b"trackDb mm10/trackDb.txt\n" + ), + trackdb_url: ( + b"track CTCF_track\n" + b"shortLabel CTCF\n" + b"longLabel CTCF example\n" + b"type bigBed 9 .\n" + b"bigDataUrl CTCF_track.bb\n\n" + b"track ATAC_track\n" + b"shortLabel ATAC\n" + b"longLabel ATAC example\n" + b"type bigBed 9 .\n" + b"bigDataUrl ATAC_track.bb\n" + ), + bigbed_url: b"fake-ctcf-track", + other_url: b"fake-atac-track", + } + + class FakeResponse: + def __init__(self, payload: bytes) -> None: + self._payload = payload + + def read(self) -> bytes: + return self._payload + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + return False + + def fake_urlopen(req, timeout=60.0): # noqa: ARG001 + url = req.full_url if hasattr(req, "full_url") else str(req) + if url not in payloads: + raise RuntimeError(f"unexpected url: {url}") + return FakeResponse(payloads[url]) + + monkeypatch.setattr(regulatory_enrichment.request, "urlopen", fake_urlopen) + + rows = regulatory_enrichment.search_unibind_trackhub_tracks( + trackhub_url=hub_url, + assembly="hg38", + search_terms=["ctcf"], + ) + assert len(rows) == 1 + assert rows[0]["track"] == "CTCF_track" + assert rows[0]["url"] == bigbed_url + + cached = regulatory_enrichment.resolve_unibind_track_paths( + trackhub_url=hub_url, + assembly="hg38", + search_terms=["ctcf"], + cache_dir=tmp_path, + ) + assert len(cached) == 1 + assert cached[0].exists() + assert cached[0].read_bytes() == b"fake-ctcf-track" + + +def test_resolve_unibind_track_paths_prefers_explicit_local_paths(tmp_path): + p = tmp_path / "local_track.bed" + p.write_text("chr1\\t0\\t10\\n", encoding="utf-8") + + resolved = regulatory_enrichment.resolve_unibind_track_paths( + track_paths=[p], + ) + assert len(resolved) == 1 + assert resolved[0] == p.resolve() + + +def test_workflow_wrapper_resolve_unibind_track_paths_uses_regulatory_spec(monkeypatch): + captured = {} + + def fake_resolve(**kwargs): + captured.update(kwargs) + return [Path("/tmp/fake.bb")] + + monkeypatch.setattr( + regulatory_enrichment, "resolve_unibind_track_paths", fake_resolve + ) + spec = workflows.resolve_regulatory_enrichment_spec( + species="human", + target_genome="hg38", + providers=("unibind",), + ) + + paths = workflows.resolve_unibind_track_paths( + search_terms=["ctcf"], + regulatory_spec=spec, + ) + assert len(paths) == 1 + assert captured["assembly"] == "hg38" + + +def test_submit_unibind_tfbs_extraction_parses_job_page(monkeypatch): + endpoint = "https://example.org/TFBS_extraction/" + landing_html = ( + '
' + ) + submitted_html = ( + "" + "" + '" + "
Job status:Queued
Results URL:' + "https://example.org/TFBS_extraction/jobA/" + "
" + ) + + class FakeResponse: + def __init__(self, payload: str, url: str) -> None: + self._payload = payload.encode("utf-8") + self._url = url + + def read(self) -> bytes: + return self._payload + + def geturl(self) -> str: + return self._url + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + return False + + class FakeOpener: + def open(self, req, timeout=60.0): # noqa: ARG002 + url = req.full_url if hasattr(req, "full_url") else str(req) + data = getattr(req, "data", None) + if url == endpoint and data is None: + return FakeResponse(landing_html, endpoint) + if url == endpoint and data is not None: + assert b"performTFBSextraction" in data + assert b"collection" in data + return FakeResponse( + submitted_html, "https://example.org/TFBS_extraction/jobA/" + ) + raise RuntimeError(f"Unexpected request url={url!r}") + + monkeypatch.setattr( + regulatory_enrichment.request, "build_opener", lambda *args: FakeOpener() + ) + + result = regulatory_enrichment.submit_unibind_tfbs_extraction( + regions=["chr1:10-20"], + species="human", + collection="robust", + endpoint_url=endpoint, + ) + assert isinstance(result, UniBindJobResult) + assert result.job_id == "jobA" + assert result.status == "queued" + assert result.job_url == "https://example.org/TFBS_extraction/jobA/" + assert result.query["species"] == "homo_sapiens" + + +def test_submit_unibind_enrichment_requires_required_inputs(): + with pytest.raises(ValueError, match="background_regions"): + regulatory_enrichment.submit_unibind_enrichment( + regions=["chr1:10-20"], + analysis_type="oneSetBg", + endpoint_url="https://example.org/enrichment/", + ) + + with pytest.raises(ValueError, match="comparison_regions"): + regulatory_enrichment.submit_unibind_enrichment( + regions=["chr1:10-20"], + analysis_type="twoSets", + endpoint_url="https://example.org/enrichment/", + ) + + +def test_poll_unibind_job_waits_until_terminal(monkeypatch): + pages = [ + "Job status:Running", + ( + "Job status:Completed" + 'Download' + ), + ] + state = {"i": 0} + + def fake_fetch_text(url: str, timeout_seconds: float = 60.0): # noqa: ARG001 + index = state["i"] + if index < len(pages) - 1: + state["i"] += 1 + return pages[index] + + monkeypatch.setattr(regulatory_enrichment, "_fetch_text", fake_fetch_text) + + start = UniBindJobResult( + job_id="jobB", + status="queued", + job_url="https://example.org/TFBS_extraction/jobB/", + endpoint_url="https://example.org/TFBS_extraction/", + ) + result = regulatory_enrichment.poll_unibind_job( + start, + poll_interval_seconds=0.01, + timeout_seconds=1.0, + ) + assert result.status == "completed" + assert ( + "https://example.org/temp/jobB/extraction_results.bed" in result.download_urls + ) + assert len(result.status_history) >= 2 + + +def test_download_unibind_job_outputs_writes_files(monkeypatch, tmp_path): + payloads = { + "https://example.org/temp/jobC/a.tsv": b"a", + "https://example.org/temp/jobC/b.bed": b"b", + } + + def fake_fetch_bytes(url: str, timeout_seconds: float = 60.0): # noqa: ARG001 + return payloads[url] + + monkeypatch.setattr(regulatory_enrichment, "_fetch_bytes", fake_fetch_bytes) + job = UniBindJobResult( + job_id="jobC", + status="completed", + job_url="https://example.org/enrichment/jobC/", + endpoint_url="https://example.org/enrichment/", + download_urls=list(payloads.keys()), + ) + outputs = regulatory_enrichment.download_unibind_job_outputs( + job=job, + output_dir=tmp_path, + ) + assert len(outputs) == 2 + assert outputs[0].exists() + assert outputs[1].exists() + + +def test_workflow_wrapper_unibind_tfbs_uses_regulatory_spec(monkeypatch): + captured = {} + + def fake_run(**kwargs): + captured.update(kwargs) + return UniBindJobResult( + job_id="jobD", + status="completed", + job_url="https://example.org/TFBS_extraction/jobD/", + endpoint_url="https://example.org/TFBS_extraction/", + ) + + monkeypatch.setattr(regulatory_enrichment, "run_unibind_tfbs_extraction", fake_run) + spec = workflows.resolve_regulatory_enrichment_spec( + species="mouse", + providers=("unibind",), + ) + _ = workflows.unibind_tfbs_extraction_workflow( + regions=["chr1:1-2"], + regulatory_spec=spec, + wait=False, + ) + assert captured["species"] == "mus_musculus" + + +def test_workflow_wrapper_unibind_enrichment_uses_regulatory_spec(monkeypatch): + captured = {} + + def fake_run(**kwargs): + captured.update(kwargs) + return UniBindJobResult( + job_id="jobE", + status="completed", + job_url="https://example.org/enrichment/jobE/", + endpoint_url="https://example.org/enrichment/", + ) + + monkeypatch.setattr(regulatory_enrichment, "run_unibind_enrichment", fake_run) + spec = workflows.resolve_regulatory_enrichment_spec( + species="human", + providers=("unibind",), + ) + _ = workflows.unibind_enrichment_workflow( + regions=["chr1:1-2"], + background_regions=["chr1:1-3"], + regulatory_spec=spec, + wait=False, + ) + assert captured["species"] == "homo_sapiens" diff --git a/tests/test_run_modkit.py b/tests/test_run_modkit.py new file mode 100644 index 0000000..aeb5077 --- /dev/null +++ b/tests/test_run_modkit.py @@ -0,0 +1,189 @@ +import subprocess +from pathlib import Path + +import pytest + +from dimelo import run_modkit + + +def _fake_capabilities() -> run_modkit.ModkitCapabilities: + return run_modkit.ModkitCapabilities( + executable="modkit", + version_raw="modkit 0.6.1", + version="0.6.1", + version_tuple=(0, 6, 1), + supports_mod_threshold=True, + supports_mod_thresholds=False, + supports_modified_bases=True, + supports_force_allow_implicit=False, + supports_extract_subcommands=True, + extract_supports_reference_long=True, + extract_supports_reference_short=True, + ) + + +def test_ensure_modkit_available_forwards_executable_override(monkeypatch): + captured: dict[str, object] = {} + capabilities = _fake_capabilities() + + def fake_get_modkit_capabilities(quiet=False, executable=None): + captured["quiet"] = quiet + captured["executable"] = executable + return capabilities + + monkeypatch.setattr( + run_modkit, "get_modkit_capabilities", fake_get_modkit_capabilities + ) + + result = run_modkit._ensure_modkit_available( + quiet=True, + executable=Path("/opt/modkit-0.6.1/bin/modkit"), + ) + + assert result == capabilities + assert captured == { + "quiet": True, + "executable": "/opt/modkit-0.6.1/bin/modkit", + } + + +def test_resolve_modkit_executable_prefers_env_override(monkeypatch): + monkeypatch.setenv(run_modkit.MODKIT_EXECUTABLE_ENV, "modkit-0.6") + monkeypatch.setattr( + run_modkit.shutil, "which", lambda name: f"/usr/local/bin/{name}" + ) + + resolved = run_modkit._resolve_modkit_executable(None) + + assert resolved == "/usr/local/bin/modkit-0.6" + + +def test_resolve_modkit_executable_raises_for_missing_path(): + with pytest.raises(FileNotFoundError, match="does not exist"): + run_modkit._resolve_modkit_executable("/tmp/does-not-exist/modkit") + + +def test_configure_modkit_executable_sets_and_clears_env(monkeypatch): + monkeypatch.delenv(run_modkit.MODKIT_EXECUTABLE_ENV, raising=False) + + run_modkit.configure_modkit_executable("/opt/modkit-custom/bin/modkit") + assert ( + run_modkit.os.environ.get(run_modkit.MODKIT_EXECUTABLE_ENV) + == "/opt/modkit-custom/bin/modkit" + ) + + run_modkit.configure_modkit_executable(None) + assert run_modkit.MODKIT_EXECUTABLE_ENV not in run_modkit.os.environ + + +def test_modkit_cache_fingerprint_changes_when_binary_changes(tmp_path): + fake_modkit = tmp_path / "modkit" + fake_modkit.write_text("v1") + fp1 = run_modkit._modkit_cache_fingerprint(str(fake_modkit)) + + # Ensure metadata changes (mtime and size) to trigger new fingerprint. + fake_modkit.write_text("v2-updated") + fp2 = run_modkit._modkit_cache_fingerprint(str(fake_modkit)) + + assert fp1 != fp2 + + +def test_get_modkit_capabilities_uses_resolved_path_and_fingerprint(monkeypatch): + captured: dict[str, object] = {} + capabilities = _fake_capabilities() + + def fake_prepare_modkit_path(quiet=False): + captured["prepared_quiet"] = quiet + + def fake_resolve_modkit_executable(executable): + captured["resolved_from"] = executable + return "/resolved/modkit" + + def fake_modkit_cache_fingerprint(executable_path): + captured["fingerprint_path"] = executable_path + return "fingerprint-123" + + def fake_cached(*, executable_path, executable_fingerprint, quiet=False): + captured["cached_executable_path"] = executable_path + captured["cached_executable_fingerprint"] = executable_fingerprint + captured["cached_quiet"] = quiet + return capabilities + + monkeypatch.setattr(run_modkit, "_prepare_modkit_path", fake_prepare_modkit_path) + monkeypatch.setattr( + run_modkit, "_resolve_modkit_executable", fake_resolve_modkit_executable + ) + monkeypatch.setattr( + run_modkit, "_modkit_cache_fingerprint", fake_modkit_cache_fingerprint + ) + monkeypatch.setattr(run_modkit, "_get_modkit_capabilities_cached", fake_cached) + + result = run_modkit.get_modkit_capabilities( + quiet=True, + executable="modkit-0.6.1", + ) + + assert result == capabilities + assert captured == { + "prepared_quiet": True, + "resolved_from": "modkit-0.6.1", + "fingerprint_path": "/resolved/modkit", + "cached_executable_path": "/resolved/modkit", + "cached_executable_fingerprint": "fingerprint-123", + "cached_quiet": True, + } + + +def test_should_render_live_progress_respects_env_off(monkeypatch): + monkeypatch.setenv("DIMELO_PROGRESS_MODE", "off") + monkeypatch.setattr(run_modkit.sys.stderr, "isatty", lambda: True) + + assert run_modkit._should_render_live_progress() is False + + +def test_should_render_live_progress_auto_disables_notebook(monkeypatch): + monkeypatch.delenv("DIMELO_PROGRESS_MODE", raising=False) + monkeypatch.setenv("JPY_PARENT_PID", "12345") + monkeypatch.setattr(run_modkit.sys.stderr, "isatty", lambda: True) + + assert run_modkit._should_render_live_progress() is False + + +def test_get_modkit_capabilities_extract_subcommands_fallback_for_modkit_0_6( + monkeypatch, +): + run_modkit._get_modkit_capabilities_cached.cache_clear() + + def fake_run( + cmd, stdout=None, stderr=None, text=None, check=None, capture_output=None + ): + if cmd == ["modkit", "--version"]: + return subprocess.CompletedProcess(cmd, 0, stdout="modkit 0.6.1", stderr="") + if cmd == ["modkit", "pileup", "--help"]: + return subprocess.CompletedProcess( + cmd, + 0, + stdout=" --mod-threshold\n --modified-bases\n", + stderr="", + ) + if cmd == ["modkit", "extract", "--help"]: + # Simulate help text that does not include the exact usage marker. + return subprocess.CompletedProcess( + cmd, 0, stdout="Extract command\n", stderr="" + ) + if cmd == ["modkit", "extract", "full", "--help"]: + return subprocess.CompletedProcess( + cmd, 0, stdout=" --reference\n", stderr="" + ) + raise AssertionError(f"unexpected subprocess command: {cmd}") + + monkeypatch.setattr(run_modkit.subprocess, "run", fake_run) + + caps = run_modkit._get_modkit_capabilities_cached( + executable_path="modkit", + executable_fingerprint="fingerprint-test", + quiet=True, + ) + + assert caps.supports_extract_subcommands is True + assert caps.extract_supports_reference_long is True diff --git a/tests/test_shared_cluster_tests.py b/tests/test_shared_cluster_tests.py new file mode 100644 index 0000000..2668043 --- /dev/null +++ b/tests/test_shared_cluster_tests.py @@ -0,0 +1,395 @@ +import pandas as pd +import pytest + +from dimelo.models import ContrastSpec, SharedClusterModel, SharedClusterResult + + +def _make_shared_cluster_test_result() -> SharedClusterResult: + return SharedClusterResult( + model=SharedClusterModel( + mode="read_global", + motifs=["A,0"], + feature_names=["f0", "f1"], + preprocessing={"signal_normalization": "none"}, + estimator=object(), + cluster_labels=["C0", "C1"], + fit_metadata={"clusterer": "minibatch_kmeans", "n_clusters": 2}, + ), + assignments=pd.DataFrame( + { + "sample_id": ["s1", "s2", "s3", "s4"], + "condition": ["NS", "NS", "treated", "treated"], + "subject_id": ["p1", "p2", "p1", "p2"], + "cluster": ["C0", "C0", "C1", "C1"], + } + ), + cluster_distribution=pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "cluster": "C0", + "count": 80, + "fraction": 0.80, + }, + { + "sample_id": "s1", + "condition": "NS", + "cluster": "C1", + "count": 20, + "fraction": 0.20, + }, + { + "sample_id": "s2", + "condition": "NS", + "cluster": "C0", + "count": 75, + "fraction": 0.75, + }, + { + "sample_id": "s2", + "condition": "NS", + "cluster": "C1", + "count": 25, + "fraction": 0.25, + }, + { + "sample_id": "s3", + "condition": "treated", + "cluster": "C0", + "count": 30, + "fraction": 0.30, + }, + { + "sample_id": "s3", + "condition": "treated", + "cluster": "C1", + "count": 70, + "fraction": 0.70, + }, + { + "sample_id": "s4", + "condition": "treated", + "cluster": "C0", + "count": 25, + "fraction": 0.25, + }, + { + "sample_id": "s4", + "condition": "treated", + "cluster": "C1", + "count": 75, + "fraction": 0.75, + }, + ] + ), + condition_distribution=pd.DataFrame( + [ + { + "condition": "NS", + "cluster": "C0", + "count": 155, + "fraction": 0.775, + "replicate_n": 2, + }, + { + "condition": "NS", + "cluster": "C1", + "count": 45, + "fraction": 0.225, + "replicate_n": 2, + }, + { + "condition": "treated", + "cluster": "C0", + "count": 55, + "fraction": 0.275, + "replicate_n": 2, + }, + { + "condition": "treated", + "cluster": "C1", + "count": 145, + "fraction": 0.725, + "replicate_n": 2, + }, + ] + ), + distribution_change=None, + cluster_profiles=pd.DataFrame(columns=["cluster", "count", "f0", "f1"]), + region_summaries=None, + plot_data={}, + figures={}, + metadata={}, + ) + + +def _make_shared_cluster_time_course_result() -> SharedClusterResult: + result = _make_shared_cluster_test_result() + result.assignments = pd.DataFrame( + { + "sample_id": ["t0_a", "t1_a", "t2_a"], + "condition": ["t0", "t1", "t2"], + "subject_id": ["p1", "p1", "p1"], + "cluster": ["C0", "C0", "C1"], + } + ) + result.cluster_distribution = pd.DataFrame( + [ + { + "sample_id": "t0_a", + "condition": "t0", + "cluster": "C0", + "count": 80, + "fraction": 0.80, + }, + { + "sample_id": "t0_a", + "condition": "t0", + "cluster": "C1", + "count": 20, + "fraction": 0.20, + }, + { + "sample_id": "t1_a", + "condition": "t1", + "cluster": "C0", + "count": 55, + "fraction": 0.55, + }, + { + "sample_id": "t1_a", + "condition": "t1", + "cluster": "C1", + "count": 45, + "fraction": 0.45, + }, + { + "sample_id": "t2_a", + "condition": "t2", + "cluster": "C0", + "count": 25, + "fraction": 0.25, + }, + { + "sample_id": "t2_a", + "condition": "t2", + "cluster": "C1", + "count": 75, + "fraction": 0.75, + }, + ] + ) + result.condition_distribution = pd.DataFrame( + [ + { + "condition": "t0", + "cluster": "C0", + "count": 80, + "fraction": 0.80, + "replicate_n": 1, + }, + { + "condition": "t0", + "cluster": "C1", + "count": 20, + "fraction": 0.20, + "replicate_n": 1, + }, + { + "condition": "t1", + "cluster": "C0", + "count": 55, + "fraction": 0.55, + "replicate_n": 1, + }, + { + "condition": "t1", + "cluster": "C1", + "count": 45, + "fraction": 0.45, + "replicate_n": 1, + }, + { + "condition": "t2", + "cluster": "C0", + "count": 25, + "fraction": 0.25, + "replicate_n": 1, + }, + { + "condition": "t2", + "cluster": "C1", + "count": 75, + "fraction": 0.75, + "replicate_n": 1, + }, + ] + ) + return result + + +def test_shared_cluster_tests_module_exports_entry_point(): + from dimelo import shared_cluster_tests + + assert hasattr(shared_cluster_tests, "shared_cluster_tests") + + +def test_shared_cluster_tests_rejects_unsupported_contrast_mode(): + from dimelo import shared_cluster_tests + + result = _make_shared_cluster_test_result() + + with pytest.raises(NotImplementedError, match="background_adjusted"): + shared_cluster_tests.shared_cluster_tests( + result=result, + contrast=ContrastSpec( + mode="background_adjusted", + numerator=["treated"], + denominator=["NS"], + background=["bg"], + ), + ) + + +def test_shared_cluster_tests_pairwise_returns_summary_details_and_plot_data(): + from dimelo import shared_cluster_tests + from dimelo.models import ContrastSpec + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_test_result(), + contrast=ContrastSpec( + mode="pairwise", numerator=["treated"], denominator=["NS"] + ), + test="permutation", + n_permutations=50, + random_state=7, + ) + + assert set(result.summary.columns) >= { + "contrast_id", + "composition_effect_size", + "omnibus_p_value", + "top_cluster", + } + assert set(result.details.columns) >= { + "cluster", + "fraction", + "reference_fraction", + "delta_fraction", + "p_value", + "adjusted_p_value", + } + assert set(result.plot_data) >= {"summary_table", "cluster_effect_table"} + + +def test_shared_cluster_tests_matched_pairwise_uses_contrast_pairing_key(): + from dimelo import shared_cluster_tests + from dimelo.models import ContrastSpec + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_test_result(), + contrast=ContrastSpec( + mode="matched_pairwise", + numerator=["treated"], + denominator=["NS"], + pairing_key="subject_id", + ), + test="permutation", + n_permutations=50, + random_state=7, + ) + + assert result.metadata["paired"] is True + assert result.metadata["pairing_key"] == "subject_id" + + +def test_shared_cluster_tests_supports_chi_squared_screen(): + from dimelo import shared_cluster_tests + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_test_result(), + contrast=ContrastSpec( + mode="pairwise", numerator=["treated"], denominator=["NS"] + ), + test="chi_squared", + ) + + assert result.metadata["inference_level"] == "pooled_screen" + assert result.summary.loc[0, "test"] == "chi_squared" + assert 0.0 <= result.summary.loc[0, "omnibus_p_value"] <= 1.0 + + +def test_shared_cluster_tests_supports_g_test_screen(): + from dimelo import shared_cluster_tests + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_test_result(), + contrast=ContrastSpec( + mode="pairwise", numerator=["treated"], denominator=["NS"] + ), + test="g_test", + ) + + assert result.metadata["inference_level"] == "pooled_screen" + assert result.summary.loc[0, "test"] == "g_test" + assert 0.0 <= result.summary.loc[0, "omnibus_p_value"] <= 1.0 + + +def test_shared_cluster_tests_time_course_returns_omnibus_and_trend_outputs(): + from dimelo import shared_cluster_tests + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_time_course_result(), + contrast=ContrastSpec(mode="time_course", time_order=["t0", "t1", "t2"]), + test="permutation", + n_permutations=50, + random_state=7, + ) + + assert {"omnibus_p_value", "trend_p_value", "composition_effect_size"} <= set( + result.summary.columns + ) + assert "time_course_table" in result.plot_data + + +def test_shared_cluster_tests_time_course_optional_pairwise_follow_up(): + from dimelo import shared_cluster_tests + + result = shared_cluster_tests.shared_cluster_tests( + result=_make_shared_cluster_time_course_result(), + contrast=ContrastSpec(mode="time_course", time_order=["t0", "t1", "t2"]), + test="permutation", + include_pairwise=True, + n_permutations=20, + random_state=7, + ) + + assert result.pairwise is not None + + +def test_shared_cluster_tests_time_course_permutations_do_not_build_dataframe_per_iteration( + monkeypatch, +): + from dimelo import shared_cluster_tests + + result = _make_shared_cluster_time_course_result() + n_permutations = 20 + dataframe_calls = 0 + original_dataframe = shared_cluster_tests.pd.DataFrame + + def _counting_dataframe(*args, **kwargs): + nonlocal dataframe_calls + dataframe_calls += 1 + return original_dataframe(*args, **kwargs) + + monkeypatch.setattr(shared_cluster_tests.pd, "DataFrame", _counting_dataframe) + shared_cluster_tests.shared_cluster_tests( + result=result, + contrast=ContrastSpec(mode="time_course", time_order=["t0", "t1", "t2"]), + test="permutation", + n_permutations=n_permutations, + random_state=7, + ) + + assert dataframe_calls < n_permutations diff --git a/tests/test_utils_regions.py b/tests/test_utils_regions.py new file mode 100644 index 0000000..e11e8ff --- /dev/null +++ b/tests/test_utils_regions.py @@ -0,0 +1,11 @@ +from dimelo import utils + + +def test_regions_dict_from_input_accepts_tuple_region_collections(): + tuple_regions = ("chr2:5-10,+", "chr1:0-3,-") + list_regions = ["chr2:5-10,+", "chr1:0-3,-"] + + tuple_result = utils.regions_dict_from_input(tuple_regions) + list_result = utils.regions_dict_from_input(list_regions) + + assert tuple_result == list_result diff --git a/tests/test_workflows.py b/tests/test_workflows.py new file mode 100644 index 0000000..7b81d1b --- /dev/null +++ b/tests/test_workflows.py @@ -0,0 +1,2174 @@ +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest + +from dimelo import plotting, workflows +from dimelo.models import ( + ChipAtlasEnrichmentResult, + ContrastSpec, + DatasetArtifact, + RegionContrastResult, + RegionDiscoveryResult, + SampleSpec, + SharedClusterModel, + SharedClusterResult, +) + + +def _workflow_samples(): + return [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + regions_bed="control-s1.bed", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="15min", + extract_h5="s2.h5", + regions_bed="control-s2.bed", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + + +def _mock_discovery_result(*args, **kwargs): + hits = pd.DataFrame( + [ + { + "window_id": "chr1:0-500", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": "+", + "score_value": 0.9, + "adjusted_p_value": 0.01, + }, + { + "window_id": "chr1:500-1000", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": "-", + "score_value": 0.6, + "adjusted_p_value": 0.02, + }, + { + "window_id": "chr1:1000-1500", + "chromosome": "chr1", + "start": 1000, + "end": 1500, + "strand": "+", + "score_value": 0.2, + "adjusted_p_value": 0.20, + }, + ] + ) + return RegionDiscoveryResult( + hits=hits, + windows=hits.copy(), + contrast=None, + plot_data={}, + metadata={"score": "effect_size_only"}, + ) + + +def _mock_cluster_result(*args, **kwargs): + assignments = pd.DataFrame( + [ + {"sample_id": "s1", "condition": "NS", "cluster": "C0"}, + {"sample_id": "s2", "condition": "15min", "cluster": "C1"}, + ] + ) + return SharedClusterResult( + model=SharedClusterModel( + mode=kwargs["mode"], + motifs=list(kwargs["motifs"]), + feature_names=["f0"], + preprocessing={}, + estimator=object(), + cluster_labels=["C0", "C1"], + fit_metadata={}, + ), + assignments=assignments, + cluster_distribution=pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "cluster": "C0", + "count": 1, + "fraction": 1.0, + }, + { + "sample_id": "s2", + "condition": "15min", + "cluster": "C1", + "count": 1, + "fraction": 1.0, + }, + ] + ), + condition_distribution=pd.DataFrame( + [ + {"condition": "NS", "cluster": "C0", "fraction": 1.0, "replicate_n": 1}, + { + "condition": "15min", + "cluster": "C1", + "fraction": 1.0, + "replicate_n": 1, + }, + ] + ), + distribution_change=None, + cluster_profiles=pd.DataFrame([{"cluster": "C0", "count": 1, "f0": 0.0}]), + region_summaries=None, + plot_data={}, + metadata={"matched_regions": kwargs.get("matched_regions")}, + ) + + +def _mock_region_contrast_result(*args, **kwargs): + contrast = kwargs["contrast"] + regions = pd.DataFrame( + [ + { + "region_id": "chr1:0-500,+", + "chromosome": "chr1", + "start": 0, + "end": 500, + "strand": "+", + "fraction": 0.8, + "reference_fraction": 0.2, + "delta_fraction": 0.6, + "log2_fc": 2.0, + "effect_size": 0.6, + "rank": 1, + "numerator_modified_count": 8, + "numerator_valid_count": 10, + "numerator_replicate_n": 1, + "denominator_modified_count": 2, + "denominator_valid_count": 10, + "denominator_replicate_n": 1, + }, + { + "region_id": "chr1:500-1000,-", + "chromosome": "chr1", + "start": 500, + "end": 1000, + "strand": "-", + "fraction": 0.6, + "reference_fraction": 0.3, + "delta_fraction": 0.3, + "log2_fc": 1.0, + "effect_size": 0.3, + "rank": 2, + "numerator_modified_count": 6, + "numerator_valid_count": 10, + "numerator_replicate_n": 1, + "denominator_modified_count": 3, + "denominator_valid_count": 10, + "denominator_replicate_n": 1, + }, + ] + ) + summary = regions[ + [ + "region_id", + "fraction", + "reference_fraction", + "delta_fraction", + "log2_fc", + "rank", + "numerator_modified_count", + "numerator_valid_count", + "numerator_replicate_n", + "denominator_modified_count", + "denominator_valid_count", + "denominator_replicate_n", + ] + ].copy() + return RegionContrastResult( + regions=regions, + summary=summary, + contrast=contrast, + plot_data={"region_effect_sizes": summary.copy()}, + metadata={ + "analysis_unit": kwargs.get("analysis_unit", "ensemble_region"), + "representation": kwargs.get("representation", "modified_fraction"), + "signal_source": kwargs.get("signal_source", "pileup_counts"), + "test": kwargs.get("test", "effect_size_only"), + "multiple_testing": kwargs.get("multiple_testing", "fdr_bh"), + }, + ) + + +def _fake_region_anchored_extract(*args, **kwargs): + regions = kwargs["regions"] + region_list = list(regions) if isinstance(regions, list) else [regions] + metadata = [] + for region in region_list: + chrom_coords, strand = region.split(",") + chrom, coords = chrom_coords.split(":") + start, end = coords.split("-") + metadata.append((chrom, int(start), int(end), strand)) + matrix = np.array([[0.2, 0.8], [0.8, 0.2]], dtype=np.float32) + return matrix[: len(metadata)], metadata + + +def test_discovery_cluster_workflow_returns_both_results(monkeypatch): + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + + result = workflows.discovery_cluster_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + ) + + assert result.discovery.hits.shape[0] == 3 + assert result.clustering.model.mode == "region_anchored" + assert list(result.selected_regions.columns) == [ + "chrom", + "start", + "end", + "name", + "score", + "strand", + ] + assert result.metadata["selection"]["mode"] == "top_n" + assert result.metadata["selection"]["top_n"] == 250 + + +def test_discovery_cluster_workflow_selects_top_n_hits(monkeypatch): + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + + result = workflows.discovery_cluster_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + selection={"mode": "top_n", "top_n": 1}, + ) + + assert result.selected_regions["name"].tolist() == ["chr1:0-500"] + + +def test_discovery_cluster_workflow_selects_all_hits(monkeypatch): + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + + result = workflows.discovery_cluster_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + selection={"mode": "all"}, + ) + + assert set(result.selected_regions["name"].tolist()) == { + "chr1:0-500", + "chr1:500-1000", + "chr1:1000-1500", + } + assert result.metadata["selection"]["top_n"] is None + + +def test_discovery_cluster_workflow_passes_selected_regions_into_clustering( + monkeypatch, +): + captured = {} + + def fake_cluster(**kwargs): + captured["matched_regions"] = kwargs["matched_regions"] + return _mock_cluster_result(**kwargs) + + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "shared_cluster_distribution", fake_cluster) + + result = workflows.discovery_cluster_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + selection={"mode": "top_n", "top_n": 2}, + ) + + assert captured["matched_regions"] == ["chr1:0-500,+", "chr1:500-1000,-"] + assert list(result.selected_regions.columns) == [ + "chrom", + "start", + "end", + "name", + "score", + "strand", + ] + assert list(result.selected_regions["name"]) == ["chr1:0-500", "chr1:500-1000"] + + +def test_discovery_cluster_workflow_errors_when_no_hits_survive_selection(monkeypatch): + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + + with pytest.raises(ValueError, match="No discovery hits remained after selection"): + workflows.discovery_cluster_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + selection={"mode": "top_n", "top_n": 0}, + ) + + +def test_discovery_cluster_workflow_rejects_unknown_selection_mode(monkeypatch): + called = {"scan_genome": False} + + def fake_scan_genome(*args, **kwargs): + called["scan_genome"] = True + raise AssertionError( + "scan_genome should not be called for invalid selection config" + ) + + monkeypatch.setattr(workflows.region_discovery, "scan_genome", fake_scan_genome) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + + with pytest.raises(ValueError, match="Unsupported selection mode"): + workflows.discovery_cluster_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + selection={"mode": "invalid"}, + ) + assert called["scan_genome"] is False + + +def test_discovery_cluster_workflow_rejects_invalid_clustering_config_before_scan( + monkeypatch, +): + called = {"scan_genome": False} + + def fake_scan_genome(*args, **kwargs): + called["scan_genome"] = True + raise AssertionError( + "scan_genome should not be called for invalid clustering config" + ) + + monkeypatch.setattr(workflows.region_discovery, "scan_genome", fake_scan_genome) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + + with pytest.raises(NotImplementedError, match="clusterer='minibatch_kmeans' only"): + workflows.discovery_cluster_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={ + "mode": "region_anchored", + "clusterer": "agglomerative", + "n_clusters": 2, + }, + ) + assert called["scan_genome"] is False + + +def test_discovery_cluster_workflow_region_anchored_uses_serializable_matched_regions( + monkeypatch, +): + captured = {"resolve_params": []} + + def fake_resolve_artifact(requested_artifact, available_artifacts, artifact_policy): + captured["resolve_params"].append(requested_artifact.params["matched_regions"]) + return None + + def fake_region_features(*args, **kwargs): + regions = kwargs["regions"] + captured["regions"] = regions + return _fake_region_anchored_extract(*args, **kwargs) + + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "resolve_artifact", fake_resolve_artifact) + monkeypatch.setattr( + workflows.cluster, + "region_feature_matrix_from_pileup", + fake_region_features, + ) + + result = workflows.discovery_cluster_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2, "make_plots": False}, + selection={"mode": "top_n", "top_n": 2}, + ) + + assert captured["regions"] == ["chr1:0-500,+", "chr1:500-1000,-"] + assert captured["resolve_params"] == [ + ["chr1:0-500,+", "chr1:500-1000,-"], + ["chr1:0-500,+", "chr1:500-1000,-"], + ] + assert result.clustering.metadata["matched_regions"] == [ + "chr1:0-500,+", + "chr1:500-1000,-", + ] + + +def test_discovery_cluster_workflow_artifact_params_keep_matched_regions_json_serializable( + monkeypatch, +): + captured = {} + region_spec = ["chr1:0-500,+", "chr1:500-1000,-"] + artifact_samples = [] + for sample in _workflow_samples(): + artifact_samples.append( + SampleSpec( + sample_id=sample.sample_id, + condition=sample.condition, + extract_h5=sample.extract_h5, + regions_bed=sample.regions_bed, + metadata={ + "pileup_path": sample.metadata["pileup_path"], + "artifacts": [ + DatasetArtifact( + sample_id=sample.sample_id, + artifact_type="pileup", + path=sample.metadata["pileup_path"], + format="bed.gz", + params={ + "motifs": ["A,0"], + "matched_regions": region_spec, + "signal_normalization": "none", + "feature_scaling": "robust_zscore", + "cluster_basis": "shape_plus_level", + }, + provenance={ + "pipeline": "parse_bam", + "source_files": [], + "source_fingerprints": [], + }, + ) + ], + }, + ) + ) + + def fake_resolve_artifact(requested_artifact, available_artifacts, artifact_policy): + captured.setdefault("params", []).append( + requested_artifact.params["matched_regions"] + ) + return available_artifacts[0] + + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "resolve_artifact", fake_resolve_artifact) + monkeypatch.setattr( + workflows.cluster, + "region_feature_matrix_from_pileup", + _fake_region_anchored_extract, + ) + + result = workflows.discovery_cluster_workflow( + samples=artifact_samples, + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2, "make_plots": False}, + selection={"mode": "top_n", "top_n": 2}, + ) + + assert captured["params"] == [region_spec, region_spec] + assert result.clustering.metadata["cache_hits"] == { + "s1": "s1.bed.gz", + "s2": "s2.bed.gz", + } + + +def test_discovery_cluster_workflow_materializes_samples_iterable(monkeypatch): + captured = {} + + def fake_discovery(*args, **kwargs): + captured["discovery_sample_ids"] = [ + sample.sample_id for sample in kwargs["samples"] + ] + return _mock_discovery_result(*args, **kwargs) + + def fake_cluster(**kwargs): + captured["sample_ids"] = [sample.sample_id for sample in kwargs["samples"]] + return _mock_cluster_result(**kwargs) + + monkeypatch.setattr(workflows.region_discovery, "scan_genome", fake_discovery) + monkeypatch.setattr(workflows, "shared_cluster_distribution", fake_cluster) + + result = workflows.discovery_cluster_workflow( + samples=(sample for sample in _workflow_samples()), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + selection={"mode": "top_n", "top_n": 2}, + ) + + assert result.discovery.hits.shape[0] == 3 + assert result.clustering.model.mode == "region_anchored" + assert captured["discovery_sample_ids"] == ["s1", "s2"] + assert captured["sample_ids"] == ["s1", "s2"] + assert list(result.selected_regions["name"]) == ["chr1:0-500", "chr1:500-1000"] + + +def test_discovery_cluster_workflow_materializes_motif_generator(monkeypatch): + captured = {} + + def fake_discovery(*args, **kwargs): + captured["discovery_motifs"] = list(kwargs["motifs"]) + return _mock_discovery_result(*args, **kwargs) + + def fake_cluster(**kwargs): + captured["clustering_motifs"] = list(kwargs["motifs"]) + return _mock_cluster_result(**kwargs) + + monkeypatch.setattr(workflows.region_discovery, "scan_genome", fake_discovery) + monkeypatch.setattr(workflows, "shared_cluster_distribution", fake_cluster) + + workflows.discovery_cluster_workflow( + samples=_workflow_samples(), + motifs=(motif for motif in ["A,0", "CG,1"]), + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + selection={"mode": "top_n", "top_n": 2}, + ) + + assert captured["discovery_motifs"] == ["A,0", "CG,1"] + assert captured["clustering_motifs"] == ["A,0", "CG,1"] + + +def test_discovery_cluster_contrast_workflow_returns_all_results(monkeypatch): + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + monkeypatch.setattr( + workflows.region_contrasts, "score_regions", _mock_region_contrast_result + ) + + result = workflows.discovery_cluster_contrast_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + contrasts={ + "contrast": ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + ), + "test": "effect_size_only", + }, + ) + + assert result.discovery.hits.shape[0] == 3 + assert result.clustering.model.mode == "region_anchored" + assert result.contrasts.metadata["test"] == "effect_size_only" + assert result.metadata["contrast_scope"] == "selected" + assert list(result.selected_regions.columns) == [ + "chrom", + "start", + "end", + "name", + "score", + "strand", + ] + assert result.metadata["full_scan_windows"].equals(result.discovery.windows) + + +def test_discovery_cluster_contrast_workflow_scores_selected_regions_by_default( + monkeypatch, +): + captured = {} + + def fake_score_regions(**kwargs): + captured["regions"] = kwargs["regions"] + return _mock_region_contrast_result(**kwargs) + + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + monkeypatch.setattr(workflows.region_contrasts, "score_regions", fake_score_regions) + + result = workflows.discovery_cluster_contrast_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + contrasts={ + "contrast": ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + ), + }, + selection={"mode": "top_n", "top_n": 2}, + ) + + assert captured["regions"] == ["chr1:0-500,+", "chr1:500-1000,-"] + assert result.selected_regions["name"].tolist() == ["chr1:0-500", "chr1:500-1000"] + + +def test_discovery_cluster_contrast_workflow_normalizes_clustering_region_ids_for_joining( + monkeypatch, +): + def fake_cluster(**kwargs): + result = _mock_cluster_result(**kwargs) + result.assignments = pd.DataFrame( + [ + { + "region_id": "chr1:0-500:+", + "sample_id": "s1", + "condition": "NS", + "cluster": "C0", + }, + { + "region_id": "chr1:500-1000:-", + "sample_id": "s2", + "condition": "15min", + "cluster": "C1", + }, + ] + ) + result.region_summaries = pd.DataFrame( + [ + { + "region_id": "chr1:0-500:+", + "sample_id": "s1", + "condition": "NS", + "cluster": "C0", + "count": 1, + "fraction": 1.0, + }, + { + "region_id": "chr1:500-1000:-", + "sample_id": "s2", + "condition": "15min", + "cluster": "C1", + "count": 1, + "fraction": 1.0, + }, + ] + ) + return result + + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "shared_cluster_distribution", fake_cluster) + monkeypatch.setattr( + workflows.region_contrasts, "score_regions", _mock_region_contrast_result + ) + + result = workflows.discovery_cluster_contrast_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + contrasts={ + "contrast": ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + ), + }, + selection={"mode": "top_n", "top_n": 2}, + ) + + contrast_region_ids = set(result.contrasts.regions["region_id"]) + assert set(result.clustering.assignments["region_id"]) == contrast_region_ids + assert set(result.clustering.region_summaries["region_id"]) == contrast_region_ids + assert set(result.clustering.assignments["region_id"]) == set( + result.clustering.region_summaries["region_id"] + ) + + +def test_discovery_cluster_contrast_workflow_uses_custom_contrast_regions_when_provided( + monkeypatch, +): + captured = {} + + def fake_score_regions(**kwargs): + captured["regions"] = kwargs["regions"] + return _mock_region_contrast_result(**kwargs) + + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + monkeypatch.setattr(workflows.region_contrasts, "score_regions", fake_score_regions) + + custom_regions = ["chr2:0-100,+", "chr2:100-200,-"] + result = workflows.discovery_cluster_contrast_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + contrasts={ + "contrast": ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + ), + "regions": custom_regions, + }, + selection={"mode": "top_n", "top_n": 2}, + ) + + assert captured["regions"] == custom_regions + assert result.metadata["contrast_scope"] == "custom" + assert result.selected_regions["name"].tolist() == ["chr1:0-500", "chr1:500-1000"] + + +def test_discovery_cluster_contrast_workflow_preserves_full_scan_windows_context( + monkeypatch, +): + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + monkeypatch.setattr( + workflows.region_contrasts, "score_regions", _mock_region_contrast_result + ) + + result = workflows.discovery_cluster_contrast_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + contrasts={ + "contrast": ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + ), + }, + ) + + assert result.metadata["full_scan_windows"].equals(result.discovery.windows) + + +def test_discovery_cluster_contrast_workflow_rejects_missing_contrast_config( + monkeypatch, +): + monkeypatch.setattr( + workflows.region_discovery, "scan_genome", _mock_discovery_result + ) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + + with pytest.raises(ValueError, match=r"requires contrasts\['contrast'\]"): + workflows.discovery_cluster_contrast_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + contrasts={"test": "effect_size_only"}, + ) + + +def test_discovery_cluster_contrast_workflow_fast_fails_invalid_contrast_config_before_scan( + monkeypatch, +): + called = {"scan_genome": False} + + def fake_scan_genome(*args, **kwargs): + called["scan_genome"] = True + raise AssertionError( + "scan_genome should not be called for invalid contrast config" + ) + + monkeypatch.setattr(workflows.region_discovery, "scan_genome", fake_scan_genome) + monkeypatch.setattr(workflows, "shared_cluster_distribution", _mock_cluster_result) + + with pytest.raises(ValueError, match="read_mod_fraction"): + workflows.discovery_cluster_contrast_workflow( + samples=_workflow_samples(), + motifs=["A,0"], + genome_sizes={"chr1": 1500}, + discovery={"window_size": 500, "step_size": 500}, + clustering={"mode": "region_anchored", "n_clusters": 2}, + contrasts={ + "contrast": ContrastSpec( + mode="pairwise", + numerator=["15min"], + denominator=["NS"], + ), + "analysis_unit": "single_read", + }, + ) + + assert called["scan_genome"] is False + + +def test_shared_cluster_distribution_read_global(monkeypatch): + fake_samples = [ + SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5"), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5"), + ] + + def fake_extract(*args, **kwargs): + hdf5_file = kwargs["hdf5_file"] + + class R: + if hdf5_file == "s1.h5": + data_matrix = np.array([[0.0, 0.0, 0.1, 0.1], [0.0, 0.1, 0.0, 0.1]]) + metadata = [ + { + "read_name": "s1-r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + }, + { + "read_name": "s1-r2", + "chromosome": "chr1", + "region_start": 10, + "region_end": 14, + }, + ] + else: + data_matrix = np.array([[1.0, 0.9, 1.0, 0.9], [0.9, 1.0, 0.9, 1.0]]) + metadata = [ + { + "read_name": "s2-r1", + "chromosome": "chr1", + "region_start": 20, + "region_end": 24, + }, + { + "read_name": "s2-r2", + "chromosome": "chr1", + "region_start": 30, + "region_end": 34, + }, + ] + val_matrix = np.ones((2, 4), dtype=float) + datasets = [] + regions_dict = None + + return R() + + def fake_features(result, **kwargs): + return result.data_matrix, ["f0", "f1", "f2", "f3"] + + monkeypatch.setattr(workflows.cluster, "extract_read_windows", fake_extract) + monkeypatch.setattr(workflows.cluster, "read_window_feature_matrix", fake_features) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="read_global", + motifs=["A,0"], + n_clusters=2, + training_sample_per_dataset=2, + make_plots=False, + ) + + assert not result.assignments.empty + assert not result.cluster_distribution.empty + assert not result.condition_distribution.empty + assert not result.cluster_profiles.empty + assert set(result.assignments["sample_id"]) == {"s1", "s2"} + assert "replicate" in result.assignments.columns + assert "cluster_distribution_bar" in result.plot_data + assert "cluster_distribution_heatmap" in result.plot_data + assert result.metadata["artifact_policy"] == "prefer_cached" + assert set(result.metadata["cache_misses"]) == {"s1", "s2"} + + +def test_shared_cluster_distribution_read_global_builds_region_summaries_from_coordinates( + monkeypatch, +): + fake_samples = [ + SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5"), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5"), + ] + + def fake_extract(*args, **kwargs): + hdf5_file = kwargs["hdf5_file"] + + class R: + if hdf5_file == "s1.h5": + data_matrix = np.array([[0.0, 0.0, 0.1, 0.1], [0.0, 0.1, 0.0, 0.1]]) + metadata = [ + { + "read_name": "s1-r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + }, + { + "read_name": "s1-r2", + "chromosome": "chr1", + "region_start": 10, + "region_end": 14, + }, + ] + else: + data_matrix = np.array([[1.0, 0.9, 1.0, 0.9], [0.9, 1.0, 0.9, 1.0]]) + metadata = [ + { + "read_name": "s2-r1", + "chromosome": "chr1", + "region_start": 20, + "region_end": 24, + "region_strand": "+", + }, + { + "read_name": "s2-r2", + "chromosome": "chr1", + "region_start": 30, + "region_end": 34, + "region_strand": "-", + }, + ] + val_matrix = np.ones((2, 4), dtype=float) + datasets = [] + regions_dict = None + + return R() + + monkeypatch.setattr(workflows.cluster, "extract_read_windows", fake_extract) + monkeypatch.setattr( + workflows.cluster, + "read_window_feature_matrix", + lambda result, **kwargs: (result.data_matrix, ["f0", "f1", "f2", "f3"]), + ) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="read_global", + motifs=["A,0"], + n_clusters=2, + training_sample_per_dataset=2, + make_plots=False, + ) + + assert result.region_summaries is not None + assert { + "region_id", + "sample_id", + "condition", + "cluster", + "count", + "fraction", + } <= set(result.region_summaries.columns) + assert set(result.region_summaries["sample_id"]) == {"s1", "s2"} + assert set(result.region_summaries["condition"]) == {"NS", "15min"} + + +def test_shared_cluster_distribution_read_global_leaves_region_summaries_empty_without_coordinates( + monkeypatch, +): + fake_samples = [ + SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5"), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5"), + ] + + def fake_extract(*args, **kwargs): + class R: + data_matrix = np.array([[0.0, 0.0], [1.0, 1.0]]) + val_matrix = np.ones((2, 2), dtype=float) + metadata = [{"read_name": "r1"}, {"read_name": "r2"}] + datasets = [] + regions_dict = None + + return R() + + monkeypatch.setattr(workflows.cluster, "extract_read_windows", fake_extract) + monkeypatch.setattr( + workflows.cluster, + "read_window_feature_matrix", + lambda result, **kwargs: (result.data_matrix, ["f0", "f1"]), + ) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="read_global", + motifs=["A,0"], + n_clusters=2, + training_sample_per_dataset=2, + make_plots=False, + ) + + assert result.region_summaries is None + + +def test_shared_cluster_distribution_read_global_prefers_cluster_summarizer( + monkeypatch, +): + fake_samples = [ + SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5"), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5"), + ] + called = {"count": 0} + + def fake_extract(*args, **kwargs): + hdf5_file = kwargs["hdf5_file"] + + class R: + if hdf5_file == "s1.h5": + data_matrix = np.array([[0.0, 0.0], [0.0, 0.1]]) + metadata = [ + { + "read_name": "s1-r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 2, + }, + { + "read_name": "s1-r2", + "chromosome": "chr1", + "region_start": 10, + "region_end": 12, + }, + ] + else: + data_matrix = np.array([[1.0, 1.0], [0.9, 1.0]]) + metadata = [ + { + "read_name": "s2-r1", + "chromosome": "chr1", + "region_start": 20, + "region_end": 22, + }, + { + "read_name": "s2-r2", + "chromosome": "chr1", + "region_start": 30, + "region_end": 32, + }, + ] + val_matrix = np.ones((2, 2), dtype=float) + datasets = [] + regions_dict = None + + return R() + + def fake_summarizer(*, metadata, labels, include_strand=True): + called["count"] += 1 + if metadata: + first = metadata[0] + chrom = first.get("chromosome") + start = int(first.get("region_start")) + end = int(first.get("region_end")) + else: + chrom, start, end = "chr1", 0, 1 + return pd.DataFrame( + [ + { + "chrom": chrom, + "start": start, + "end": end, + "strand": "+", + "cluster": labels[0] if len(labels) else "C0", + "count": len(labels), + "fraction": 1.0 if len(labels) else 0.0, + "source": "summarizer", + } + ] + ) + + monkeypatch.setattr(workflows.cluster, "extract_read_windows", fake_extract) + monkeypatch.setattr( + workflows.cluster, + "read_window_feature_matrix", + lambda result, **kwargs: (result.data_matrix, ["f0", "f1"]), + ) + monkeypatch.setattr( + workflows.cluster, + "summarize_read_cluster_region_associations", + fake_summarizer, + raising=False, + ) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="read_global", + motifs=["A,0"], + n_clusters=2, + training_sample_per_dataset=2, + make_plots=False, + ) + + assert called["count"] == len(fake_samples) + assert result.region_summaries is not None + assert "source" in result.region_summaries.columns + assert set(result.region_summaries["source"]) == {"summarizer"} + + +def test_shared_cluster_distribution_propagates_sample_metadata_into_assignments_read_global( + monkeypatch, +): + fake_samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={ + "subject_id": "mouse-1", + "sample_id": "overwrite-attempt", + "condition": "overwrite-attempt", + "replicate": 99, + "read_name": "overwrite-attempt", + "chromosome": "wrong-chr", + "region_start": 999, + "region_end": 1000, + }, + ), + SampleSpec( + sample_id="s2", + condition="15min", + extract_h5="s2.h5", + metadata={ + "subject_id": "mouse-2", + "sample_id": "overwrite-attempt", + "condition": "overwrite-attempt", + "replicate": 88, + "read_name": "overwrite-attempt", + "chromosome": "wrong-chr", + "region_start": 999, + "region_end": 1000, + }, + ), + ] + + def fake_extract(*args, **kwargs): + hdf5_file = kwargs["hdf5_file"] + + class R: + if hdf5_file == "s1.h5": + data_matrix = np.array([[0.0, 0.0, 0.1, 0.1], [0.0, 0.1, 0.0, 0.1]]) + metadata = [ + { + "read_name": "s1-r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 4, + }, + { + "read_name": "s1-r2", + "chromosome": "chr1", + "region_start": 10, + "region_end": 14, + }, + ] + else: + data_matrix = np.array([[1.0, 0.9, 1.0, 0.9], [0.9, 1.0, 0.9, 1.0]]) + metadata = [ + { + "read_name": "s2-r1", + "chromosome": "chr1", + "region_start": 20, + "region_end": 24, + }, + { + "read_name": "s2-r2", + "chromosome": "chr1", + "region_start": 30, + "region_end": 34, + }, + ] + val_matrix = np.ones((2, 4), dtype=float) + datasets = [] + regions_dict = None + + return R() + + monkeypatch.setattr(workflows.cluster, "extract_read_windows", fake_extract) + monkeypatch.setattr( + workflows.cluster, + "read_window_feature_matrix", + lambda result, **kwargs: (result.data_matrix, ["f0", "f1", "f2", "f3"]), + ) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="read_global", + motifs=["A,0"], + n_clusters=2, + training_sample_per_dataset=2, + ) + + assert "subject_id" in result.assignments.columns + by_read_name = result.assignments.set_index("read_name")[ + ["subject_id", "sample_id", "condition", "region_start", "region_end"] + ].to_dict("index") + assert by_read_name == { + "s1-r1": { + "subject_id": "mouse-1", + "sample_id": "s1", + "condition": "NS", + "region_start": 0, + "region_end": 4, + }, + "s1-r2": { + "subject_id": "mouse-1", + "sample_id": "s1", + "condition": "NS", + "region_start": 10, + "region_end": 14, + }, + "s2-r1": { + "subject_id": "mouse-2", + "sample_id": "s2", + "condition": "15min", + "region_start": 20, + "region_end": 24, + }, + "s2-r2": { + "subject_id": "mouse-2", + "sample_id": "s2", + "condition": "15min", + "region_start": 30, + "region_end": 34, + }, + } + assert set(result.assignments["replicate"]) == {None} + assert set(result.assignments["chromosome"]) == {"chr1"} + + +def test_shared_cluster_distribution_propagates_sample_metadata_into_assignments_region_anchored( + monkeypatch, +): + fake_samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + regions_bed="r1.bed", + metadata={ + "pileup_path": "s1.bed.gz", + "subject_id": "mouse-1", + "sample_id": "overwrite-attempt", + "condition": "overwrite-attempt", + "replicate": 99, + "region_id": "wrong-region", + "chromosome": "wrong-chr", + "start": 999, + "end": 1000, + "strand": "-", + }, + ), + SampleSpec( + sample_id="s2", + condition="15min", + extract_h5="s2.h5", + regions_bed="r2.bed", + metadata={ + "pileup_path": "s2.bed.gz", + "subject_id": "mouse-2", + "sample_id": "overwrite-attempt", + "condition": "overwrite-attempt", + "replicate": 88, + "region_id": "wrong-region", + "chromosome": "wrong-chr", + "start": 999, + "end": 1000, + "strand": "-", + }, + ), + ] + + def fake_region_table(*args, **kwargs): + return np.array([[0.2, 0.8], [0.7, 0.3]], dtype=np.float32), [ + { + "sample_id": "s1", + "condition": "NS", + "replicate": None, + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + { + "sample_id": "s2", + "condition": "15min", + "replicate": None, + "region_id": "reg1", + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + ] + + monkeypatch.setattr( + workflows.region_analysis, "build_region_feature_table", fake_region_table + ) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="region_anchored", + motifs=["A,0"], + matched_regions="matched.bed", + n_clusters=2, + make_plots=False, + ) + + assert "subject_id" in result.assignments.columns + by_region = result.assignments.set_index(["sample_id", "region_id"])[ + ["subject_id", "condition", "replicate", "chromosome", "start", "end", "strand"] + ].to_dict("index") + assert by_region == { + ("s1", "reg1"): { + "subject_id": "mouse-1", + "condition": "NS", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + ("s2", "reg1"): { + "subject_id": "mouse-2", + "condition": "15min", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + } + + +def test_shared_cluster_distribution_rejects_unsupported_mode(): + with pytest.raises(NotImplementedError, match="region_anchored"): + workflows.shared_cluster_distribution( + samples=[ + SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5"), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5"), + ], + mode="unknown_mode", + motifs=["A,0"], + ) + + +def test_shared_cluster_distribution_requires_two_datasets(): + with pytest.raises(ValueError, match="at least two datasets"): + workflows.shared_cluster_distribution( + samples=[SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5")], + mode="read_global", + motifs=["A,0"], + ) + + +def test_shared_cluster_distribution_region_anchored(monkeypatch): + fake_samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + regions_bed="r1.bed", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="15min", + extract_h5="s2.h5", + regions_bed="r2.bed", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + + def fake_region_table(*args, **kwargs): + return np.array([[0.2, 0.8], [0.7, 0.3]]), [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "15min", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + ] + + monkeypatch.setattr( + workflows.region_analysis, "build_region_feature_table", fake_region_table + ) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="region_anchored", + motifs=["A,0"], + matched_regions="matched.bed", + n_clusters=2, + make_plots=False, + ) + + assert not result.assignments.empty + assert "region_id" in result.assignments.columns + assert result.region_summaries is not None + assert { + "region_id", + "sample_id", + "condition", + "cluster", + "count", + "fraction", + } <= set(result.region_summaries.columns) + + +def test_shared_cluster_region_data_region_anchored_region_summaries_feed_region_plotting( + monkeypatch, +): + fake_samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + regions_bed="r1.bed", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="treated", + extract_h5="s2.h5", + regions_bed="r2.bed", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + + def fake_region_table(*args, **kwargs): + return np.array([[0.1, 0.9], [0.9, 0.1]]), [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "treated", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + ] + + monkeypatch.setattr( + workflows.region_analysis, "build_region_feature_table", fake_region_table + ) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="region_anchored", + motifs=["A,0"], + matched_regions="matched.bed", + n_clusters=2, + make_plots=False, + ) + + payload = plotting.prepare_shared_cluster_region_data(result=result) + + assert payload["region_table"].iloc[0].to_dict() == { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "cluster": "C0", + "count": 1, + "fraction": pytest.approx(1.0), + } + assert payload["condition_region_table"].iloc[0].to_dict() == { + "region_id": "reg1", + "condition": "NS", + "cluster": "C0", + "count": 1, + "fraction_mean": pytest.approx(1.0), + "fraction_median": pytest.approx(1.0), + "sample_n": 1, + } + + +def test_shared_cluster_distribution_region_anchored_requires_matched_regions(): + with pytest.raises(ValueError, match="requires matched_regions"): + workflows.shared_cluster_distribution( + samples=_workflow_samples(), + mode="region_anchored", + motifs=["A,0"], + make_plots=False, + ) + + +def test_shared_cluster_distribution_region_anchored_accepts_list_matched_regions( + monkeypatch, +): + captured = {} + + def fake_region_table(*args, **kwargs): + captured["matched_regions"] = kwargs["matched_regions"] + return np.array([[0.2, 0.8], [0.7, 0.3]]), [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "15min", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + ] + + monkeypatch.setattr( + workflows.region_analysis, "build_region_feature_table", fake_region_table + ) + + result = workflows.shared_cluster_distribution( + samples=_workflow_samples(), + mode="region_anchored", + motifs=["A,0"], + matched_regions=["chr1:0-2,+", "chr1:2-4,-"], + n_clusters=2, + make_plots=False, + ) + + assert captured["matched_regions"] == ["chr1:0-2,+", "chr1:2-4,-"] + assert not result.assignments.empty + + +def test_shared_cluster_distribution_tracks_condition_replicates(monkeypatch): + fake_samples = [ + SampleSpec(sample_id="s1", condition="NS", extract_h5="s1.h5"), + SampleSpec(sample_id="s2", condition="NS", extract_h5="s2.h5"), + SampleSpec(sample_id="s3", condition="15min", extract_h5="s3.h5"), + ] + + matrices = { + "s1.h5": np.array([[0.0, 0.0], [0.0, 0.1]]), + "s2.h5": np.array([[0.1, 0.0], [0.0, 0.0]]), + "s3.h5": np.array([[1.0, 1.0], [0.9, 1.0]]), + } + + def fake_extract(*args, **kwargs): + data = matrices[kwargs["hdf5_file"]] + + class R: + data_matrix = data + val_matrix = np.ones_like(data) + metadata = [ + { + "read_name": f"{kwargs['hdf5_file']}-r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 2, + }, + { + "read_name": f"{kwargs['hdf5_file']}-r2", + "chromosome": "chr1", + "region_start": 2, + "region_end": 4, + }, + ] + datasets = [] + regions_dict = None + + return R() + + monkeypatch.setattr(workflows.cluster, "extract_read_windows", fake_extract) + monkeypatch.setattr( + workflows.cluster, + "read_window_feature_matrix", + lambda result, **kwargs: (result.data_matrix, ["f0", "f1"]), + ) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="read_global", + motifs=["A,0"], + n_clusters=2, + training_sample_per_dataset=2, + ) + + ns_rows = result.condition_distribution[ + result.condition_distribution["condition"] == "NS" + ] + assert set(ns_rows["replicate_n"]) == {2} + + +def test_shared_cluster_distribution_region_anchored_rejects_multi_motif(monkeypatch): + fake_samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + regions_bed="r1.bed", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="15min", + extract_h5="s2.h5", + regions_bed="r2.bed", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + + with pytest.raises(ValueError, match="exactly one motif"): + workflows.shared_cluster_distribution( + samples=fake_samples, + mode="region_anchored", + motifs=["A,0", "CG,0"], + matched_regions="matched.bed", + ) + + +def test_shared_cluster_distribution_region_anchored_control_regions(monkeypatch): + fake_samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + regions_bed="control-s1.bed", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="15min", + extract_h5="s2.h5", + regions_bed="control-s2.bed", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + + def fake_region_table(*args, **kwargs): + matched_regions = kwargs["matched_regions"] + if matched_regions == "matched.bed": + return np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32), [ + { + "region_id": "reg1", + "sample_id": "s1", + "condition": "NS", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + { + "region_id": "reg1", + "sample_id": "s2", + "condition": "15min", + "replicate": None, + "chromosome": "chr1", + "start": 0, + "end": 2, + "strand": "+", + }, + ] + if matched_regions == "control-s1.bed": + return np.array([[0.5, 0.5]], dtype=np.float32), [ + { + "region_id": "c1", + "sample_id": "s1", + "condition": "NS", + "replicate": None, + "chromosome": "chr1", + "start": 10, + "end": 12, + "strand": "+", + } + ] + return np.array([[0.25, 0.25]], dtype=np.float32), [ + { + "region_id": "c2", + "sample_id": "s2", + "condition": "15min", + "replicate": None, + "chromosome": "chr1", + "start": 20, + "end": 22, + "strand": "+", + } + ] + + monkeypatch.setattr( + workflows.region_analysis, "build_region_feature_table", fake_region_table + ) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="region_anchored", + motifs=["A,0"], + matched_regions="matched.bed", + signal_normalization="control_regions", + n_clusters=2, + ) + + assert result.metadata["sample_normalization"]["s1"]["global_offset"] == 0.5 + assert result.metadata["sample_normalization"]["s2"]["global_offset"] == 0.25 + + +def test_shared_cluster_distribution_prefers_cached_extract_artifact(monkeypatch): + cached_artifact = DatasetArtifact( + sample_id="s1", + artifact_type="extract", + path="cached-s1.h5", + format="h5", + params={ + "motifs": ["A,0"], + "matched_regions": None, + "window_size": None, + "signal_normalization": "none", + "feature_scaling": "robust_zscore", + "cluster_basis": "shape_plus_level", + }, + provenance={ + "pipeline": "parse_bam", + "source_files": ["s1.h5"], + "source_fingerprints": [{"path": "s1.h5", "missing": True}], + "upstream_lineage": [], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.0.0"}, + ) + fake_samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"artifacts": [cached_artifact]}, + ), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5"), + ] + seen_paths = [] + + def fake_extract(*args, **kwargs): + seen_paths.append(kwargs["hdf5_file"]) + + class R: + data_matrix = np.array([[0.0, 0.0], [1.0, 1.0]]) + val_matrix = np.ones((2, 2), dtype=float) + metadata = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 2, + }, + { + "read_name": "r2", + "chromosome": "chr1", + "region_start": 2, + "region_end": 4, + }, + ] + datasets = [] + regions_dict = None + + return R() + + monkeypatch.setattr(workflows.cluster, "extract_read_windows", fake_extract) + monkeypatch.setattr( + workflows.cluster, + "read_window_feature_matrix", + lambda result, **kwargs: (result.data_matrix, ["f0", "f1"]), + ) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="read_global", + motifs=["A,0"], + n_clusters=2, + training_sample_per_dataset=2, + ) + + assert seen_paths[0] == "cached-s1.h5" + assert result.metadata["cache_hits"]["s1"] == "cached-s1.h5" + + +def test_shared_cluster_distribution_rebuilds_when_source_fingerprint_mismatches( + monkeypatch, +): + stale_artifact = DatasetArtifact( + sample_id="s1", + artifact_type="extract", + path="cached-s1.h5", + format="h5", + params={ + "motifs": ["A,0"], + "matched_regions": None, + "window_size": None, + "signal_normalization": "none", + "feature_scaling": "robust_zscore", + "cluster_basis": "shape_plus_level", + }, + provenance={ + "pipeline": "parse_bam", + "source_files": ["s1.h5"], + "source_fingerprints": [{"path": "s1.h5", "size": 123, "mtime_ns": 1}], + "upstream_lineage": [], + }, + metadata={"schema_version": "artifact-v1", "package_version": "1.0.0"}, + ) + fake_samples = [ + SampleSpec( + sample_id="s1", + condition="NS", + extract_h5="s1.h5", + metadata={"artifacts": [stale_artifact]}, + ), + SampleSpec(sample_id="s2", condition="15min", extract_h5="s2.h5"), + ] + seen_paths = [] + + def fake_extract(*args, **kwargs): + seen_paths.append(kwargs["hdf5_file"]) + + class R: + data_matrix = np.array([[0.0, 0.0], [1.0, 1.0]]) + val_matrix = np.ones((2, 2), dtype=float) + metadata = [ + { + "read_name": "r1", + "chromosome": "chr1", + "region_start": 0, + "region_end": 2, + }, + { + "read_name": "r2", + "chromosome": "chr1", + "region_start": 2, + "region_end": 4, + }, + ] + datasets = [] + regions_dict = None + + return R() + + monkeypatch.setattr(workflows.cluster, "extract_read_windows", fake_extract) + monkeypatch.setattr( + workflows.cluster, + "read_window_feature_matrix", + lambda result, **kwargs: (result.data_matrix, ["f0", "f1"]), + ) + + result = workflows.shared_cluster_distribution( + samples=fake_samples, + mode="read_global", + motifs=["A,0"], + n_clusters=2, + training_sample_per_dataset=2, + ) + + assert seen_paths[0] == "s1.h5" + assert "s1" not in result.metadata["cache_hits"] + + +def test_chip_atlas_enrichment_workflow_forwards_to_module(monkeypatch): + captured = {} + + def fake_run_enrichment(**kwargs): + captured.update(kwargs) + return ChipAtlasEnrichmentResult( + request_id="REQ1", + status="finished", + results=pd.DataFrame({"target": ["CTCF"]}), + ) + + monkeypatch.setattr(workflows.chip_atlas, "run_enrichment", fake_run_enrichment) + + result = workflows.chip_atlas_enrichment_workflow( + regions=["chr1:0-100,+"], + genome="hg38", + regions_genome="chm13", + cell_type="K562", + crossmap_chain_cache_dir="cache/chains", + fetch_results=False, + ) + + assert result.request_id == "REQ1" + assert captured["regions"] == ["chr1:0-100,+"] + assert captured["genome"] == "hg38" + assert captured["regions_genome"] == "chm13" + assert captured["antigen_class"] == "TFs and others" + assert captured["cell_type_class"] == "No description" + assert captured["threshold"] == "100" + assert captured["cell_type"] == "K562" + assert captured["crossmap_chain_cache_dir"] == "cache/chains" + assert captured["fetch_results"] is False + + +def test_chip_atlas_cluster_enrichment_workflow_per_cluster(monkeypatch): + region_summaries = pd.DataFrame( + [ + {"region_id": "chr1:0-100,+", "cluster": "C0", "fraction": 0.8}, + {"region_id": "chr1:100-200,+", "cluster": "C0", "fraction": 0.2}, + {"region_id": "chr2:0-100,-", "cluster": "C1", "fraction": 0.9}, + ] + ) + cluster_result = SharedClusterResult( + model=SharedClusterModel( + mode="read_global", + motifs=["A,0"], + feature_names=["f0"], + preprocessing={}, + estimator=object(), + cluster_labels=["C0", "C1"], + fit_metadata={}, + ), + assignments=pd.DataFrame( + [ + {"sample_id": "s1", "condition": "NS", "cluster": "C0"}, + {"sample_id": "s2", "condition": "TX", "cluster": "C1"}, + ] + ), + cluster_distribution=pd.DataFrame( + [ + { + "sample_id": "s1", + "condition": "NS", + "cluster": "C0", + "count": 1, + "fraction": 1.0, + } + ] + ), + condition_distribution=pd.DataFrame( + [{"condition": "NS", "cluster": "C0", "fraction": 1.0, "replicate_n": 1}] + ), + distribution_change=None, + cluster_profiles=pd.DataFrame([{"cluster": "C0", "count": 1, "f0": 0.1}]), + region_summaries=region_summaries, + plot_data={}, + ) + + called = [] + + def fake_chip_atlas_workflow(*, regions, **kwargs): + called.append(regions.copy()) + return ChipAtlasEnrichmentResult( + request_id=f"REQ-{len(called)}", + status="finished", + results=pd.DataFrame({"target": ["CTCF"]}), + ) + + monkeypatch.setattr( + workflows, "chip_atlas_enrichment_workflow", fake_chip_atlas_workflow + ) + + results = workflows.chip_atlas_cluster_enrichment_workflow( + cluster_result=cluster_result, + clusters=["C0", "C1"], + min_fraction=0.5, + cell_type="K562", + ) + + assert set(results.keys()) == {"C0", "C1"} + assert len(called) == 2 + assert all(frame.shape[0] >= 1 for frame in called) + assert all( + {"chrom", "start", "end", "strand"}.issubset(frame.columns) for frame in called + ) + + +def test_chip_atlas_search_peak_datasets_workflow_forwards(monkeypatch): + captured = {} + + def fake_search(**kwargs): + captured.update(kwargs) + return pd.DataFrame( + [{"dataset_id": "SRX1", "bed_url": "http://example.org/srx1.bed"}] + ) + + monkeypatch.setattr(workflows.chip_atlas, "search_peak_datasets", fake_search) + out = workflows.chip_atlas_search_peak_datasets_workflow( + antigen="CTCF", + genome="hg38", + cell_type="K562", + ) + assert out.shape[0] == 1 + assert captured["antigen"] == "CTCF" + assert captured["genome"] == "hg38" + assert captured["cell_type"] == "K562" + + +def test_chip_atlas_download_peak_datasets_workflow_forwards(monkeypatch): + captured = {} + + def fake_download(**kwargs): + captured.update(kwargs) + return pd.DataFrame([{"dataset_id": "SRX1", "variant": "top_3000"}]) + + monkeypatch.setattr(workflows.chip_atlas, "download_peak_datasets", fake_download) + datasets = pd.DataFrame( + [ + { + "dataset_id": "SRX1", + "bed_url": "http://example.org/srx1.bed", + "genome_assembly": "hg38", + } + ] + ) + out = workflows.chip_atlas_download_peak_datasets_workflow( + datasets=datasets, + dataset_ids=["SRX1"], + stratify="quartiles", + ) + assert out.shape[0] == 1 + assert captured["datasets"].equals(datasets) + assert captured["dataset_ids"] == ["SRX1"] + assert captured["stratify"] == "quartiles" + + +def test_modkit_dmr_pair_workflow_forwards(monkeypatch): + captured = {} + + def fake_run_dmr_pair(**kwargs): + captured.update(kwargs) + from dimelo.models import ModkitDMRPairResult + + return ModkitDMRPairResult( + output_path=Path("/tmp/out.bed"), + segment_path=None, + command=["modkit", "dmr", "pair"], + sites=pd.DataFrame(), + segments=None, + high_confidence_sites=pd.DataFrame(), + metadata={}, + ) + + monkeypatch.setattr(workflows.dmr, "run_dmr_pair", fake_run_dmr_pair) + out = workflows.modkit_dmr_pair_workflow( + control_bed_methyl="a.bed.gz", + experiment_bed_methyl="b.bed.gz", + ref_genome="ref.fa", + out_path="out.bed", + bases=["A"], + ) + assert out.output_path == Path("/tmp/out.bed") + assert captured["control_bed_methyl"] == "a.bed.gz" + assert captured["bases"] == ["A"] + + +def test_modkit_dmr_multi_workflow_forwards(monkeypatch): + captured = {} + + def fake_run_dmr_multi(**kwargs): + captured.update(kwargs) + from dimelo.models import ModkitDMRMultiResult + + return ModkitDMRMultiResult( + out_dir=Path("/tmp/out"), + command=["modkit", "dmr", "multi"], + pair_files=pd.DataFrame([{"pair_name": "s1_vs_s2"}]), + metadata={}, + ) + + monkeypatch.setattr(workflows.dmr, "run_dmr_multi", fake_run_dmr_multi) + out = workflows.modkit_dmr_multi_workflow( + samples={"s1": "s1.bed.gz", "s2": "s2.bed.gz"}, + regions_bed="regions.bed", + ref_genome="ref.fa", + out_dir="out", + bases=["A"], + ) + assert out.pair_files.shape[0] == 1 + assert captured["samples"] == {"s1": "s1.bed.gz", "s2": "s2.bed.gz"} + assert captured["bases"] == ["A"] + + +def test_modkit_dmr_multi_from_samples_workflow_reads_pileup_paths(monkeypatch): + captured = {} + + def fake_multi(**kwargs): + captured.update(kwargs) + from dimelo.models import ModkitDMRMultiResult + + return ModkitDMRMultiResult( + out_dir=Path("/tmp/out"), + command=["modkit", "dmr", "multi"], + pair_files=pd.DataFrame([{"pair_name": "s1_vs_s2"}]), + metadata={}, + ) + + monkeypatch.setattr(workflows, "modkit_dmr_multi_workflow", fake_multi) + samples = [ + SampleSpec( + sample_id="s1", + condition="A", + extract_h5="s1.h5", + metadata={"pileup_path": "s1.bed.gz"}, + ), + SampleSpec( + sample_id="s2", + condition="B", + extract_h5="s2.h5", + metadata={"pileup_path": "s2.bed.gz"}, + ), + ] + out = workflows.modkit_dmr_multi_from_samples_workflow( + samples=samples, + regions_bed="regions.bed", + ref_genome="ref.fa", + out_dir="out", + ) + assert out.pair_files.shape[0] == 1 + assert captured["samples"] == {"s1": "s1.bed.gz", "s2": "s2.bed.gz"} diff --git a/tutorial.ipynb b/tutorial.ipynb index bf7b85d..f859036 100644 --- a/tutorial.ipynb +++ b/tutorial.ipynb @@ -20,21 +20,9 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "You don't seem to be running on Google Colab so this cell is not going to be useful for you.\n", - "Make sure you are running you notebook in a conda environment set up as per README.md.\n", - "Once you have this notebook running in a correctly configured environment, proceed to the rest of the cells.\n", - " \n" - ] - } - ], + "outputs": [], "source": [ "# Lets Colab access your Google drive\n", "try:\n", @@ -52,12 +40,12 @@ " import condacolab\n", " condacolab.install()\n", " # Install modkit\n", - " !conda install nanoporetech::modkit==0.2.4\n", + " !conda install nanoporetech::modkit==0.6.1\n", " # Clone the repo, change the active path to be inside the repo, and install the package\n", - " !rm -r dimelo\n", - " !git clone https://github.com/streetslab/dimelo\n", + " !rm -rf dimelo-toolkit\n", + " !git clone https://github.com/streetslab/dimelo-toolkit\n", " import os\n", - " os.chdir('dimelo')\n", + " os.chdir('dimelo-toolkit')\n", " !pip install ipywidgets==7.7.1 .\n" ] }, @@ -78,7 +66,7 @@ " from google.colab import drive\n", " drive.mount('/content/drive')\n", " import os\n", - " os.chdir('dimelo') \n", + " os.chdir('dimelo-toolkit') \n", "except:\n", " pass" ] @@ -106,156 +94,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "# packages in environment at /opt/anaconda3/envs/dimelo:\n", - "#\n", - "# Name Version Build Channel\n", - "anyio 4.9.0 pypi_0 pypi\n", - "appnope 0.1.4 pypi_0 pypi\n", - "argon2-cffi 25.1.0 pypi_0 pypi\n", - "argon2-cffi-bindings 21.2.0 pypi_0 pypi\n", - "arrow 1.3.0 pypi_0 pypi\n", - "asttokens 3.0.0 pypi_0 pypi\n", - "async-lru 2.0.5 pypi_0 pypi\n", - "attrs 25.3.0 pypi_0 pypi\n", - "babel 2.17.0 pypi_0 pypi\n", - "beautifulsoup4 4.13.4 pypi_0 pypi\n", - "bleach 6.2.0 pypi_0 pypi\n", - "bzip2 1.0.8 h99b78c6_7 conda-forge\n", - "ca-certificates 2025.4.26 hbd8a1cb_0 conda-forge\n", - "certifi 2025.4.26 pypi_0 pypi\n", - "cffi 1.17.1 pypi_0 pypi\n", - "charset-normalizer 3.4.2 pypi_0 pypi\n", - "comm 0.2.2 pypi_0 pypi\n", - "contourpy 1.3.2 pypi_0 pypi\n", - "cycler 0.12.1 pypi_0 pypi\n", - "debugpy 1.8.14 pypi_0 pypi\n", - "decorator 5.2.1 pypi_0 pypi\n", - "defusedxml 0.7.1 pypi_0 pypi\n", - "dimelo 1.0.0 pypi_0 pypi\n", - "executing 2.2.0 pypi_0 pypi\n", - "fastjsonschema 2.21.1 pypi_0 pypi\n", - "fonttools 4.58.1 pypi_0 pypi\n", - "fqdn 1.5.1 pypi_0 pypi\n", - "h11 0.16.0 pypi_0 pypi\n", - "h5py 3.13.0 pypi_0 pypi\n", - "httpcore 1.0.9 pypi_0 pypi\n", - "httpx 0.28.1 pypi_0 pypi\n", - "idna 3.10 pypi_0 pypi\n", - "ipykernel 6.29.5 pypi_0 pypi\n", - "ipython 9.3.0 pypi_0 pypi\n", - "ipython-pygments-lexers 1.1.1 pypi_0 pypi\n", - "ipywidgets 8.1.7 pypi_0 pypi\n", - "isoduration 20.11.0 pypi_0 pypi\n", - "jedi 0.19.2 pypi_0 pypi\n", - "jinja2 3.1.6 pypi_0 pypi\n", - "json5 0.12.0 pypi_0 pypi\n", - "jsonpointer 3.0.0 pypi_0 pypi\n", - "jsonschema 4.24.0 pypi_0 pypi\n", - "jsonschema-specifications 2025.4.1 pypi_0 pypi\n", - "jupyter-client 8.6.3 pypi_0 pypi\n", - "jupyter-core 5.8.1 pypi_0 pypi\n", - "jupyter-events 0.12.0 pypi_0 pypi\n", - "jupyter-lsp 2.2.5 pypi_0 pypi\n", - "jupyter-server 2.16.0 pypi_0 pypi\n", - "jupyter-server-terminals 0.5.3 pypi_0 pypi\n", - "jupyterlab 4.4.3 pypi_0 pypi\n", - "jupyterlab-pygments 0.3.0 pypi_0 pypi\n", - "jupyterlab-server 2.27.3 pypi_0 pypi\n", - "jupyterlab-widgets 3.0.15 pypi_0 pypi\n", - "kaleido 0.2.1 pypi_0 pypi\n", - "kiwisolver 1.4.8 pypi_0 pypi\n", - "libcxx 20.1.6 ha82da77_0 conda-forge\n", - "libexpat 2.7.0 h286801f_0 conda-forge\n", - "libffi 3.4.6 h1da3d7d_1 conda-forge\n", - "liblzma 5.8.1 h39f12f2_1 conda-forge\n", - "liblzma-devel 5.8.1 h39f12f2_1 conda-forge\n", - "libsqlite 3.46.0 hfb93653_0 conda-forge\n", - "libzlib 1.2.13 hfb2fe0b_6 conda-forge\n", - "markupsafe 3.0.2 pypi_0 pypi\n", - "matplotlib 3.10.3 pypi_0 pypi\n", - "matplotlib-inline 0.1.7 pypi_0 pypi\n", - "mistune 3.1.3 pypi_0 pypi\n", - "modkit 0.2.4 h70deae4_1 nanoporetech\n", - "narwhals 1.41.0 pypi_0 pypi\n", - "nbclient 0.10.2 pypi_0 pypi\n", - "nbconvert 7.16.6 pypi_0 pypi\n", - "nbformat 5.10.4 pypi_0 pypi\n", - "ncurses 6.5 h5e97a16_3 conda-forge\n", - "nest-asyncio 1.6.0 pypi_0 pypi\n", - "notebook 7.4.3 pypi_0 pypi\n", - "notebook-shim 0.2.4 pypi_0 pypi\n", - "numpy 2.2.6 pypi_0 pypi\n", - "openssl 3.5.0 h81ee809_1 conda-forge\n", - "overrides 7.7.0 pypi_0 pypi\n", - "packaging 25.0 pypi_0 pypi\n", - "pandas 2.3.0 pypi_0 pypi\n", - "pandocfilters 1.5.1 pypi_0 pypi\n", - "parso 0.8.4 pypi_0 pypi\n", - "pexpect 4.9.0 pypi_0 pypi\n", - "pillow 11.2.1 pypi_0 pypi\n", - "pip 25.1.1 pyh8b19718_0 conda-forge\n", - "platformdirs 4.3.8 pypi_0 pypi\n", - "plotly 6.1.2 pypi_0 pypi\n", - "prometheus-client 0.22.1 pypi_0 pypi\n", - "prompt-toolkit 3.0.51 pypi_0 pypi\n", - "psutil 7.0.0 pypi_0 pypi\n", - "ptyprocess 0.7.0 pypi_0 pypi\n", - "pure-eval 0.2.3 pypi_0 pypi\n", - "pybigwig 0.3.24 pypi_0 pypi\n", - "pycparser 2.22 pypi_0 pypi\n", - "pygments 2.19.1 pypi_0 pypi\n", - "pyparsing 3.2.3 pypi_0 pypi\n", - "pysam 0.23.1 pypi_0 pypi\n", - "python 3.11.9 h932a869_0_cpython conda-forge\n", - "python-dateutil 2.9.0.post0 pypi_0 pypi\n", - "python-json-logger 3.3.0 pypi_0 pypi\n", - "pytz 2025.2 pypi_0 pypi\n", - "pyyaml 6.0.2 pypi_0 pypi\n", - "pyzmq 26.4.0 pypi_0 pypi\n", - "readline 8.2 h1d1bf99_2 conda-forge\n", - "referencing 0.36.2 pypi_0 pypi\n", - "requests 2.32.3 pypi_0 pypi\n", - "rfc3339-validator 0.1.4 pypi_0 pypi\n", - "rfc3986-validator 0.1.1 pypi_0 pypi\n", - "rpds-py 0.25.1 pypi_0 pypi\n", - "seaborn 0.13.2 pypi_0 pypi\n", - "send2trash 1.8.3 pypi_0 pypi\n", - "setuptools 80.9.0 pyhff2d567_0 conda-forge\n", - "six 1.17.0 pypi_0 pypi\n", - "sniffio 1.3.1 pypi_0 pypi\n", - "soupsieve 2.7 pypi_0 pypi\n", - "stack-data 0.6.3 pypi_0 pypi\n", - "terminado 0.18.1 pypi_0 pypi\n", - "tinycss2 1.4.0 pypi_0 pypi\n", - "tk 8.6.13 h5083fa2_1 conda-forge\n", - "tornado 6.5.1 pypi_0 pypi\n", - "tqdm 4.67.1 pypi_0 pypi\n", - "traitlets 5.14.3 pypi_0 pypi\n", - "types-python-dateutil 2.9.0.20250516 pypi_0 pypi\n", - "typing-extensions 4.14.0 pypi_0 pypi\n", - "tzdata 2025.2 pypi_0 pypi\n", - "uri-template 1.3.0 pypi_0 pypi\n", - "urllib3 2.4.0 pypi_0 pypi\n", - "wcwidth 0.2.13 pypi_0 pypi\n", - "webcolors 24.11.1 pypi_0 pypi\n", - "webencodings 0.5.1 pypi_0 pypi\n", - "websocket-client 1.8.0 pypi_0 pypi\n", - "wheel 0.45.1 pyhd8ed1ab_1 conda-forge\n", - "widgetsnbextension 4.0.14 pypi_0 pypi\n", - "xz 5.8.1 h9a6d368_1 conda-forge\n", - "xz-gpl-tools 5.8.1 h9a6d368_1 conda-forge\n", - "xz-tools 5.8.1 h39f12f2_1 conda-forge\n", - "zlib 1.2.13 hfb2fe0b_6 conda-forge\n" - ] - } - ], + "outputs": [], "source": [ "!conda list" ] @@ -1889,7 +1730,438 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Exporting pileups to bigwig" + "## Clustering and Classification\n", + "\n", + "This section shows how to work with processed files (`pileup.sorted.bed.gz` and `extract.h5`) to discover structure in your data.\n", + "\n", + "- **Read clustering**: build fixed-length windows per read, compute features (PCA, autocorrelation, density, stats), and cluster molecules. Supports single motifs or combined multi-motif windows.\n", + "- **Read classification**: train a binary classifier on read features to quantify separability between two samples; includes train/test metrics, confusion matrices, and per-read profile plots.\n", + "- **Region clustering**: cluster loci on their pileup profiles (single motif or concatenated multi-motif features), and visualize mean profiles plus genome distribution.\n", + "- **Region classification**: treat each locus feature vector as a sample for downstream classifiers.\n", + "\n", + "Optional clustering deps: `scikit-learn`, `scipy`, `hdbscan`, `umap-learn`, `pyranges`, `xgboost` (install via `pip install dimelo[clustering]`).\n", + "Make sure earlier parsing steps have produced the processed files before running these examples." + ] + }, + { + "cell_type": "markdown", + "id": "6f031f5a", + "metadata": {}, + "source": [ + "### Read analysis tips\n", + "- Use `span_full_window=True` and `orientation_aware=True` when extracting to keep windows consistent.\n", + "- For multi-motif windows, set `require_all_motifs=True` to drop reads missing motifs instead of zero-filling.\n", + "- Drop low-information reads by setting `require_nonzero_valid=True` and `min_valid_fraction` in `read_window_feature_matrix`.\n", + "- Inspect cluster/classifier QC: profile plots for clusters, confusion matrices and per-read profile plots for classifiers.\n", + "- Check locus homogeneity with `summarize_read_clusters_by_region` (dominant_fraction/entropy) to see if clusters are locus-driven.\n", + "- Beware class imbalance in classifiers; look at both accuracy and ROC-AUC, and at confusion matrices to spot overfitting." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Read Clustering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Single motif" + ] + }, + { + "cell_type": "markdown", + "id": "efbdf960", + "metadata": {}, + "source": [ + "*Motif semantics*: the extract file stores one row per motif per read. Passing multiple motifs yields multiple rows; use `build_multimotif_read_windows` to group motifs per read (drops missing motifs by default) or filter by `metadata['motif']` for per-motif analyses. Use `motif_index` in plotting helpers to pick which motif slice to display." + ] + }, + { + "cell_type": "markdown", + "id": "d5ae7b69", + "metadata": {}, + "source": [ + "**Sampling for speed**: Use `cluster.sample_rows` to downsample reads or loci before clustering/classification. Example:\n", + "\n", + "```python\n", + "sampled_feat, sampled_labels, idx = cluster.sample_rows(feature_matrix, labels, frac=0.2, stratify=True)\n", + "```\n", + "\n", + "Run heavy algorithms (e.g., spectral/DBSCAN/UMAP) on the sample, then apply insights back to the full set." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "ename": "FileNotFoundError", + "evalue": "[Errno 2] Unable to synchronously open file (unable to open file: name = 'output/extract.h5', errno = 2, error message = 'No such file or directory', flags = 0, o_flags = 0)", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mFileNotFoundError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 4\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mdimelo\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m cluster\n\u001b[32m 3\u001b[39m \u001b[38;5;66;03m# Combine motifs per read; windows are concatenated in the order given\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m4\u001b[39m rw_multi = \u001b[43mcluster\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbuild_multimotif_read_windows\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 5\u001b[39m \u001b[43m \u001b[49m\u001b[43mhdf5_file\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43moutput/extract.h5\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 6\u001b[39m \u001b[43m \u001b[49m\u001b[43mmotifs\u001b[49m\u001b[43m=\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mA,0\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mCG,0\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 7\u001b[39m \u001b[43m \u001b[49m\u001b[43mregions\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43moutput/regions.processed.bed\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 8\u001b[39m \u001b[43m \u001b[49m\u001b[43mwindow_size\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m2000\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 9\u001b[39m \u001b[43m \u001b[49m\u001b[43morientation_aware\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 10\u001b[39m \u001b[43m \u001b[49m\u001b[43mspan_full_window\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 11\u001b[39m \u001b[43m \u001b[49m\u001b[43mrequire_all_motifs\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 12\u001b[39m \u001b[43m)\u001b[49m\n\u001b[32m 14\u001b[39m feat_multi, _ = cluster.read_window_feature_matrix(\n\u001b[32m 15\u001b[39m rw_multi,\n\u001b[32m 16\u001b[39m n_pca=\u001b[32m6\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 19\u001b[39m min_valid_fraction=\u001b[32m0.05\u001b[39m,\n\u001b[32m 20\u001b[39m )\n\u001b[32m 21\u001b[39m clust_multi = cluster.cluster_read_windows(feat_multi, method=\u001b[33m\"\u001b[39m\u001b[33mkmeans\u001b[39m\u001b[33m\"\u001b[39m, n_clusters=\u001b[32m8\u001b[39m, random_state=\u001b[32m42\u001b[39m)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/GitHub/dimelo-toolkit/dimelo/cluster.py:514\u001b[39m, in \u001b[36mbuild_multimotif_read_windows\u001b[39m\u001b[34m(hdf5_file, motifs, regions, window_size, orientation_aware, single_strand, subset_parameters, span_full_window, require_all_motifs)\u001b[39m\n\u001b[32m 491\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 492\u001b[39m \u001b[33;03mGroup per-motif rows by read and return a combined window per read, concatenating motifs.\u001b[39;00m\n\u001b[32m 493\u001b[39m \n\u001b[32m (...)\u001b[39m\u001b[32m 510\u001b[39m \u001b[33;03m and val_matrix if available, metadata per combined read, and datasets info.\u001b[39;00m\n\u001b[32m 511\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 513\u001b[39m \u001b[38;5;66;03m# Load all per-motif rows\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m514\u001b[39m read_tuples, dataset_names, regions_dict = \u001b[43mload_processed\u001b[49m\u001b[43m.\u001b[49m\u001b[43mread_vectors_from_hdf5\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 515\u001b[39m \u001b[43m \u001b[49m\u001b[43mfile\u001b[49m\u001b[43m=\u001b[49m\u001b[43mhdf5_file\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 516\u001b[39m \u001b[43m \u001b[49m\u001b[43mmotifs\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mlist\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mmotifs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 517\u001b[39m \u001b[43m \u001b[49m\u001b[43mregions\u001b[49m\u001b[43m=\u001b[49m\u001b[43mregions\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 518\u001b[39m \u001b[43m \u001b[49m\u001b[43mwindow_size\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# we handle windowing here\u001b[39;49;00m\n\u001b[32m 519\u001b[39m \u001b[43m \u001b[49m\u001b[43msingle_strand\u001b[49m\u001b[43m=\u001b[49m\u001b[43msingle_strand\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 520\u001b[39m \u001b[43m \u001b[49m\u001b[43msubset_parameters\u001b[49m\u001b[43m=\u001b[49m\u001b[43msubset_parameters\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 521\u001b[39m \u001b[43m \u001b[49m\u001b[43mspan_full_window\u001b[49m\u001b[43m=\u001b[49m\u001b[43mspan_full_window\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 522\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 523\u001b[39m idx = _build_dataset_index(dataset_names)\n\u001b[32m 525\u001b[39m \u001b[38;5;66;03m# Group rows by read+region key\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/GitHub/dimelo-toolkit/dimelo/load_processed.py:1119\u001b[39m, in \u001b[36mread_vectors_from_hdf5\u001b[39m\u001b[34m(file, motifs, regions, window_size, single_strand, sort_by, calculate_mod_fractions, quiet, cores, subset_parameters, span_full_window)\u001b[39m\n\u001b[32m 1057\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 1058\u001b[39m \u001b[33;03mUser-facing function.\u001b[39;00m\n\u001b[32m 1059\u001b[39m \n\u001b[32m (...)\u001b[39m\u001b[32m 1115\u001b[39m \u001b[33;03m the available parameters.\u001b[39;00m\n\u001b[32m 1116\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 1117\u001b[39m _validate_subset_parameters(subset_parameters)\n\u001b[32m-> \u001b[39m\u001b[32m1119\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[43mh5py\u001b[49m\u001b[43m.\u001b[49m\u001b[43mFile\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfile\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mr\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mas\u001b[39;00m h5:\n\u001b[32m 1120\u001b[39m datasets: \u001b[38;5;28mlist\u001b[39m[\u001b[38;5;28mstr\u001b[39m] = [\n\u001b[32m 1121\u001b[39m name \u001b[38;5;28;01mfor\u001b[39;00m name, obj \u001b[38;5;129;01min\u001b[39;00m h5.items() \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(obj, h5py.Dataset)\n\u001b[32m 1122\u001b[39m ]\n\u001b[32m 1123\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mthreshold\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m h5:\n\u001b[32m 1124\u001b[39m \u001b[38;5;66;03m# we are looking at an .h5 file with the new, much better compressed format that does\u001b[39;00m\n\u001b[32m 1125\u001b[39m \u001b[38;5;66;03m# not know the data type intrinsically for mod and val vectors, so we must check\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Caskroom/mambaforge/base/envs/dimelo-toolkit/lib/python3.11/site-packages/h5py/_hl/files.py:564\u001b[39m, in \u001b[36mFile.__init__\u001b[39m\u001b[34m(self, name, mode, driver, libver, userblock_size, swmr, rdcc_nslots, rdcc_nbytes, rdcc_w0, track_order, fs_strategy, fs_persist, fs_threshold, fs_page_size, page_buf_size, min_meta_keep, min_raw_keep, locking, alignment_threshold, alignment_interval, meta_block_size, **kwds)\u001b[39m\n\u001b[32m 555\u001b[39m fapl = make_fapl(driver, libver, rdcc_nslots, rdcc_nbytes, rdcc_w0,\n\u001b[32m 556\u001b[39m locking, page_buf_size, min_meta_keep, min_raw_keep,\n\u001b[32m 557\u001b[39m alignment_threshold=alignment_threshold,\n\u001b[32m 558\u001b[39m alignment_interval=alignment_interval,\n\u001b[32m 559\u001b[39m meta_block_size=meta_block_size,\n\u001b[32m 560\u001b[39m **kwds)\n\u001b[32m 561\u001b[39m fcpl = make_fcpl(track_order=track_order, fs_strategy=fs_strategy,\n\u001b[32m 562\u001b[39m fs_persist=fs_persist, fs_threshold=fs_threshold,\n\u001b[32m 563\u001b[39m fs_page_size=fs_page_size)\n\u001b[32m--> \u001b[39m\u001b[32m564\u001b[39m fid = \u001b[43mmake_fid\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43muserblock_size\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfapl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfcpl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mswmr\u001b[49m\u001b[43m=\u001b[49m\u001b[43mswmr\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 566\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(libver, \u001b[38;5;28mtuple\u001b[39m):\n\u001b[32m 567\u001b[39m \u001b[38;5;28mself\u001b[39m._libver = libver\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Caskroom/mambaforge/base/envs/dimelo-toolkit/lib/python3.11/site-packages/h5py/_hl/files.py:238\u001b[39m, in \u001b[36mmake_fid\u001b[39m\u001b[34m(name, mode, userblock_size, fapl, fcpl, swmr)\u001b[39m\n\u001b[32m 236\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m swmr \u001b[38;5;129;01mand\u001b[39;00m swmr_support:\n\u001b[32m 237\u001b[39m flags |= h5f.ACC_SWMR_READ\n\u001b[32m--> \u001b[39m\u001b[32m238\u001b[39m fid = \u001b[43mh5f\u001b[49m\u001b[43m.\u001b[49m\u001b[43mopen\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mflags\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfapl\u001b[49m\u001b[43m=\u001b[49m\u001b[43mfapl\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 239\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m mode == \u001b[33m'\u001b[39m\u001b[33mr+\u001b[39m\u001b[33m'\u001b[39m:\n\u001b[32m 240\u001b[39m fid = h5f.open(name, h5f.ACC_RDWR, fapl=fapl)\n", + "\u001b[36mFile \u001b[39m\u001b[32mh5py/_objects.pyx:54\u001b[39m, in \u001b[36mh5py._objects.with_phil.wrapper\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mh5py/_objects.pyx:55\u001b[39m, in \u001b[36mh5py._objects.with_phil.wrapper\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mh5py/h5f.pyx:102\u001b[39m, in \u001b[36mh5py.h5f.open\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[31mFileNotFoundError\u001b[39m: [Errno 2] Unable to synchronously open file (unable to open file: name = 'output/extract.h5', errno = 2, error message = 'No such file or directory', flags = 0, o_flags = 0)" + ] + } + ], + "source": [ + "from dimelo import cluster\n", + "\n", + "# Combine motifs per read; windows are concatenated in the order given\n", + "rw_multi = cluster.build_multimotif_read_windows(\n", + " hdf5_file=\"output/extract.h5\",\n", + " motifs=[\"A,0\", \"CG,0\"],\n", + " regions=\"output/regions.processed.bed\",\n", + " window_size=1000,\n", + " orientation_aware=True,\n", + " span_full_window=True,\n", + " require_all_motifs=True,\n", + ")\n", + "\n", + "feat_multi, _ = cluster.read_window_feature_matrix(\n", + " rw_multi,\n", + " n_pca=6,\n", + " use_peak_features=False,\n", + " require_nonzero_valid=True,\n", + " min_valid_fraction=0.05,\n", + ")\n", + "clust_multi = cluster.cluster_read_windows(feat_multi, method=\"kmeans\", n_clusters=8, random_state=42)\n", + "\n", + "# QC plot: choose which motif slice to visualize\n", + "cluster.plot_cluster_profiles(\n", + " rw_multi.data_matrix,\n", + " clust_multi.labels_size_ordered,\n", + " motif_index=0, # 0 for A, 1 for CG\n", + " view_window_size=200,\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Multiple motif" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "ename": "FileNotFoundError", + "evalue": "[Errno 2] Unable to synchronously open file (unable to open file: name = 'output/extract.h5', errno = 2, error message = 'No such file or directory', flags = 0, o_flags = 0)", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mFileNotFoundError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 4\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mdimelo\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m cluster\n\u001b[32m 3\u001b[39m \u001b[38;5;66;03m# Combine motifs per read; windows are concatenated in the order given\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m4\u001b[39m rw_multi = \u001b[43mcluster\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbuild_multimotif_read_windows\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 5\u001b[39m \u001b[43m \u001b[49m\u001b[43mhdf5_file\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43moutput/extract.h5\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 6\u001b[39m \u001b[43m \u001b[49m\u001b[43mmotifs\u001b[49m\u001b[43m=\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mA,0\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mCG,0\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 7\u001b[39m \u001b[43m \u001b[49m\u001b[43mregions\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43moutput/regions.processed.bed\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 8\u001b[39m \u001b[43m \u001b[49m\u001b[43mwindow_size\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m2000\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 9\u001b[39m \u001b[43m \u001b[49m\u001b[43morientation_aware\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 10\u001b[39m \u001b[43m \u001b[49m\u001b[43mspan_full_window\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 11\u001b[39m \u001b[43m)\u001b[49m\n\u001b[32m 13\u001b[39m feat_multi, _ = cluster.read_window_feature_matrix(rw_multi, n_pca=\u001b[32m6\u001b[39m, use_peak_features=\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[32m 14\u001b[39m clust_multi = cluster.cluster_read_windows(feat_multi, method=\u001b[33m\"\u001b[39m\u001b[33mkmeans\u001b[39m\u001b[33m\"\u001b[39m, n_clusters=\u001b[32m8\u001b[39m, random_state=\u001b[32m42\u001b[39m)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/GitHub/dimelo-toolkit/dimelo/cluster.py:514\u001b[39m, in \u001b[36mbuild_multimotif_read_windows\u001b[39m\u001b[34m(hdf5_file, motifs, regions, window_size, orientation_aware, single_strand, subset_parameters, span_full_window, require_all_motifs)\u001b[39m\n\u001b[32m 491\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 492\u001b[39m \u001b[33;03mGroup per-motif rows by read and return a combined window per read, concatenating motifs.\u001b[39;00m\n\u001b[32m 493\u001b[39m \n\u001b[32m (...)\u001b[39m\u001b[32m 510\u001b[39m \u001b[33;03m and val_matrix if available, metadata per combined read, and datasets info.\u001b[39;00m\n\u001b[32m 511\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 513\u001b[39m \u001b[38;5;66;03m# Load all per-motif rows\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m514\u001b[39m read_tuples, dataset_names, regions_dict = \u001b[43mload_processed\u001b[49m\u001b[43m.\u001b[49m\u001b[43mread_vectors_from_hdf5\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 515\u001b[39m \u001b[43m \u001b[49m\u001b[43mfile\u001b[49m\u001b[43m=\u001b[49m\u001b[43mhdf5_file\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 516\u001b[39m \u001b[43m \u001b[49m\u001b[43mmotifs\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mlist\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mmotifs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 517\u001b[39m \u001b[43m \u001b[49m\u001b[43mregions\u001b[49m\u001b[43m=\u001b[49m\u001b[43mregions\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 518\u001b[39m \u001b[43m \u001b[49m\u001b[43mwindow_size\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# we handle windowing here\u001b[39;49;00m\n\u001b[32m 519\u001b[39m \u001b[43m \u001b[49m\u001b[43msingle_strand\u001b[49m\u001b[43m=\u001b[49m\u001b[43msingle_strand\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 520\u001b[39m \u001b[43m \u001b[49m\u001b[43msubset_parameters\u001b[49m\u001b[43m=\u001b[49m\u001b[43msubset_parameters\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 521\u001b[39m \u001b[43m \u001b[49m\u001b[43mspan_full_window\u001b[49m\u001b[43m=\u001b[49m\u001b[43mspan_full_window\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 522\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 523\u001b[39m idx = _build_dataset_index(dataset_names)\n\u001b[32m 525\u001b[39m \u001b[38;5;66;03m# Group rows by read+region key\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/GitHub/dimelo-toolkit/dimelo/load_processed.py:1119\u001b[39m, in \u001b[36mread_vectors_from_hdf5\u001b[39m\u001b[34m(file, motifs, regions, window_size, single_strand, sort_by, calculate_mod_fractions, quiet, cores, subset_parameters, span_full_window)\u001b[39m\n\u001b[32m 1057\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 1058\u001b[39m \u001b[33;03mUser-facing function.\u001b[39;00m\n\u001b[32m 1059\u001b[39m \n\u001b[32m (...)\u001b[39m\u001b[32m 1115\u001b[39m \u001b[33;03m the available parameters.\u001b[39;00m\n\u001b[32m 1116\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 1117\u001b[39m _validate_subset_parameters(subset_parameters)\n\u001b[32m-> \u001b[39m\u001b[32m1119\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[43mh5py\u001b[49m\u001b[43m.\u001b[49m\u001b[43mFile\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfile\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mr\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mas\u001b[39;00m h5:\n\u001b[32m 1120\u001b[39m datasets: \u001b[38;5;28mlist\u001b[39m[\u001b[38;5;28mstr\u001b[39m] = [\n\u001b[32m 1121\u001b[39m name \u001b[38;5;28;01mfor\u001b[39;00m name, obj \u001b[38;5;129;01min\u001b[39;00m h5.items() \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(obj, h5py.Dataset)\n\u001b[32m 1122\u001b[39m ]\n\u001b[32m 1123\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mthreshold\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m h5:\n\u001b[32m 1124\u001b[39m \u001b[38;5;66;03m# we are looking at an .h5 file with the new, much better compressed format that does\u001b[39;00m\n\u001b[32m 1125\u001b[39m \u001b[38;5;66;03m# not know the data type intrinsically for mod and val vectors, so we must check\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Caskroom/mambaforge/base/envs/dimelo-toolkit/lib/python3.11/site-packages/h5py/_hl/files.py:564\u001b[39m, in \u001b[36mFile.__init__\u001b[39m\u001b[34m(self, name, mode, driver, libver, userblock_size, swmr, rdcc_nslots, rdcc_nbytes, rdcc_w0, track_order, fs_strategy, fs_persist, fs_threshold, fs_page_size, page_buf_size, min_meta_keep, min_raw_keep, locking, alignment_threshold, alignment_interval, meta_block_size, **kwds)\u001b[39m\n\u001b[32m 555\u001b[39m fapl = make_fapl(driver, libver, rdcc_nslots, rdcc_nbytes, rdcc_w0,\n\u001b[32m 556\u001b[39m locking, page_buf_size, min_meta_keep, min_raw_keep,\n\u001b[32m 557\u001b[39m alignment_threshold=alignment_threshold,\n\u001b[32m 558\u001b[39m alignment_interval=alignment_interval,\n\u001b[32m 559\u001b[39m meta_block_size=meta_block_size,\n\u001b[32m 560\u001b[39m **kwds)\n\u001b[32m 561\u001b[39m fcpl = make_fcpl(track_order=track_order, fs_strategy=fs_strategy,\n\u001b[32m 562\u001b[39m fs_persist=fs_persist, fs_threshold=fs_threshold,\n\u001b[32m 563\u001b[39m fs_page_size=fs_page_size)\n\u001b[32m--> \u001b[39m\u001b[32m564\u001b[39m fid = \u001b[43mmake_fid\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43muserblock_size\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfapl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfcpl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mswmr\u001b[49m\u001b[43m=\u001b[49m\u001b[43mswmr\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 566\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(libver, \u001b[38;5;28mtuple\u001b[39m):\n\u001b[32m 567\u001b[39m \u001b[38;5;28mself\u001b[39m._libver = libver\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Caskroom/mambaforge/base/envs/dimelo-toolkit/lib/python3.11/site-packages/h5py/_hl/files.py:238\u001b[39m, in \u001b[36mmake_fid\u001b[39m\u001b[34m(name, mode, userblock_size, fapl, fcpl, swmr)\u001b[39m\n\u001b[32m 236\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m swmr \u001b[38;5;129;01mand\u001b[39;00m swmr_support:\n\u001b[32m 237\u001b[39m flags |= h5f.ACC_SWMR_READ\n\u001b[32m--> \u001b[39m\u001b[32m238\u001b[39m fid = \u001b[43mh5f\u001b[49m\u001b[43m.\u001b[49m\u001b[43mopen\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mflags\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfapl\u001b[49m\u001b[43m=\u001b[49m\u001b[43mfapl\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 239\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m mode == \u001b[33m'\u001b[39m\u001b[33mr+\u001b[39m\u001b[33m'\u001b[39m:\n\u001b[32m 240\u001b[39m fid = h5f.open(name, h5f.ACC_RDWR, fapl=fapl)\n", + "\u001b[36mFile \u001b[39m\u001b[32mh5py/_objects.pyx:54\u001b[39m, in \u001b[36mh5py._objects.with_phil.wrapper\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mh5py/_objects.pyx:55\u001b[39m, in \u001b[36mh5py._objects.with_phil.wrapper\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mh5py/h5f.pyx:102\u001b[39m, in \u001b[36mh5py.h5f.open\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[31mFileNotFoundError\u001b[39m: [Errno 2] Unable to synchronously open file (unable to open file: name = 'output/extract.h5', errno = 2, error message = 'No such file or directory', flags = 0, o_flags = 0)" + ] + } + ], + "source": [ + "from dimelo import cluster\n", + "\n", + "# Combine motifs per read; windows are concatenated in the order given\n", + "rw_multi = cluster.build_multimotif_read_windows(\n", + " hdf5_file=\"output/extract.h5\",\n", + " motifs=[\"A,0\", \"CG,0\"],\n", + " regions=\"output/regions.processed.bed\",\n", + " window_size=1000,\n", + " orientation_aware=True,\n", + " span_full_window=True,\n", + ")\n", + "\n", + "feat_multi, _ = cluster.read_window_feature_matrix(rw_multi, n_pca=6, use_peak_features=False)\n", + "clust_multi = cluster.cluster_read_windows(feat_multi, method=\"kmeans\", n_clusters=8, random_state=42)\n", + "\n", + "# QC plot (shows the first motif window for readability)\n", + "motif1_len = rw_multi.data_matrix.shape[1] // len([\"A,0\", \"CG,0\"])\n", + "cluster.plot_cluster_profiles(rw_multi.data_matrix[:, :motif1_len], clust_multi.labels_size_ordered, view_window_size=200)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Read Classification" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Single Motif" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'feat_single' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[3]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# sample_labels must align to feat_single rows (e.g., [\"sampleA\", \"sampleA\", \"sampleB\", ...])\u001b[39;00m\n\u001b[32m 2\u001b[39m clf_single = cluster.classify_read_features_binary(\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m \u001b[43mfeat_single\u001b[49m,\n\u001b[32m 4\u001b[39m sample_labels=sample_labels_single,\n\u001b[32m 5\u001b[39m classifier=\u001b[33m\"\u001b[39m\u001b[33mxgboost\u001b[39m\u001b[33m\"\u001b[39m, \u001b[38;5;66;03m# or logreg/sgd/random_forest/svc/knn\u001b[39;00m\n\u001b[32m 6\u001b[39m random_state=\u001b[32m42\u001b[39m,\n\u001b[32m 7\u001b[39m )\n\u001b[32m 8\u001b[39m \u001b[38;5;28mprint\u001b[39m(clf_single[\u001b[33m\"\u001b[39m\u001b[33mmetrics\u001b[39m\u001b[33m\"\u001b[39m])\n\u001b[32m 9\u001b[39m cluster.plot_confusion_matrices(clf_single[\u001b[33m\"\u001b[39m\u001b[33mpredictions\u001b[39m\u001b[33m\"\u001b[39m])\n", + "\u001b[31mNameError\u001b[39m: name 'feat_single' is not defined" + ] + } + ], + "source": [ + "# sample_labels must align to feat_single rows (e.g., [\"sampleA\", \"sampleA\", \"sampleB\", ...])\n", + "clf_single = cluster.classify_read_features_binary(\n", + " feat_single,\n", + " sample_labels=sample_labels_single,\n", + " classifier=\"xgboost\", # or logreg/sgd/random_forest/svc/knn\n", + " random_state=42,\n", + ")\n", + "print(clf_single[\"metrics\"])\n", + "cluster.plot_confusion_matrices(clf_single[\"predictions\"])\n", + "# cluster.plot_classification_profiles(rw_single.data_matrix, clf_single[\"predictions\"], split=\"test\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "70a3b81c", + "metadata": {}, + "source": [ + "*Tip*: For concatenated motif windows, use `motif_index` in plotting helpers to choose which motif slice to visualize (e.g., 0 for the first motif, 1 for the second)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Multiple motifs" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'feat_multi' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[4]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# sample_labels_multi must align to feat_multi rows\u001b[39;00m\n\u001b[32m 2\u001b[39m clf_multi = cluster.classify_read_features_binary(\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m \u001b[43mfeat_multi\u001b[49m,\n\u001b[32m 4\u001b[39m sample_labels=sample_labels_multi,\n\u001b[32m 5\u001b[39m classifier=\u001b[33m\"\u001b[39m\u001b[33mxgboost\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 6\u001b[39m random_state=\u001b[32m42\u001b[39m,\n\u001b[32m 7\u001b[39m )\n\u001b[32m 8\u001b[39m \u001b[38;5;28mprint\u001b[39m(clf_multi[\u001b[33m\"\u001b[39m\u001b[33mmetrics\u001b[39m\u001b[33m\"\u001b[39m])\n\u001b[32m 9\u001b[39m cluster.plot_confusion_matrices(clf_multi[\u001b[33m\"\u001b[39m\u001b[33mpredictions\u001b[39m\u001b[33m\"\u001b[39m])\n", + "\u001b[31mNameError\u001b[39m: name 'feat_multi' is not defined" + ] + } + ], + "source": [ + "# sample_labels_multi must align to feat_multi rows\n", + "clf_multi = cluster.classify_read_features_binary(\n", + " feat_multi,\n", + " sample_labels=sample_labels_multi,\n", + " classifier=\"xgboost\",\n", + " random_state=42,\n", + ")\n", + "print(clf_multi[\"metrics\"])\n", + "cluster.plot_confusion_matrices(clf_multi[\"predictions\"])\n", + "# cluster.plot_classification_profiles(rw_multi.data_matrix[:, :motif1_len], clf_multi[\"predictions\"], split=\"test\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Region Clustering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Single Motif" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Build region-level features from the pileup output (align motifs across loci)\n", + "pileup_matrix, region_info = cluster.region_feature_matrix_from_pileup(\n", + " bedmethyl_file=\"output/pileup.sorted.bed.gz\",\n", + " motif=\"A,0\",\n", + " regions=\"output/regions.processed.bed\",\n", + " window_size=500,\n", + " regions_5to3prime=True,\n", + ")\n", + "\n", + "# Cluster loci on their profiles\n", + "region_labels, _ = cluster.cluster_features(pileup_matrix, n_clusters=4)\n", + "\n", + "# Visualize mean profiles + heatmap of loci sorted by cluster\n", + "cluster.plot_region_cluster_profiles(pileup_matrix, region_labels, window_size=1000)\n", + "\n", + "# Export cluster-labeled BED and karyotype view\n", + "cluster.export_region_clusters_to_bed(region_info, region_labels, \"region_clusters.bed\")\n", + "cluster.plot_cluster_karyotype(\"region_clusters.bed\", \"ref.fasta.fai\") # chrom sizes (FAI) needed for karyotype plot\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Multiple motifs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Region Classification" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Single motif" + ] + }, + { + "cell_type": "markdown", + "id": "b5e02651", + "metadata": {}, + "source": [ + "*Tip*: When concatenating multiple motifs per locus, use `motif_index` in `plot_region_cluster_profiles` to choose which motif slice to visualize (e.g., 0 for the first motif). Clustering can still use the full concatenated matrix." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from dimelo import cluster\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.metrics import accuracy_score, roc_auc_score, confusion_matrix\n", + "from sklearn.linear_model import LogisticRegression\n", + "import numpy as np\n", + "\n", + "# Build region features\n", + "pileup_matrix_A, region_info = cluster.region_feature_matrix_from_pileup(\n", + " bedmethyl_file=\"output/pileup.sorted.bed.gz\",\n", + " motif=\"A,0\",\n", + " regions=\"output/regions.processed.bed\",\n", + " window_size=500,\n", + " regions_5to3prime=True,\n", + ")\n", + "\n", + "# Provide a label per region (e.g., condition, annotation class)\n", + "# region_labels_y = [...]\n", + "# Replace the following line with your actual labels:\n", + "# region_labels_y = np.array([...], dtype=str)\n", + "\n", + "# Split train/test\n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " pileup_matrix_A, region_labels_y, test_size=0.2, random_state=42, stratify=region_labels_y\n", + ")\n", + "\n", + "clf = LogisticRegression(max_iter=500, class_weight=\"balanced\")\n", + "clf.fit(X_train, y_train)\n", + "proba = clf.predict_proba(X_test)[:, 1] if len(np.unique(y_train)) == 2 else None\n", + "y_pred = clf.predict(X_test)\n", + "\n", + "print(\"Accuracy:\", accuracy_score(y_test, y_pred))\n", + "if proba is not None:\n", + " print(\"ROC-AUC:\", roc_auc_score(y_test, proba))\n", + "print(\"Confusion matrix:\\n\", confusion_matrix(y_test, y_pred))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Multiple motif" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from dimelo import cluster\n", + "import numpy as np\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.metrics import accuracy_score, roc_auc_score, confusion_matrix\n", + "from sklearn.linear_model import LogisticRegression\n", + "\n", + "motifs = [\"A,0\", \"CG,0\"]\n", + "matrices = []\n", + "for m in motifs:\n", + " mat, region_info = cluster.region_feature_matrix_from_pileup(\n", + " bedmethyl_file=\"output/pileup.sorted.bed.gz\",\n", + " motif=m,\n", + " regions=\"output/regions.processed.bed\",\n", + " window_size=500,\n", + " regions_5to3prime=True,\n", + " )\n", + " matrices.append(mat)\n", + "\n", + "# Concatenate features per locus\n", + "pileup_matrix_multi = np.concatenate(matrices, axis=1)\n", + "\n", + "# region_labels_y = [...] # labels per region, same length as pileup_matrix_multi rows\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " pileup_matrix_multi, region_labels_y, test_size=0.2, random_state=42, stratify=region_labels_y\n", + ")\n", + "\n", + "clf = LogisticRegression(max_iter=500, class_weight=\"balanced\")\n", + "clf.fit(X_train, y_train)\n", + "proba = clf.predict_proba(X_test)[:, 1] if len(np.unique(y_train)) == 2 else None\n", + "y_pred = clf.predict(X_test)\n", + "\n", + "print(\"Accuracy:\", accuracy_score(y_test, y_pred))\n", + "if proba is not None:\n", + " print(\"ROC-AUC:\", roc_auc_score(y_test, proba))\n", + "print(\"Confusion matrix:\\n\", confusion_matrix(y_test, y_pred))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Export" ] }, { @@ -1933,7 +2205,7 @@ "version_minor": 0 }, "text/plain": [ - "Step 1: Indexing contigs in pileup.sorted.bed.gz to set up bigwig header for pileup.fractions.bigwig: 0%| …" + "Step 1: Indexing contigs in pileup.sorted.bed.gz to set up bigwig header for pileup.fractions.bigwig: 0%| \u2026" ] }, "metadata": {}, @@ -2185,7 +2457,7 @@ "version_minor": 0 }, "text/plain": [ - "Step 2: Writing pileup.sorted.bed.gz contents to pileup.fractions.bigwig: 0%| | 0/17 [00:00 0\n", + " if read_window_min_valid_fraction > 0 and rw_single.val_matrix.shape[1] > 0:\n", + " valid_mask &= (valid_sums / rw_single.val_matrix.shape[1]) >= read_window_min_valid_fraction\n", + " assignments_single = assignments_single.loc[valid_mask].reset_index(drop=True)\n", + "\n", + "cluster_labels_single = [f\"C{int(v)}\" for v in np.asarray(read_clusters_single.labels_size_ordered)]\n", + "if len(assignments_single) != len(cluster_labels_single):\n", + " raise ValueError(\n", + " f\"Cluster/metadata length mismatch after alignment: {len(cluster_labels_single)} labels vs {len(assignments_single)} rows\"\n", + " )\n", + "assignments_single['cluster'] = cluster_labels_single\n", + "if 'source_label' not in assignments_single.columns:\n", + " assignments_single['source_label'] = 'merged'\n", + "\n", + "def _region_id_from_assignment(row):\n", + " strand = str(row.get('region_strand', '.'))\n", + " strand = strand if strand in {'+', '-', '.'} else '.'\n", + " return f\"{row['chromosome']}:{int(row['region_start'])}-{int(row['region_end'])},{strand}\"\n", + "\n", + "assignments_single['region_id'] = assignments_single.apply(_region_id_from_assignment, axis=1)\n", + "\n", + "region_summaries_single = (\n", + " assignments_single.groupby(['region_id', 'cluster', 'source_label'], dropna=False)\n", + " .size()\n", + " .reset_index(name='count')\n", + ")\n", + "region_summaries_single['fraction'] = (\n", + " region_summaries_single['count']\n", + " / region_summaries_single.groupby(['region_id', 'source_label'])['count'].transform('sum')\n", + ")\n", + "\n", + "cluster_result_for_chip_atlas = SharedClusterResult(\n", + " model=SharedClusterModel(\n", + " mode='read_global',\n", + " motifs=['A,0'],\n", + " feature_names=feature_names_single,\n", + " preprocessing={'tutorial_object': True},\n", + " estimator=read_clusters_single.model,\n", + " cluster_labels=sorted(pd.unique(assignments_single['cluster'])),\n", + " fit_metadata={'random_state': 42},\n", + " ),\n", + " assignments=assignments_single,\n", + " cluster_distribution=pd.DataFrame(),\n", + " condition_distribution=pd.DataFrame(),\n", + " distribution_change=None,\n", + " cluster_profiles=pd.DataFrame(),\n", + " region_summaries=region_summaries_single,\n", + " plot_data={},\n", + " metadata={'source': 'tutorial on/off-target merged read clustering'},\n", + ")\n", + "\n", + "print(f\"Prepared {len(assignments_single):,} reads across {assignments_single['cluster'].nunique()} clusters and {region_summaries_single['region_id'].nunique()} regions.\")\n", + "region_summaries_single.sort_values(['source_label', 'fraction'], ascending=[True, False]).head(12)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from dimelo import workflows\n", + "\n", + "RUN_CHIP_ATLAS = False # switch to True to submit enrichment jobs\n", + "\n", + "chip_atlas_kwargs = {\n", + " 'genome': 'hg38',\n", + " 'mode': 'per_cluster',\n", + " 'top_n_regions': 50,\n", + " 'min_fraction': 0.5,\n", + " # Optional specificity knobs (set to None for broad scans):\n", + " # 'antigen_class': 'TFs and others',\n", + " # 'antigen': 'CTCF',\n", + " # 'cell_type_class': 'Blood',\n", + " # 'cell_type': 'K562',\n", + " 'wait': True,\n", + " 'fetch_results': True,\n", + " 'timeout_seconds': 900,\n", + "}\n", + "\n", + "if RUN_CHIP_ATLAS:\n", + " try:\n", + " chip_results = workflows.chip_atlas_cluster_enrichment_workflow(\n", + " cluster_result=cluster_result_for_chip_atlas,\n", + " **chip_atlas_kwargs,\n", + " )\n", + " for cluster_name, result in chip_results.items():\n", + " print(f\"\\n[{cluster_name}] request_id={result.request_id} status={result.status}\")\n", + " if result.results is not None and not result.results.empty:\n", + " print(result.results.head(10).to_string(index=False))\n", + " else:\n", + " print('No parsed result table returned.')\n", + " except Exception as exc:\n", + " print(f\"ChIP-Atlas query failed: {exc}\")\n", + "else:\n", + " print('Skipping ChIP-Atlas query. Set RUN_CHIP_ATLAS=True to run enrichment.')\n" + ] } ], "metadata": { "kernelspec": { - "display_name": "dimelo", + "display_name": "dimelo-toolkit", "language": "python", "name": "python3" }, diff --git a/tutorial_offline.ipynb b/tutorial_offline.ipynb new file mode 100644 index 0000000..096e404 --- /dev/null +++ b/tutorial_offline.ipynb @@ -0,0 +1,314 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Offline End-to-End Tutorial Gate\n", + "\n", + "This notebook is a deterministic local-data tutorial path for stabilization verification.\n", + "It demonstrates core parse/load/plot paths, workflow outputs, and an offline-safe regulatory/chip-atlas setup flow.\n" + ], + "id": "cell-000" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import json\n", + "from matplotlib import pyplot as plt\n", + "\n", + "from dimelo import parse_bam, plot_depth_profile, plot_enrichment, plot_reads, workflows\n", + "from dimelo.models import SampleSpec\n", + "\n", + "repo_root = Path.cwd()\n", + "data_dir = repo_root / \"dimelo\" / \"test\" / \"data\"\n", + "reference_dir = repo_root / \"dimelo\" / \"test\" / \"output\"\n", + "artifact_dir = repo_root / \"artifacts\" / \"tutorial_offline\"\n", + "artifact_dir.mkdir(parents=True, exist_ok=True)\n", + "\n", + "ctcf_bam_file_updated = reference_dir / \"ctcf_demo.updated.bam\"\n", + "ctcf_target_regions = data_dir / \"ctcf_demo_peak.bed\"\n", + "ctcf_off_target_regions = data_dir / \"ctcf_demo_not_peak.bed\"\n", + "ref_genome_file = reference_dir / \"chm13.draft_v1.0.fasta\"\n", + "\n", + "required_paths = [ctcf_bam_file_updated, ctcf_target_regions, ctcf_off_target_regions, ref_genome_file]\n", + "missing = [str(path) for path in required_paths if not path.exists()]\n", + "if missing:\n", + " raise FileNotFoundError(\"Missing required local tutorial assets: \" + \", \".join(missing))\n", + "\n", + "print(\"Using local assets:\")\n", + "for path in required_paths:\n", + " print(f\" - {path}\")\n" + ], + "id": "cell-001" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Parse BAM (Pileup + Extract)" + ], + "id": "cell-002" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pileup_file, pileup_regions = parse_bam.pileup(\n", + " input_file=ctcf_bam_file_updated,\n", + " output_name=\"offline_tutorial_pileup\",\n", + " ref_genome=ref_genome_file,\n", + " output_directory=artifact_dir,\n", + " regions=[ctcf_target_regions, ctcf_off_target_regions],\n", + " motifs=[\"A,0\", \"CG,0\"],\n", + " thresh=190,\n", + " window_size=1000,\n", + " cores=1,\n", + " quiet=True,\n", + ")\n", + "\n", + "extract_file, extract_regions = parse_bam.extract(\n", + " input_file=ctcf_bam_file_updated,\n", + " output_name=\"offline_tutorial_extract\",\n", + " ref_genome=ref_genome_file,\n", + " output_directory=artifact_dir,\n", + " regions=[ctcf_target_regions, ctcf_off_target_regions],\n", + " motifs=[\"A,0\", \"CG,0\"],\n", + " thresh=190,\n", + " window_size=1000,\n", + " cores=1,\n", + " quiet=True,\n", + ")\n", + "\n", + "print(\"pileup_file:\", pileup_file)\n", + "print(\"extract_file:\", extract_file)\n", + "print(\"pileup_regions:\", pileup_regions)\n", + "print(\"extract_regions:\", extract_regions)\n" + ], + "id": "cell-003" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plotting Modules (Enrichment / Depth / Reads)" + ], + "id": "cell-004" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.figure(figsize=(5, 3))\n", + "plot_enrichment.by_regions(\n", + " mod_file_name=pileup_file,\n", + " regions_list=[ctcf_target_regions, ctcf_off_target_regions],\n", + " motif=\"A,0\",\n", + " sample_names=[\"on-target\", \"off-target\"],\n", + " single_strand=False,\n", + ")\n", + "enrichment_png = artifact_dir / \"enrichment_by_region.png\"\n", + "plt.tight_layout()\n", + "plt.savefig(enrichment_png, dpi=150)\n", + "plt.close()\n", + "\n", + "plt.figure(figsize=(6, 3))\n", + "plot_depth_profile.by_modification(\n", + " mod_file_name=pileup_file,\n", + " regions=ctcf_target_regions,\n", + " motifs=[\"A,0\", \"CG,0\"],\n", + " window_size=1000,\n", + " single_strand=False,\n", + " smooth_window=50,\n", + ")\n", + "depth_png = artifact_dir / \"depth_profile.png\"\n", + "plt.tight_layout()\n", + "plt.savefig(depth_png, dpi=150)\n", + "plt.close()\n", + "\n", + "single_region = \"chr1:114357437-114359753\"\n", + "plt.figure(figsize=(7, 3))\n", + "plot_reads.plot_reads(\n", + " mod_file_name=extract_file,\n", + " regions=single_region,\n", + " motifs=[\"A,0\", \"CG,0\"],\n", + " window_size=500,\n", + " single_strand=False,\n", + " thresh=0.5,\n", + " s=0.8,\n", + ")\n", + "reads_png = artifact_dir / \"single_read_raster.png\"\n", + "plt.tight_layout()\n", + "plt.savefig(reads_png, dpi=150)\n", + "plt.close()\n", + "\n", + "print(\"Saved:\")\n", + "print(f\" - {enrichment_png}\")\n", + "print(f\" - {depth_png}\")\n", + "print(f\" - {reads_png}\")\n" + ], + "id": "cell-005" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Workflow-Level Output (Shared Clustering)" + ], + "id": "cell-006" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "samples = [\n", + " SampleSpec(\n", + " sample_id=\"demo_A\",\n", + " condition=\"baseline\",\n", + " extract_h5=str(extract_file),\n", + " regions_bed=str(pileup_regions),\n", + " metadata={\"pileup_path\": str(pileup_file)},\n", + " ),\n", + " SampleSpec(\n", + " sample_id=\"demo_B\",\n", + " condition=\"treated\",\n", + " extract_h5=str(extract_file),\n", + " regions_bed=str(pileup_regions),\n", + " metadata={\"pileup_path\": str(pileup_file)},\n", + " ),\n", + "]\n", + "\n", + "shared_result = workflows.shared_cluster_distribution(\n", + " samples=samples,\n", + " mode=\"region_anchored\",\n", + " motifs=[\"A,0\"],\n", + " matched_regions=pileup_regions,\n", + " n_clusters=2,\n", + " training_sample_per_dataset=2000,\n", + " make_plots=False,\n", + " quiet=True,\n", + " cores=1,\n", + ")\n", + "\n", + "shared_result.condition_distribution.head()\n" + ], + "id": "cell-007" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Regulatory / ChIP-Atlas Setup Spec (Offline-Safe)" + ], + "id": "cell-008" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "regulatory_spec = workflows.resolve_regulatory_enrichment_spec(\n", + " species=\"homo_sapiens\",\n", + " providers=[\"screen\", \"unibind\"],\n", + " target_genome=\"hg38\",\n", + ")\n", + "\n", + "regulatory_spec\n" + ], + "id": "cell-009" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "RUN_CHIP_ATLAS = False # Optional live-network step\n", + "\n", + "chip_atlas_kwargs = {\n", + " \"genome\": \"hg38\",\n", + " \"mode\": \"per_cluster\",\n", + " \"top_n_regions\": 25,\n", + " \"timeout_seconds\": 120.0,\n", + "}\n", + "\n", + "if RUN_CHIP_ATLAS:\n", + " chip_result = workflows.chip_atlas_cluster_enrichment_workflow(\n", + " cluster_result=shared_result,\n", + " **chip_atlas_kwargs,\n", + " )\n", + " display(chip_result)\n", + "else:\n", + " print(\"Skipping live ChIP-Atlas request (RUN_CHIP_ATLAS=False).\")\n", + " print(\"Prepared call arguments:\", chip_atlas_kwargs)\n" + ], + "id": "cell-010" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Persist Tutorial Summary" + ], + "id": "cell-011" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "summary = {\n", + " \"pileup_file\": str(pileup_file),\n", + " \"extract_file\": str(extract_file),\n", + " \"pileup_regions\": str(pileup_regions),\n", + " \"extract_regions\": str(extract_regions),\n", + " \"plot_outputs\": {\n", + " \"enrichment\": str(artifact_dir / \"enrichment_by_region.png\"),\n", + " \"depth_profile\": str(artifact_dir / \"depth_profile.png\"),\n", + " \"single_read_raster\": str(artifact_dir / \"single_read_raster.png\"),\n", + " },\n", + " \"workflow\": {\n", + " \"cluster_labels\": list(shared_result.model.cluster_labels),\n", + " \"condition_rows\": int(shared_result.condition_distribution.shape[0]),\n", + " \"region_summary_rows\": int(0 if shared_result.region_summaries is None else shared_result.region_summaries.shape[0]),\n", + " },\n", + " \"regulatory_spec\": {\n", + " \"species\": regulatory_spec.species,\n", + " \"providers\": list(regulatory_spec.providers),\n", + " \"target_genome\": regulatory_spec.target_genome,\n", + " },\n", + "}\n", + "summary_path = artifact_dir / \"offline_tutorial_summary.json\"\n", + "summary_path.write_text(json.dumps(summary, indent=2))\n", + "print(\"Wrote\", summary_path)\n", + "summary\n" + ], + "id": "cell-012" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file