From c7e92ce7527a5f312b69cc9c5f31ec2a3e6ba61d Mon Sep 17 00:00:00 2001 From: Jacob Hyrum Tye <142532933+jacob-tye@users.noreply.github.com> Date: Thu, 10 Sep 2026 21:36:57 -0600 Subject: [PATCH 1/9] Add workflow to sync main branch into dev --- .github/workflows/sync-main-to-dev.yml | 59 ++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/sync-main-to-dev.yml diff --git a/.github/workflows/sync-main-to-dev.yml b/.github/workflows/sync-main-to-dev.yml new file mode 100644 index 0000000..a96ea70 --- /dev/null +++ b/.github/workflows/sync-main-to-dev.yml @@ -0,0 +1,59 @@ +name: Sync main into dev + +on: + push: + branches: + - main + +permissions: + contents: read + pull-requests: write + +jobs: + sync-main-to-dev: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check whether dev is behind main + id: check + run: | + git fetch origin main dev + + if [ "$(git rev-list --count origin/dev..origin/main)" -gt 0 ]; then + echo "needs_sync=true" >> "$GITHUB_OUTPUT" + else + echo "needs_sync=false" >> "$GITHUB_OUTPUT" + fi + + - name: Check for existing sync PR + if: steps.check.outputs.needs_sync == 'true' + id: pr + env: + GH_TOKEN: ${{ github.token }} + run: | + EXISTING_PR=$(gh pr list \ + --base dev \ + --head main \ + --state open \ + --json number \ + --jq '.[0].number // empty') + + echo "number=$EXISTING_PR" >> "$GITHUB_OUTPUT" + + - name: Create sync PR + if: > + steps.check.outputs.needs_sync == 'true' && + steps.pr.outputs.number == '' + env: + GH_TOKEN: ${{ github.token }} + run: | + gh pr create \ + --base dev \ + --head main \ + --title "Sync main into dev" \ + --body "Automatically created because main contains commits that are not yet in dev." From 8f15aa6c664c11349fe321f2bee9558784bde29d Mon Sep 17 00:00:00 2001 From: Jacob Hyrum Tye <142532933+jacob-tye@users.noreply.github.com> Date: Thu, 10 Sep 2026 21:43:15 -0600 Subject: [PATCH 2/9] Delete .github/workflows/sync-main-to-dev.yml Do not have the right current permissions right now --- .github/workflows/sync-main-to-dev.yml | 59 -------------------------- 1 file changed, 59 deletions(-) delete mode 100644 .github/workflows/sync-main-to-dev.yml diff --git a/.github/workflows/sync-main-to-dev.yml b/.github/workflows/sync-main-to-dev.yml deleted file mode 100644 index a96ea70..0000000 --- a/.github/workflows/sync-main-to-dev.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Sync main into dev - -on: - push: - branches: - - main - -permissions: - contents: read - pull-requests: write - -jobs: - sync-main-to-dev: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check whether dev is behind main - id: check - run: | - git fetch origin main dev - - if [ "$(git rev-list --count origin/dev..origin/main)" -gt 0 ]; then - echo "needs_sync=true" >> "$GITHUB_OUTPUT" - else - echo "needs_sync=false" >> "$GITHUB_OUTPUT" - fi - - - name: Check for existing sync PR - if: steps.check.outputs.needs_sync == 'true' - id: pr - env: - GH_TOKEN: ${{ github.token }} - run: | - EXISTING_PR=$(gh pr list \ - --base dev \ - --head main \ - --state open \ - --json number \ - --jq '.[0].number // empty') - - echo "number=$EXISTING_PR" >> "$GITHUB_OUTPUT" - - - name: Create sync PR - if: > - steps.check.outputs.needs_sync == 'true' && - steps.pr.outputs.number == '' - env: - GH_TOKEN: ${{ github.token }} - run: | - gh pr create \ - --base dev \ - --head main \ - --title "Sync main into dev" \ - --body "Automatically created because main contains commits that are not yet in dev." From d0d0fbd928af6ed924ab4a8cbbb04d47d5b76917 Mon Sep 17 00:00:00 2001 From: Jacob Hyrum Tye <142532933+jacob-tye@users.noreply.github.com> Date: Thu, 10 Sep 2026 22:18:32 -0600 Subject: [PATCH 3/9] Create CITATION.cff for MethylSeg Add citation file for MethylSeg software with details. --- CITATION.cff | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..e86cd61 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,17 @@ +cff-version: 1.2.0 +message: "If you use MethylSeg, please cite this software." +title: "MethylSeg" +type: software +abstract: >- + MethylSeg is a context-aware methylome segmentation method for identifying + partially methylated domains in whole-genome bisulfite sequencing and + methylation microarray data. +authors: + - family-names: "Tye" + given-names: "Jacob" + orcid: "https://orcid.org/0009-0000-6883-9572" + - family-names: "Clement" + given-names: "Kendell" + orcid: "https://orcid.org/0000-0003-3808-0811" +repository-code: "https://github.com/clementlab/MethylSeg" +url: "https://github.com/clementlab/MethylSeg" From 0343b38c59a7be0caebb44ce055dcd97e3b3eaab Mon Sep 17 00:00:00 2001 From: Jacob Tye Date: Thu, 10 Sep 2026 22:59:01 -0600 Subject: [PATCH 4/9] updated readme --- CITATION.cff | 1 + README.md | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index e86cd61..0b77cd7 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -2,6 +2,7 @@ cff-version: 1.2.0 message: "If you use MethylSeg, please cite this software." title: "MethylSeg" type: software +version: "0.1.8" abstract: >- MethylSeg is a context-aware methylome segmentation method for identifying partially methylated domains in whole-genome bisulfite sequencing and diff --git a/README.md b/README.md index 38e3808..e4ed2d4 100644 --- a/README.md +++ b/README.md @@ -255,8 +255,13 @@ The BED files themselves are tab-delimited and do not contain a header. ## Citation -A manuscript describing MethylSeg is in preparation. Citation information will -be added when it becomes available. +If you use MethylSeg, please cite the software using the +[CITATION.cff](https://github.com/clementlab/MethylSeg/blob/main/CITATION.cff) +metadata. On GitHub, select **Cite this repository** to copy the citation in APA +or BibTeX format. + +A manuscript describing MethylSeg is in preparation. Its citation will be added +when available. ## Planned support From 047104129ec2ff35356aee2720baadd86270de11 Mon Sep 17 00:00:00 2001 From: Jacob Tye Date: Fri, 18 Sep 2026 15:10:09 -0600 Subject: [PATCH 5/9] added pdf figure generation and parameter to set low coverage like values --- methylseg/helper_classes.py | 5 +++++ methylseg/methyl_state_assigner.py | 17 +++++++++++++++-- methylseg/methylseg_pathway.py | 2 ++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/methylseg/helper_classes.py b/methylseg/helper_classes.py index 8ed4b3f..480996e 100644 --- a/methylseg/helper_classes.py +++ b/methylseg/helper_classes.py @@ -174,6 +174,7 @@ def __init__( resolution="auto", min_coverage=10, remove_low_coverage_like_cpgs=False, + low_coverage_like_beta_values=None, chunk_size=1_000_000, retain_removed_rows=True, ): @@ -195,6 +196,8 @@ def __init__( If ``True``, remove CpGs with beta values commonly produced by very low coverage counts, such as 0.0, 0.25, 0.33, 0.5, 0.66/0.67, 0.75, and 1.0. + low_coverage_like_beta_values + A set of beta values that are indicative of low coverage. chunk_size Number of rows to read at a time when processing large files. retain_removed_rows @@ -207,6 +210,8 @@ def __init__( self.resolution = resolution self.min_coverage = min_coverage self.remove_low_coverage_like_cpgs = remove_low_coverage_like_cpgs + if low_coverage_like_beta_values is not None: + self.LOW_COVERAGE_LIKE_BETA_VALUES = frozenset(low_coverage_like_beta_values) self.chunk_size = chunk_size self.retain_removed_rows = retain_removed_rows diff --git a/methylseg/methyl_state_assigner.py b/methylseg/methyl_state_assigner.py index 9c79387..cf2266a 100644 --- a/methylseg/methyl_state_assigner.py +++ b/methylseg/methyl_state_assigner.py @@ -1,6 +1,7 @@ """Window-based emission feature engineering and KMeans state assignment.""" from enum import Enum +from pathlib import Path import textwrap import warnings @@ -2556,11 +2557,18 @@ def plot_feature_distributions_by_kmeans_state( show_plots: bool = True, state_colors: dict | None = None, state_cutoffs: dict | None = None, + save_plots: bool | None = None, + save_pdf: bool = False, ): """Plot training-emission histograms stratified by KMeans state. ``state_cutoffs`` optionally controls the biological-state display labels; it does not change the KMeans assignments. + + When ``save_plots`` is omitted, the legacy behavior is preserved: + plots are saved as PNG only when ``show_plots`` is false. Set + ``save_plots=True`` to save while displaying and ``save_pdf=True`` to + add same-stem PDF companions. """ if not hasattr(self, "model"): raise ValueError("No trained model found. Please train a model first.") @@ -2623,8 +2631,13 @@ def plot_feature_distributions_by_kmeans_state( ax.set_title(f"Distribution of {emission} by KMeans State") ax.legend() fig.tight_layout() + should_save = (not show_plots) if save_plots is None else save_plots + if should_save and self.out_dir is not None: + output_path = Path(self.out_dir) / f"feature_distribution_{emission}.png" + output_path.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(output_path) + if save_pdf: + fig.savefig(output_path.with_suffix(".pdf")) if show_plots: plt.show() - elif self.out_dir is not None: - fig.savefig(f"{self.out_dir}/feature_distribution_{emission}.png") plt.close(fig) diff --git a/methylseg/methylseg_pathway.py b/methylseg/methylseg_pathway.py index 13b0e69..e96ac6a 100644 --- a/methylseg/methylseg_pathway.py +++ b/methylseg/methylseg_pathway.py @@ -83,6 +83,7 @@ def prepare_sample_info( resolution: str = "auto", min_coverage: int = 10, remove_low_coverage_like_cpgs: bool = False, + low_coverage_like_beta_values: set[float] | None = None, ) -> tuple[SampleInfo, pd.DataFrame]: """ Prepare a methylation file into the package's canonical sample schema. @@ -112,6 +113,7 @@ def prepare_sample_info( resolution=resolution, min_coverage=min_coverage, remove_low_coverage_like_cpgs=remove_low_coverage_like_cpgs, + low_coverage_like_beta_values=low_coverage_like_beta_values, ).prepare() @staticmethod From 1258d61973b206bed9743333a05c072fcc9a7053 Mon Sep 17 00:00:00 2001 From: Jacob Tye Date: Fri, 18 Sep 2026 16:11:04 -0600 Subject: [PATCH 6/9] added roadmap.md and updated links --- .gitignore | 1 + README.md | 7 +++---- ROADMAP.md | 17 +++++++++++++++++ docs/conf.py | 12 ++++++++++-- 4 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 ROADMAP.md diff --git a/.gitignore b/.gitignore index 66d843e..543b4b3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ docs/_generated/ docs/tutorials/generated/ docs/readme.md docs/troubleshooting.md +docs/roadmap.md docs/quickstart.png docs/_static/logo.png data/archive.tar.gz diff --git a/README.md b/README.md index e4ed2d4..6b1f44b 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ values were observed during testing: > [!NOTE] > Numba has known compatibility issues on ARM-based systems. See the -> [Troubleshooting guide](TROUBLESHOOTING.md) for installation guidance. +> [Troubleshooting guide](https://github.com/clementlab/MethylSeg/blob/main/TROUBLESHOOTING.md) +> for installation guidance. ## Installation @@ -44,8 +45,6 @@ or install the current version from GitHub: python -m pip install "git+https://github.com/clementlab/MethylSeg.git" ``` - - ## Reference files ### Sample data @@ -265,7 +264,7 @@ when available. ## Planned support -- [ ] Add defaults for HM27 and EPIC microarray formats +See [ROADMAP.md](https://github.com/clementlab/MethylSeg/blob/main/ROADMAP.md) ## Reporting issues diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..5565633 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,17 @@ +# MethylSeg Roadmap + +## 1.1.0 + +- [ ] Develop and benchmark platform-specific defaults for HM27 and EPIC arrays +- [ ] Add optional confidence scores for MethylSeg region calls + +## 1.2.0 + +- [ ] Add a scalable multi-sample workflow + - Support efficient processing of multiple samples + - Generate cohort-level consensus regions and recurrence statistics + +## 2.0.0 + +- [ ] Replace the external ctHMM implementation with an in-house model + - This will change the underlying model specification and may produce results that differ from MethylSeg 1.x. diff --git a/docs/conf.py b/docs/conf.py index a493d4c..10af16a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,6 +12,7 @@ GENERATED_TUTORIALS = DOCS_ROOT / "tutorials" / "generated" STAGED_README = DOCS_ROOT / "readme.md" STAGED_TROUBLESHOOTING = DOCS_ROOT / "troubleshooting.md" +STAGED_ROADMAP = DOCS_ROOT / "roadmap.md" README_IMAGE = "quickstart.png" README_LOGO = "logo.png" REPOSITORY_URL = "https://github.com/clementlab/MethylSeg" @@ -107,15 +108,21 @@ def _manual_rst_pages() -> list[str]: "tutorials", "api", "troubleshooting", + "roadmap", ] def _stage_readme() -> None: - """Copy the README while retargeting links that are relative to the repo.""" + """Copy the README while retargeting repository links for Sphinx.""" readme = (PACKAGE_ROOT / "README.md").read_text() readme = _convert_github_alerts(readme) readme = readme.replace( - "(TROUBLESHOOTING.md)", "(troubleshooting.md)" + f"({REPOSITORY_URL}/blob/main/TROUBLESHOOTING.md)", + "(troubleshooting.md)", + ) + readme = readme.replace( + f"({REPOSITORY_URL}/blob/main/ROADMAP.md)", + "(roadmap.md)", ) readme = readme.replace( "(examples/run_full_pipeline.ipynb)", @@ -130,6 +137,7 @@ def _stage_readme() -> None: readme = readme.replace("(LICENSE.md)", f"({REPOSITORY_URL}/blob/main/LICENSE)") _write(STAGED_README, readme) _write(STAGED_TROUBLESHOOTING, (PACKAGE_ROOT / "TROUBLESHOOTING.md").read_text()) + _write(STAGED_ROADMAP, (PACKAGE_ROOT / "ROADMAP.md").read_text()) STATIC_ROOT.mkdir(parents=True, exist_ok=True) copy2(PACKAGE_ROOT / README_LOGO, STATIC_ROOT / README_LOGO) copy2(PACKAGE_ROOT / README_IMAGE, DOCS_ROOT / README_IMAGE) From 06fa480df8797f4b9de9b27f669d89adf144b388 Mon Sep 17 00:00:00 2001 From: Jacob Hyrum Tye <142532933+jacob-tye@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:43:06 -0600 Subject: [PATCH 7/9] Add DOI badge to README Added a DOI badge for citation in README. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6b1f44b..cc6da74 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ![logo](https://raw.githubusercontent.com/clementlab/MethylSeg/main/logo.png) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22904886.svg)](https://doi.org/10.5281/zenodo.22904886) + MethylSeg is a Python toolkit for identifying methylation domains from whole-genome bisulfite sequencing (WGBS) and microarray methylation data. It supports data preparation, methylation-state model training, genome From 92ef47a37254759a04a08c741b201a8017c5819a Mon Sep 17 00:00:00 2001 From: Jacob Hyrum Tye <142532933+jacob-tye@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:43:32 -0600 Subject: [PATCH 8/9] Change installation source to PyPI Updated installation instructions to use PyPI instead of TestPyPI. --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cc6da74..e1d8ab2 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,10 @@ values were observed during testing: ## Installation -Install from TestPyPI +Install from PyPI ```bash -python -m pip install \ - --index-url https://test.pypi.org/simple/ \ - --extra-index-url https://pypi.org/simple/ \ - methylseg +python -m pip install methylseg ``` or install the current version from GitHub: From 6acf7b391817d2d10f764a2a9cb939b909b31157 Mon Sep 17 00:00:00 2001 From: Jacob Hyrum Tye <142532933+jacob-tye@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:46:17 -0600 Subject: [PATCH 9/9] Bump version from 0.1.8 to 1.0.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 21c7658..d54ae81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "methylseg" -version = "0.1.8" +version = "1.0.0" description = "Methylation segmentation utilities used in the TCGA methylation analysis repo." readme = "README.md" requires-python = ">=3.10"