Source code for ensembl.tools.anno.simple_feature_annotation.cpg

# See the NOTICE file distributed with this work for additional information
# regarding copyright ownership.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Set of discriminant functions that can recognize structural and compositional features
such as CpG islands, promoter regions and first splice-donor sites.
References
----------
:cite:`cpg`
"""
__all__ = ["run_cpg"]

import argparse
import logging
import logging.config
import multiprocessing
from os import PathLike
from pathlib import Path
import re
import subprocess
from typing import List, Union

from ensembl.tools.anno.utils._utils import (
    check_exe,
    create_dir,
    check_gtf_content,
    get_seq_region_length,
    get_slice_id,
    slice_output_to_gtf,
    get_sequence,
)

logger = logging.getLogger(__name__)


[docs] def run_cpg( # pylint:disable=too-many-arguments, too-many-positional-arguments, too-many-locals, too-many-branches genome_file: PathLike, output_dir: Path, cpg_bin: Path = Path("cpg_lh"), cpg_min_length: int = 400, cpg_min_gc_content: int = 50, cpg_min_oe: float = 0.6, num_threads: int = 1, bedtools_bin: str = "/hps/software/users/ensembl/ensw/C8-MAR21-sandybridge/linuxbrew/bin/bedtools", # pylint:disable=line-too-long ) -> None: """ Run CpG islands on genomic slices :param genome_file: Genome file path. :type genome_file: PathLike :param output_dir: Working directory path :type output_dir: Path :param cpg_bin: CpG software path. :type cpg_bin: Path :param cpg_min_length: Min length of CpG islands :type cpg_min_length: int :param cpg_min_gc_content: Min GC frequency percentage :type cpg_min_gc_content: int :param cpg_min_oe: Min ratio of the observed to expected number of CpG (CpGo/e) :type cpg_min_oe: float :param num_threads: int, number of threads. :type num_threads: int :param bedtools_bin: Bedtools executable path. :type bedtools_bin: str :return: None :rtype: None """ # Use default path if user didn't supply one cpg_bin = cpg_bin or Path("cpg_lh") check_exe(cpg_bin) cpg_dir = create_dir(output_dir, "cpg_output") output_file = cpg_dir / "annotation.gtf" if output_file.exists(): transcript_count = check_gtf_content(output_file, "simple_feature") if transcript_count > 0: logger.info("Cpg gtf file exists") return logger.info("Creating list of genomic slices") seq_region_to_length = get_seq_region_length(genome_file, 5000) slice_ids_per_region = get_slice_id( seq_region_to_length, slice_size=1000000, overlap=0, min_length=5000 ) # pylint:disable=line-too-long logger.info("Running CpG") pool = multiprocessing.Pool(int(num_threads)) # pylint:disable=consider-using-with for slice_id in slice_ids_per_region: pool.apply_async( _multiprocess_cpg, args=( cpg_bin, slice_id, genome_file, cpg_dir, cpg_min_length, cpg_min_gc_content, cpg_min_oe, bedtools_bin, ), ) pool.close() pool.join() slice_output_to_gtf(cpg_dir, "feature_id", "cpg", True, ".cpg.gtf") for gtf_file in cpg_dir.glob("*.cpg.gtf"): gtf_file.unlink()
def _multiprocess_cpg( # pylint:disable=too-many-arguments, too-many-locals, too-many-branches, too-many-positional-arguments cpg_bin: Path, slice_id: List[str], genome_file: Path, cpg_dir: Path, cpg_min_length: int = 400, cpg_min_gc_content: int = 50, cpg_min_oe: float = 0.6, bedtools_bin: str = "/hps/software/users/ensembl/ensw/C8-MAR21-sandybridge/linuxbrew/bin/bedtools", # pylint:disable=line-too-long ) -> None: """ Annotation of CpG islands on multiprocess on genomic slices Args: cpg_bin: CpG software path. slice_id: Slice id to run CpG on. genome_file : Genome file. cpg_dir : Output dir. cpg_min_length : Min length of CpG islands cpg_min_gc_content : Min GC frequency percentage cpg_min_oe : Min ratio of the observed to expected number of CpG (CpGo/e) bedtools_bin: Bedtools executable path. """ region_name, start, end = slice_id logger.info( "Processing slice to find CpG islands with cpg_lh: %s:%s:%s", region_name, start, end, ) seq = get_sequence(region_name, int(start), int(end), 1, genome_file, cpg_dir, bedtools_bin) slice_name = f"{region_name}.rs{start}.re{end}" # with TemporaryDirectory(dir=cpg_dir) as tmpdirname: slice_file = cpg_dir / f"{slice_name}.fa" with open(slice_file, "w+", encoding="utf8") as region_out: region_out.write(f">{region_name}\n{seq}\n") region_results = cpg_dir / f"{slice_file}.cpg.gtf" output_file = Path(f"{slice_file}.cpg") cpg_cmd = [str(cpg_bin), str(slice_file)] with open(output_file, "w+", encoding="utf8") as cpg_out: subprocess.run(cpg_cmd, stdout=cpg_out, check=True) _create_cpg_gtf( output_file, region_results, region_name, cpg_min_length, cpg_min_gc_content, cpg_min_oe, ) slice_file.unlink() output_file.unlink() def _create_cpg_gtf( # pylint:disable=too-many-arguments, too-many-locals, too-many-branches, too-many-positional-arguments output_file: Path, region_results: Path, region_name: str, cpg_min_length: int = 400, cpg_min_gc_content: int = 50, cpg_min_oe: float = 0.6, ) -> None: """ Read the fasta file and save the content in gtf format All the genomic slices are collected in a single gtf output Args: output_file : GTF file with final results. region_results : GTF file with the results per region. region_name :Coordinates of genomic slice. cpg_dir : Output dir. cpg_min_length : Min length of CpG islands cpg_min_gc_content : Min GC frequency percentage cpg_min_oe : Min ratio of the observed to expected number of CpG (CpGo/e) """ with ( open(output_file, "r", encoding="utf8") as cpg_in, open(region_results, "w+", encoding="utf8") as cpg_out, ): feature_count = 1 for line in cpg_in: result_match = re.search(r"^" + region_name, line) if result_match: results = line.split() start = int(results[1]) end = int(results[2]) length = end - start + 1 score = float(results[3]) gc_content = float(results[6]) oe_score_str = results[7] oe_score: Union[float, int] if oe_score_str in ("-", "inf"): oe_score = 0 else: oe_score = float(oe_score_str) if ( int(length) >= int(cpg_min_length) and gc_content >= int(cpg_min_gc_content) and oe_score >= float(cpg_min_oe) ): gtf_line = ( f"{region_name}\tCpG\tsimple_feature\t{start}\t" f'{end}\t.\t+\t.\tfeature_id "{feature_count}"; score "{score}";\n' ) cpg_out.write(gtf_line) def parse_args(): """Parse command line arguments.""" parser = argparse.ArgumentParser(description="CpG's arguments") parser.add_argument("--genome_file", required=True, help="Genome file path") parser.add_argument("--output_dir", required=True, help="Output directory path") parser.add_argument("--cpg_bin", default="cpg_lh", help="CpG executable path") parser.add_argument("--cpg_min_length", type=int, default=400, help="Min length of CpG islands") parser.add_argument( "--cpg_min_gc_content", type=int, default=50, help="Min GC frequency percentage" ) # pylint:disable=line-too-long parser.add_argument( "--cpg_min_oe", type=float, default=0.6, help="Min ratio of the observed to expected number of CpG (CpGo/e)", ) parser.add_argument("--num_threads", type=int, default=1, help="Number of threads") parser.add_argument( "--bedtools_bin", default="/hps/software/users/ensembl/ensw/C8-MAR21-sandybridge/linuxbrew/bin/bedtools", help="Bedtools executable path", ) return parser.parse_args() def main(): """CpG's entry-point.""" args = parse_args() log_file_path = create_dir(args.output_dir, "log") / "cpg.log" loginipath = Path(__file__).parents[6] / "conf" / "logging.conf" logging.config.fileConfig( loginipath, defaults={"logfilename": str(log_file_path)}, disable_existing_loggers=False, ) run_cpg( args.genome_file, args.output_dir, args.cpg_bin, args.cpg_min_length, args.cpg_min_gc_content, args.cpg_min_oe, args.num_threads, args.bedtools_bin, ) if __name__ == "__main__": main()